Java NIO: как узнать, когда SocketChannel read() завершен с неблокирующим вводом-выводом

в настоящее время я использую неблокирующий SocketChannel (Java 1.6) для работы в качестве клиента на сервере Redis. Redis принимает команды простого текста непосредственно над сокетом, завершается CRLF и отвечает в-как, быстрый пример:

отправить: 'PINGrn'

RECV: '+ PONGrn'

Redis также может возвращать огромные ответы (в зависимости от того, что вы просите) со многими разделами rn-завершенных данных как часть одного ответ.

Я использую стандарт while (socket.read () > 0) {//добавить байты} цикл для чтения байтов из сокета и повторной сборки их на стороне клиента в ответ.

примечание: Я не использую селектор, просто несколько клиентских SocketChannels, подключенных к серверу, ожидая обслуживания команд отправки/получения.

Что меня смущает, так это контракт на SocketChannel.read () метод неблокирующий режим, в частности, как узнать, когда сервер закончит отправку, и у меня есть все сообщение.

У меня есть несколько методов защиты от слишком быстрого возврата и предоставления серверу возможности ответить, но одна вещь, на которой я застрял:

  1. это когда-нибудь возможно для прочитать() вернуть байт, то при последующем вызове возвращают байт, но по другому последующий вызов снова вернуть байт?

в основном, могу ли я поверьте, что сервер отвечает мне, если я получил хотя бы 1 байт и в конечном итоге прочитать() возвращает 0, тогда я знаю, что я закончил, или возможно, сервер был просто занят и может отбросить еще несколько байтов, если я подожду и продолжу пытаться?

Если это can продолжайте отправлять байты даже после того, как read () вернул 0 байт (после предыдущих успешных чтений), тогда я понятия не имею, как сказать, когда сервер закончил говорить со мной и на самом деле смущен как java.io.* стиль связи будет даже знать, когда сервер "сделано"либо.

Как вы, ребята, знаете, read никогда не возвращает -1, если соединение не мертво, и это стандартные долгоживущие соединения DB, поэтому я не буду закрывать и открывать их по каждому запросу.

Я знаю, что популярный ответ (по крайней мере, для этих вопросов NIO) состоял в том, чтобы посмотреть на гризли, мину или Нетти-если возможно, я действительно хотел бы узнать, как все это работает в сыром состоянии, прежде чем принять некоторые 3-й партии зависимостей.

спасибо.

Бонус-Вопрос:

Я изначально думал, что блокирующий SocketChannel будет способом пойти с этим, поскольку я действительно не хочу, чтобы вызывающий что-либо делал, пока я не обработаю их команду и не дам им ответ в любом случае.

Если это окажется лучшим способом пойти, я был немного смущен, увидев этот SocketChannel.блоки read (), пока нет байтов, достаточных для заполнения данного буфер... не читая все байт за байтом, я не могу понять, как это поведение по умолчанию на самом деле предназначено для использования... Я никогда не знаю точно размер ответа, возвращающегося с сервера, поэтому мои звонки в SocketChannel.read () всегда блокируется до тайм-аута (в этот момент я наконец вижу, что содержимое находится в буфере).

Я не совсем понимаю, как правильно использовать метод блокировки, так как он всегда зависает при чтении.

4 ответов


если он может продолжать отправлять байты даже после того, как read() вернул 0 байт (после предыдущих успешных чтений), то я понятия не имею, как сказать, когда сервер закончил говорить со мной, и на самом деле смущен, как java.io.* стиль связи будет даже знать, когда сервер "сделано"либо.

прочитайте и следуйте протоколу:

http://redis.io/topics/protocol

спецификация описывает возможные типы ответов и как узнать их. Некоторые заканчиваются строкой, в то время как многострочные ответы включают количество префиксов.

ответы

Redis будет отвечать на команды с различными видами ответов. Можно проверить вид ответа с первого байта, отправленного сервером:

  • С одной строкой ответа первый байт ответа будет "+"
  • С сообщением об ошибке первый байт ответа будет "- "
  • с целым числом первым байтом ответа будет":"
  • с массовым ответом первый байт ответа будет"$"
  • С Multi-bulk ответом первый байт ответа будет "*"

однострочный ответ

однострочный ответ в виде однострочной строки начиная с " + "заканчивается на"\r\n". ...

...

Multi-bulk ответы

такие команды, как LRANGE, должны возвращать несколько значений (каждый элемент списка является значением, а LRANGE должен возвращать более одного элемента). Это достигается с помощью нескольких массовых записей,с префиксом начальной строки, указывающей, сколько объемных записей будет следовать.


возможно ли, чтобы read() возвращал байты, тогда при последующем вызове не возвращаются байты, но при другом последующем вызове снова возвращаются некоторые байты? В принципе, могу ли я доверять, что сервер отвечает мне, если я получил по крайней мере 1 байт и в конечном итоге read() возвращает 0, тогда я знаю, что я закончил, или возможно, что сервер был просто занят и может отбросить еще несколько байтов, если я подожду и продолжу пытаться?

Да, это возможно. Его не только из-за того, что сервер занят, но и перегруженность сети и сбитые маршруты могут вызвать данные для "паузы". Данные-это поток, который может "приостановить" в любом месте потока без связи с протоколом приложения.

продолжайте считывать поток в буфер. Взглянуть на первый символ, чтобы определить, какой тип ответа ждать. Проверьте буфер после каждого успешного чтения, пока буфер не содержит полное сообщение в соответствии со спецификацией.


Я изначально думал, что блокирующий SocketChannel будет идти с этим как Я действительно не хочу, чтобы вызывающий что-либо делал, пока я не обработаю их команду и не дам им ответ.


посмотрите на свои спецификации Redis для этого ответа.

это не противоречит правилам для вызова .read() для возврата 0 байтов при одном вызове и 1 или более байтов при последующем вызове. Это совершенно законно. Если что-то должно было вызвать задержку доставки, либо из-за задержки сети или медлительности сервера Redis, это могло произойти.

ответ, который вы ищете, - это тот же ответ на вопрос: "если я подключился вручную к серверу Redis и отправил команда, как я мог знать, когда это было сделано, посылая мне ответ, чтобы я мог отправить другую команду?"

ответ должен быть найден в спецификации Redis. Если нет глобального маркера, который сервер отправляет после выполнения вашей команды,это может быть реализовано на основе команд. Если спецификации Redis не позволяют этого, то это ошибка в спецификациях Redis. Они должны сказать вам, как сказать, когда они отправили все свои данные. Вот почему оболочки имеют командные подсказки. У Redis должен быть эквивалент.

в случае, если Redis не имеет этого в своих спецификациях, я бы предложил ввести какую-то функциональность таймера. Код вашего потока, обрабатывающего сокет, сигнализирует, что команда завершена после того, как данные не были получены в течение определенного периода времени, например, пяти секунд. Выберите период времени, который значительно больше, чем самая длинная команда занимает для выполнения на сервер.


Я использую стандарт while (socket.прочитать() > 0) {//добавляем bytes} loop

Это не стандартная техника в NIO. Вы должны сохраните результат чтения в переменной и проверьте его на:

  1. -1, указывая EOS, что означает, что вы должны закрыть канал
  2. ноль, что означает отсутствие данных для чтения, что означает, что вы должны вернуться к циклу select () и
  3. положительное значение, что означает, что вы прочитали это много байтов, которые вы должны извлечь и удалить из ByteBuffer (get()/compact()) перед продолжением.

прошло много времени, но ... . .

В настоящее время я использую неблокирующий SocketChannel

чтобы быть ясным, SocketChannels блокируются по умолчанию; чтобы сделать их неблокирующими, необходимо явно вызвать SocketChannel#configureBlocking(false)

Я предполагаю, что вы сделали это

Я не использую селектор

Вау; это проблема; если вы собираетесь использовать неблокирующие каналы, то вы всегда должны использовать Селектор (по крайней мере, для чтения); в противном случае вы столкнетесь с описанной вами путаницей, а именно. read(ByteBuffer) == 0 ничего не значит (Ну, это означает, что в буфере приема tcp нет байтов в этот момент).

это аналогично проверке вашего почтового ящика, и он пуст; означает ли это, что письмо никогда не придет? никогда не посылали?

что меня смущает, так это контракт SocketChannel.метод Read() в неблокирующем режиме, в частности, как узнать, когда сервер закончит отправку, и у меня есть все сообщение.

существует контракт - > если селектор выбрал канал для операции чтения,затем следующий вызов SocketChannel#read(ByteBuffer) гарантированно возвращается > 0 (предполагая, что есть место в ByteBuffer arg)

именно поэтому вы используете селектор, и потому что он может в одном вызове выбора "выбрать" 1Ks SocketChannels, которые имеют байты, готовые к чтению

Теперь нет ничего неправильно использовать SocketChannels в режиме блокировки по умолчанию; и учитывая ваше описание (клиент или два), вероятно, нет причин, чтобы его проще; но если вы хотите использовать неблокирующие каналы, используйте селектор