Java InputStream блокирует чтение - PullRequest
50 голосов
/ 04 марта 2009

Согласно Java API, InputStream.read() описывается как:

Если байт недоступен, потому что конец потока достигнут, значение -1 возвращается. Этот метод блокирует, пока не будут доступны входные данные, конец потока обнаружен, или исключение.

У меня есть цикл while(true), выполняющий чтение, и я всегда получаю -1, когда по потоку ничего не отправляется. Это ожидается.

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

Или read () блокируется, только если есть другой поток, обращающийся к потоку, и ваш read () не может получить доступ к потоку?


Что приводит меня к следующему вопросу. Раньше у меня был прослушиватель событий (предоставленный моей библиотекой), который уведомлял меня, когда данные были доступны. Когда меня уведомили, я позвонил бы while((aByte = read()) > -1) сохранить байт. Я был озадачен, когда получил два события в непосредственной близости, и не все мои данные отображались. Казалось, что будет отображаться только хвостовая часть данных второго события, а остальные отсутствуют.

В конце концов я изменил свой код, чтобы при получении события я вызывал if(inputStream.available() > 0) while((aByte = read()) > -1) для сохранения байта. Теперь все заработало и все мои данные были отображены.

Может кто-нибудь объяснить это поведение? Говорят, что InputStream.available() возвращает количество байтов, которые вы можете прочитать перед блокировкой следующего вызывающего (потока?). Даже если я не использую .available (), я ожидаю, что чтение первого события просто блокирует чтение второго события, но не стирает или не использует слишком много данных потока. Почему при этом не отображаются все мои данные?

Ответы [ 7 ]

44 голосов
/ 04 марта 2009

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

Например, InputStream из сокета Socket будет блокировать, а не возвращать EOF, до тех пор, пока не будет получен пакет TCP с установленным флагом FIN. Когда EOF получен из такого потока, вы можете быть уверены, что все данные, отправленные в этот сокет, были надежно получены, и вы не сможете больше читать данные. (Если блокирующее чтение приводит к исключению, с другой стороны, некоторые данные могут быть потеряны.)

Другие потоки, например потоки из необработанного файла или последовательного порта, могут не иметь аналогичного формата или протокола, чтобы указывать, что больше нет данных. Такие потоки могут немедленно возвращать EOF (-1), а не блокировать, когда в настоящее время нет доступных данных. Однако в отсутствие такого формата или протокола вы не можете быть уверены, когда другая сторона закончит отправку данных.


Что касается вашего второго вопроса, похоже, что вы, возможно, имели состояние гонки. Не видя рассматриваемого кода, я предполагаю, что проблема на самом деле заключается в вашем методе «отображения». Возможно, попытка отображения во втором уведомлении каким-то образом затрудняет работу, выполненную во время первого уведомления.

17 голосов
/ 04 марта 2009

Возвращает -1, если это конец потока. Если поток все еще открыт (то есть сокетное соединение), но данные не достигли стороны чтения (сервер работает медленно, сети работают медленно ...) блоки read ().

Вам не нужен доступный вызов (). Мне трудно понять ваш дизайн уведомлений, но вам не нужны никакие вызовы, кроме самого read (). Метод available () доступен только для удобства.

13 голосов
/ 04 марта 2009

ОК, это немного беспорядочно, поэтому давайте разберемся с этим первым: InputStream.read() блокировка не имеет ничего общего с многопоточностью. Если у вас есть несколько потоков, читающих из одного и того же входного потока, и вы запускаете два события очень близко друг к другу - где каждый поток пытается использовать событие, то вы получите повреждение: первый поток для чтения получит несколько байтов (возможно, все байты), и когда второй поток будет запланирован, он будет читать остальные байты. Если вы планируете использовать один поток ввода-вывода в более чем одном потоке, всегда synchronized() {} при некоторых внешних ограничениях.

Во-вторых, если вы можете читать с вашего InputStream до тех пор, пока не получите -1, а затем подождать и снова сможете читать позже, то используемая вами реализация InputStream не работает! Контракт на InputStream четко гласит, что InputStream.read() должен возвращать -1 только тогда, когда больше нет данных для чтения, потому что достигнут конец всего потока, и больше данных никогда не будет доступно - как при чтении из файл, и вы дошли до конца.

Поведение для «больше нет доступных данных, пожалуйста, подождите, и вы получите больше» для read(), чтобы блокировать и не возвращать, пока не будут доступны некоторые данные (или возникнет исключение).

6 голосов
/ 23 апреля 2012

По умолчанию поведение предоставленного RXTX InputStream не соответствует.

Вы должны установить порог приема на 1 и отключить тайм-аут приема:

serialPort.enableReceiveThreshold(1);
serialPort.disableReceiveTimeout();

Источник: Последовательное соединение RXTX - проблема с блокировкой чтения ()

4 голосов
/ 01 сентября 2009

Да! Не сдавайся в своем эфире пока Jbu. Мы говорим о последовательной связи здесь. Для последовательных устройств абсолютно ожидаемо, что -1 может / будет возвращен при чтении, но все же ожидается получение данных в более позднее время. Проблема в том, что большинство людей привыкли иметь дело с TCP / IP, который всегда должен возвращать 0, если только TCP / IP не отключен ... тогда да, -1 имеет смысл. Однако в Serial отсутствует поток данных в течение длительных периодов времени, а также «HTTP Keep Alive», или пульс TCP / IP, или (в большинстве случаев) нет аппаратного управления потоком. Но связь является физической, и все еще связана с "медью" и все еще отлично жива.

Теперь, если то, что они говорят, правильно, то есть: Serial должен быть закрыт на -1, тогда почему мы должны следить за такими вещами, как OnCTS, pmCarroerDetect, onDSR, onRingIndicator и т. Д ... Черт возьми, если 0 означает, что он есть, а -1 означает, что его нет, тогда закрутите все эти функции обнаружения! : -)

Проблема, с которой вы можете столкнуться, может быть в другом месте.

Теперь перейдем к конкретике:

В: «Казалось, что будет отображаться только хвостовая часть данных второго события, а остальные отсутствуют».

A: Я собираюсь догадаться, что вы были в цикле, повторно используя один и тот же буфер byte []. 1-е сообщение поступает, еще не отображается на экране / log / std out (потому что вы находитесь в цикле), затем вы читаете 2-е сообщение, заменяя данные 1-го сообщения в буфере. Опять же, потому что я собираюсь угадать, что вы не сохраняете то, сколько вы читаете, а затем убедитесь, что вы сместили буфер хранилища на предыдущую сумму чтения.

В: «Я в конечном итоге изменил свой код, чтобы при получении события я вызывал if (inputStream.available ()> 0), а ((aByte = read ())> -1) сохранял байт ".

A: Браво ... там все хорошо. Теперь, ваш буфер данных находится внутри оператора IF, ваше 2-е сообщение не будет загромождать ваше 1-е ... ну, на самом деле, это было, вероятно, только одно большое (er) сообщение на 1-м месте. Но теперь вы прочтете все за один раз, сохраняя данные нетронутыми.

C: "... состояние гонки ..."

A: Аааа, хорошо, поймай всех козлов! Состояние гонки ... :-) Да, возможно, это было условие гонки, на самом деле, возможно, оно и было. Но это также может быть способ, которым RXTX очищает флаг. Снятие «флага доступности данных» может произойти не так быстро, как ожидается. Например, кто-нибудь знает разницу между read VS readLine в отношении очистки буфера, в котором ранее были сохранены данные, и повторной установки флага события? Я тоже. :-) И пока я не могу найти ответ ... но ... позвольте мне рассказать еще несколько предложений. Программирование на основе событий все еще имеет некоторые недостатки. Позвольте привести пример из реальной жизни, с которым мне пришлось столкнуться недавно.

  • Я получил некоторые данные TCP / IP, скажем, 20 байтов.
  • Итак, я получаю OnEvent для полученных данных.
  • Я начинаю чтение даже с 20 байтов.
  • Прежде чем я закончу читать мои 20 байтов ... я получаю еще 10 байтов.
  • TCP / IP, однако, выглядит так, чтобы уведомить меня, о, видит, что флаг все еще установлен, и больше не будет уведомлять меня.
  • Тем не менее, я заканчиваю читать свои 20 байтов (доступно () сказал, что было 20) ...
  • ... и последние 10 байтов остаются в TCP / IP Q ... потому что я не был уведомлен о них.

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

Полная противоположность тому, что происходит сейчас с вами.

Итак, перейдите к IF IF () ... прочитайте возвращенную длину данных. Затем, если вы параноик, установите таймер и снова вызовите available (), если там все еще есть данные, тогда выполните чтение без новых данных. Если available () возвращает 0 (или -1), то расслабьтесь ... откиньтесь на спинку ... и дождитесь следующего уведомления OnEvent.

2 голосов
/ 05 мая 2015

InputStream - это просто абстрактный класс , к сожалению, реализация решает, что произойдет.

Что происходит, если ничего не найдено:

  • Сокеты (т.е. SocketInputStream) будут блокироваться до получения данных (по умолчанию). Но можно установить таймаут (см .: setSoTimeout), тогда read будет блокироваться в течение x мс. Если все еще ничего не получено, тогда будет брошено SocketTimeoutException.

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

  • Serialio связь всегда вернется -1; Вы также можете установить время ожидания (используйте setTimeoutRx), read будет сначала блокироваться в течение x мс, но результат все равно будет -1, если ничего не найдено. (Примечание: но доступно несколько последовательных классов io, поведение может зависеть от поставщика.)

  • Файлы (читатели или потоки) приведут к EOFException.

Обработка общего решения:

  • Если вы поместите любой из вышеперечисленных потоков в DataInputStream, вы можете использовать такие методы, как readByte, readChar и т. Д. Все -1 значения преобразуются в EOFException. (PS: если вы выполняете много небольших операций чтения, то лучше сначала обернуть их в BufferedInputStream)
  • Оба SocketTimeoutException и EOFException расширяются IOException, и есть несколько других возможных IOException. Удобно просто проверить IOException, чтобы обнаружить проблемы со связью.

Другая чувствительная тема - это сброс. flush в терминах сокетов означает «отправить его сейчас», но в терминах Serialio это означает «сбросить буфер».

0 голосов
/ 23 ноября 2010

Я думаю, вы можете получить весь поток данных, если используете thread.sleep ()

...