Java NIO: Кога правилно да превключвате между OP_WRITE и OP_READ

Като малко предистория:

Имам връзка към сървър с SocketChannel, SelectionKey... и т.н. От страна на клиента, ако искам да изпратя нещо на сървъра, просто записвам данните си в ByteBuffer и ги изпращам през канала на сокета. Ако всичко е написано, аз съм готов и мога да се върна към OP_READ. Ако не всичко е записано, вземам останалите байтове, съхранявам ги в буфер "за изпращане" някъде и маркирам OP_WRITE на ключа (добра идея ли е да заменя OP_READ, така че да е само запис?).

Следователно, следващия път, когато извикам selectNow(), предполагам, че ще разпознае OP_WRITE и ще се опита да изчисти повече данни (което ще се опитам да направя, като вляза в друг цикъл за запис с данните за запис и повторя предишния, ако е необходимо ).

Това ме навежда на два въпроса:

  • Трябва ли да го оставя в OP_WRITE, докато всички данни бъдат изчистени? Или трябва да променя на OP_READ и да опитам каквото и да е четене между тях?

Ако каналът за писане е пълен и не мога да пиша, трябва ли просто да продължа да повтарям, докато мога да започна да пиша? Ако връзката внезапно се задуши, не съм сигурен дали трябва просто да напиша това, което мога, да обърна обратно към OP_READ, да се опитам да прочета и след това да обърна обратно към OP_WRITE. От това, което прочетох, изглежда, че това не е правилният начин за правене на нещата (и може да причини големи режийни разходи, постоянно превключващи напред-назад?).

  • Какъв е оптималният начин за обработка на четене и запис на групови данни, когато и двата буфера могат да се напълнят?

Четенето звучи лесно, защото просто превъртате, докато данните се изразходват, но при запис... сървърът може само да пише, а не да чете. Това ще ви остави с доста пълен буфер за изпращане и въртенето завинаги на OP_WRITE без четене би било лошо. Как избягвате тази ситуация? Задавате ли таймер, на който просто спирате опитите за писане и започвате да четете отново, ако буферът за изпращане не се изчиства? Ако е така, премахвате ли OP_WRITE и го помните ли за по-късно?

Страничен въпрос: Имате ли дори нужда от OP_READ, за да четете от мрежата? Не съм сигурен дали е като OP_WRITE, където го маркираш само в конкретен случай (само в случай, че го правя погрешно, тъй като го имам на OP_READ 99,9% от времето).

В момента просто задавам ключа си на OP_READ и след това го оставям в този режим, чакайки данни, и след това отивам на OP_WRITE, ако и само ако записът не успее да изпрати всички данни (със стойност на write() 0).


person Water    schedule 22.06.2015    source източник
comment
Мисля, че бих искал да видя някакъв (опростен) код. Споменавате OP_WRITE и selectNow(), но не Selector, а само SocketChannel, така че наистина съм объркан какво, по дяволите, правите.   -  person markspace    schedule 22.06.2015
comment
@markspace Как е объркващо? Селекторът се подразбира, как бихте извикали selectNow() без селектор? На някои от нещата ми може да се отговори, без дори да е необходим код, тъй като втората половина е чисто концептуална.   -  person Water    schedule 22.06.2015
comment
Използването на селектор е достатъчно сложно дори с код. Само с описание на английски няма начин да позная какво всъщност се случва. Ако някой друг иска да познае, нокаутирайте се, но не мога да разбера какво всъщност се опитвате да направите/имате проблем.   -  person markspace    schedule 22.06.2015
comment
@markspace Напълно ясно е.   -  person user207421    schedule 22.06.2015


Отговори (2)


Трябва ли да го оставя в OP_WRITE, докато всички данни бъдат изчистени? Или трябва да променя на OP_READ и да опитам каквото и да е четене между тях?

Има различни мнения за това. Моето е, че партньорът трябва да чете всяка част от отговора, който изпращате, преди да изпрати нова заявка, и ако не го направи, той просто се държи лошо и това не трябва да насърчавате, като четете напред. В противен случай просто ще останете без памет в крайна сметка и не трябва да позволявате на клиент да ви причини това. Разбира се, това предполага, че вие ​​сте сървърът в протокола заявка-отговор. Други ситуации имат свои собствени изисквания.

Ако каналът за писане е пълен и не мога да пиша, трябва ли просто да продължа да повтарям, докато мога да започна да пиша?

Не, изчаквате OP_WRITE да се задейства.

Ако връзката внезапно се задуши, не съм сигурен дали трябва просто да напиша това, което мога, да обърна обратно към OP_READ, да се опитам да прочета и след това да обърна обратно към OP_WRITE. От това, което прочетох, изглежда, че това не е правилният начин за правене на нещата (и може да причини големи режийни разходи, постоянно превключващи напред-назад?).

Режийните разходи не са значителни, но това е грешно нещо в ситуацията, която описах по-горе.

Какъв е оптималният начин за обработка на четене и запис на групови данни, когато и двата буфера могат да се напълнят?

Като цяло, четете, когато OP_READ задейства; пишете, когато имате нужда; и използвайте OP_WRITE, за да ви уведоми кога изходящият щанд се е освободил.

Имате ли дори нужда от OP_READ, за да четете от мрежата?

Да иначе просто пушиш процесора.

person user207421    schedule 22.06.2015

Всеки път, когато трябва да пишете, просто задайте интересуващата ви операция на (OP_READ || OP_WRITE). Когато приключите с писането, просто задайте интересуващата ви операция на OP_READ.

това е всичко, което трябва да направите.

person banbul lary    schedule 17.08.2017
comment
Не е необходимо да задавате OP_WRITE „когато трябва да пишете“. Трябва да го зададете, когато трябва да знаете кога можете да пишете, след като записът е върнал нула, и в никакъв друг момент. - person user207421; 12.08.2020