Java NIO: Как узнать, когда SocketChannel read () завершается с неблокирующим вводом / выводом - PullRequest
6 голосов
/ 07 февраля 2011

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

SEND: 'PING \ r \ n'

RECV:'+ PONG \ r \ n'

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

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

ПРИМЕЧАНИЕ. Я не использую Selector, я просто использую несколько клиентских SocketChannels на стороне клиента, подключенных к серверу, ожидающих обслуживания команд отправки / получения.

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

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

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

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

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

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

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

Спасибо.

Бонусный вопрос:

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

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

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

Ответы [ 4 ]

4 голосов
/ 08 февраля 2011

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

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

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

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

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

3 голосов
/ 08 февраля 2011

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

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

http://redis.io/topics/protocol

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

Ответов

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

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

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

Однострочный ответ представлен в виде однострочной строки , начинающейся с "+", оканчивающейся на "\ r \ n" . ...

...

Множественные ответы

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


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

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

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


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

Я думаю, что вы правы. Исходя из моего быстрого взгляда на спецификацию, блокировка чтения не будет работать для этого протокола. Поскольку он выглядит на основе строки, BufferedReader может помочь, но вам все еще нужно знать, как распознать, когда ответ завершен.

2 голосов
/ 07 сентября 2015

Это было давно, но.,,

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

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

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

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

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

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

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

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

Именно поэтому вы используете Selector, и потому что он может за один вызов select "выбрать" 1KK SocketChannels, у которых есть готовые байты для чтения

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

2 голосов
/ 15 апреля 2011

Я использую стандарт while (socket.read ()> 0) {// append байт} loop

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

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