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