web-dev-qa-db-ja.com

Java InputStreamブロッキング読み取り

Java api)によれば、InputStream.read()は次のように記述されます:

ストリームの終わりに達したために使用可能なバイトがない場合、値-1が返されます。このメソッドは、入力データが利用可能になるか、ストリームの終わりが検出されるか、例外がスローされるまでブロックします。

読み取りを行うwhile(true)ループがあり、ストリームに何も送信されない場合は常に-1を取得します。予想通りです。

私の質問は、いつread()がブロックされるのですか?データを取得できない場合は、-1を返します。データが受信されるまでブロック読み取りが待機することを期待します。入力ストリームの最後に達した場合、read()は-1を返すのではなく、単にデータを待つべきではありませんか?

または、ストリームにアクセスする別のスレッドがあり、read()がストリームにアクセスできない場合、read()はブロックするだけですか?


それは私の次の質問に私を導きます。以前は、データが利用可能になったときに通知するイベントリスナー(ライブラリが提供)を使用していました。通知を受けたときに、while((aByte = read()) > -1)を呼び出して、バイトを保存します。非常に近い時間に2つのイベントが発生し、すべてのデータが表示されていなかったときに困惑しました。 2番目のイベントのデータの末尾のみが表示され、残りは欠落しているように見えました。

最終的にコードを変更して、イベントを取得したときにif(inputStream.available() > 0) while((aByte = read()) > -1)という名前でバイトを保存するようにしました。これで正常に機能し、すべてのデータが表示されました。

誰かがこの動作を説明できますか? InputStream.available()は、(ストリームの?)次の呼び出し元をブロックする前に読み取ることができるバイト数を返すと言われています。 .available()を使用しない場合でも、最初のイベントの読み取りは2番目のイベントの読み取りをブロックするだけで、消去または大量のストリームデータを消費しないことを期待します。これを行うと、すべてのデータが表示されないのはなぜですか?

51
jbu

InputStreamの一部の実装の基になるデータソースは、ストリームの最後に到達したことを通知でき、これ以上データは送信されません。この信号が受信されるまで、そのようなストリームの読み取り操作はブロックされる可能性があります。

たとえば、InputStreamソケットからのSocketは、TCP FINフラグが設定されたパケットが受信されるまで、EOFを返すのではなくブロックします。 EOFはそのようなストリームから受信され、そのソケットで送信されたすべてのデータが確実に受信されたことを保証でき、これ以上データを読み取ることができなくなります。一方、例外が発生し、一部のデータが失われた可能性があります。)

生のファイルやシリアルポートからのストリームなど、他のストリームには、これ以上データが利用できないことを示す同様の形式やプロトコルがない場合があります。そのようなストリームは、現在利用可能なデータがないときにブロックするのではなく、すぐにEOF(-1)を返すことができます。ただし、そのような形式またはプロトコルがない場合、他の側はデータの送信を完了しました。


2番目の質問については、競合状態にあった可能性があります。問題のコードを見ることなく、問題は実際に「表示」の方法にあると推測しています。おそらく、2番目の通知で表示しようとしたのは、最初の通知中に行われた作業を何らかの形で覆すことでした。

45
erickson

ストリームの終わりであれば-1を返します。ストリームがまだ開いている(つまり、ソケット接続)が、読み取り側にデータが届いていない場合(サーバーが遅い、ネットワークが遅い、など)、read()ブロックします。

Call()を呼び出す必要はありません。通知の設計を理解するのは大変ですが、read()以外の呼び出しは必要ありません。メソッドavailable()は便宜のためだけにあります。

17

OK、これは少し混乱しているので、最初にこれをクリアします:InputStream.read()ブロッキングはマルチスレッドとは関係ありません。同じ入力ストリームから複数のスレッドを読み取り、2つのイベントを互いに非常に近い場所でトリガーする場合-各スレッドがイベントを消費しようとすると、破損が発生します:読み取る最初のスレッドはいくつかのバイト(おそらくすべてバイト)、2番目のスレッドがスケジュールされると、残りのバイトを読み取ります。複数のスレッドで単一のIOストリーム)を使用する場合は、何らかの外部制約で常にsynchronized() {}を使用します。

第二に、-1を取得するまでInputStreamから読み取り、待機して後で読み取りできる場合、使用しているInputStream実装は壊れています。 InputStreamのコントラクトでは、InputStream.read()は、ストリーム全体の終わりに達し、これ以上データが届かないため、読み取るデータがなくなった場合にのみ-1を返すことを明確に規定しています。利用可能-ファイルから読み取り、最後に到達したときのように。

「これ以上データが利用できません。しばらくお待ちください」という振る舞いは、read()がブロックし、利用可能なデータがあるまで(または例外がスローされるまで)戻りません。

13
Guss

デフォルトでは、提供されたRXTX InputStreamの動作は準拠していません。

受信しきい値を1に設定し、受信タイムアウトを無効にする必要があります。

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

ソース: RXTXシリアル接続-read()のブロックに関する問題

6
Robert

そう!まだJbuのストリームをgiveめないでください。ここでシリアル通信について話します。シリアルの場合、読み取り時に-1が返される可能性がありますが、それでも後でデータが返されることが予想されます。問題は、ほとんどの人がTCP/IPの処理に慣れていることです。TCP/ IPが切断されない限り、常に0を返す必要があります。ただし、シリアルでは、長期間のデータフローはなく、「HTTPキープアライブ」、TCP/IPハートビート、または(ほとんどの場合)ハードウェアフロー制御はありません。しかし、リンクは物理的であり、依然として「銅」で接続されており、完全に有効です。

今、彼らが言っていることが正しい場合、すなわち、シリアルで-1を閉じる必要がある場合、なぜOnCTS、pmCarroerDetect、onDSR、onRingIndicatorなどのようなものを監視する必要があるのでしょうか? 、および-1はそうではないことを意味し、これらすべての検出機能をねじ込みます! :-)

あなたが直面しているかもしれない問題は他の場所にあるかもしれません。

次に、詳細について:

Q:「2番目のイベントのデータの末尾のみが表示され、残りは欠落しているように見えました。」

A:同じbyte []バッファーを再利用して、ループに陥っていると推測します。最初のメッセージが入り、まだ画面/ログ/標準出力に表示されていないため(ループにいるため)、2番目のメッセージを読んで、バッファー内の1番目のメッセージデータを置き換えます。繰り返しますが、私はあなたがどれだけ読んだかを保存しないと推測するので、前の読み込み量だけストアバッファをオフセットするようにしました。

Q:「イベントを取得したときにif(inputStream.available()> 0)while((aByte = read())> -1)バイトを保存するようにコードを最終的に変更しました。」

A:ブラボー...それは良いことです。これで、データバッファーはIFステートメント内にあり、2番目のメッセージは1番目のメッセージを上書きしません...まあ、実際には、おそらく1番目の大きな(より)メッセージでした。しかし、今では、データをそのまま保持しながら、すべてを一度に読み取ることができます。

C:「...競合状態...」

A:ああ、いいやつはすべての山羊を捕まえる!競合状態... :-)はい、これは競合状態であったかもしれません。しかし、RXTXがフラグをクリアする方法でもあります。 「データ利用可能フラグ」のクリアは、期待するほど迅速に行われない場合があります。たとえば、データが以前に保存されたバッファのクリアとイベントフラグの再設定に関連して、readとreadLineの違いを知っている人はいますか?私もそうではありません。:-)また、答えを見つけることもできません...しかし...さらにいくつかの文章を探しましょう。イベント駆動型プログラミングにはまだいくつかの欠陥があります。最近対処しなければならなかった現実世界の例を挙げましょう。

  • 20バイトのTCP/IPデータを取得しました。
  • したがって、受信データのOnEventを受け取ります。
  • 20バイトでも「読み取り」を開始します。
  • 20バイトを読み終える前に、さらに10バイトを取得します。
  • しかし、TCP/IPは私に通知するように見えます、ああ、フラグがまだ設定されていることを確認し、再度私に通知しません。
  • しかし、私は20バイトの読み取りを終了しました(available()は20があったと言いました)...
  • ...そして、最後の10バイトはTCP/IP Qに残ります...私はそれらの通知を受けなかったためです。

フラグがまだ設定されているため、バイトを読み始めたにもかかわらず、通知が見逃されました。バイトを終了した場合、フラグはクリアされ、次の10バイトの通知を受信することになります。

今あなたのために起こっていることの正反対。

そうです、IF available()を使用して...返されたデータの長さの読み取りを行います。次に、あなたが妄想している場合は、タイマーを設定し、available()を再度呼び出します。そこにまだデータがある場合は、新しいデータを読み取らないでください。 available()が0(または-1)を返す場合、リラックスして...座って...次のOnEvent通知を待ちます。

4
Adrian

InputStreamは単なる抽象クラスです、残念ながら実装は何が起こるかを決定します。

何も見つからない場合はどうなりますか?

  • ソケット(つまり、SocketInputStream)は、データが受信されるまでブロックされます(デフォルト)。ただし、タイムアウトを設定することは可能です(setSoTimeoutを参照)、readはxミリ秒間ブロックされます。それでも何も受信されない場合、SocketTimeoutExceptionがスローされます。

    しかし、タイムアウトの有無にかかわらず、SocketInputStreamからの読み取り時々-1例えば、複数のクライアントが同時に同じHost:port、デバイスが接続されているように見えても、readの結果はすぐに-1(データを返さない)。

  • Serialio通信は常に-1;タイムアウトを設定することもできます(setTimeoutRxを使用)、readは最初にxミリ秒間ブロックしますが、結果は依然として-1何も見つからない場合。 (注意:ただし、複数のシリアルioクラスが利用可能であり、動作はベンダーに依存する可能性があります。)

  • ファイル(リーダーまたはストリーム)はEOFExceptionになります。

汎用ソリューションの作成:

  • 上記のストリームのいずれかをDataInputStreamでラップすると、readBytereadCharなどのメソッドを使用できます。 - すべて -1値はEOFExceptionに変換されます。(PS:多数の小さな読み取りを実行する場合は、BufferedInputStream first)
  • SocketTimeoutExceptionEOFExceptionは両方ともIOExceptionを拡張し、他にもいくつかの可能なIOExceptionがあります。 通信の問題を検出するためにIOExceptionをチェックするのが便利です。

別のデリケートなトピックはフラッシュです。ソケットに関してflushは「すぐに送信」を意味しますが、Serialioに関しては「バッファを破棄」を意味します。

2
bvdb