Java NIO:大きなメッセージをすぐに送信すると、パケットが切り詰められ、データ損失が発生します。
-
20-08-2019 - |
質問
Java (NIO) サーバー (Linux を実行している) からクライアントに複数の大きなメッセージを立て続けに送信すると、パケットが切り捨てられるという厄介な問題があります。問題が発生するには、メッセージが大きく、非常に高速に送信される必要があります。基本的に私のコードが行っていることは次のとおりです (実際のコードではありませんが、多かれ少なかれ何が起こっているのかを示します)。
//-- setup stuff: --
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
String msg = "A very long message (let's say 20KB)...";
//-- inside loop to handle incoming connections: --
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.socket().setTcpNoDelay(true);
sc.socket().setSendBufferSize(1024*1024);
//-- later, actual sending of messages: --
for (int n=0; n<20; n++){
ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
sc.write(bb);
bb.rewind();
}
したがって、パケットが十分に長く、できるだけ早く送信される場合(つまり、このような遅延なしのループで)、反対側では、次のような結果が得られることがよくあります。
[COMPLETE PACKET 1]
[COMPLETE PACKET 2]
[COMPLETE PACKET 3]
[START OF PACKET 4][SOME OR ALL OF PACKET 5]
データ損失が発生し、パケットは一緒に実行され始め、パケット 5 の先頭 (この例では) がパケット 4 の先頭と同じメッセージで到着します。単に切り捨てるだけではなく、メッセージをまとめて実行します。
これは TCP バッファまたは「ウィンドウ サイズ」に関連しているか、あるいはここのサーバーが OS やネットワーク アダプターなどの処理能力よりも高速にデータを提供しているだけではないかと想像しています。しかし、それをどのように確認し、発生を防ぐことができるでしょうか?sc.write() の使用ごとのメッセージの長さを減らしても、繰り返しの回数を増やしても、同じ問題が発生します。単純に短時間のデータ量に問題があるようです。sc.write() が例外をスローしていることもわかりません (上記の例ではチェックしていないことはわかっていますが、テストではチェックしています)。
さらに多くのデータを受け入れる準備ができていないかどうかをプログラムで確認し、遅延を設定して、準備ができるまで待つことができれば幸いです。また、「sc.socket()。setsendbuffersize(1024*1024);」かどうかはわかりません。何らかの効果があります。または、これをLinux側に調整する必要がある場合。SocketChannel を本当に「フラッシュ」する方法はありますか?不十分な回避策として、たとえば 10KB を超えるメッセージを送信しようとするときはいつでも、バッファリングされているものを明示的に完全に送信するように試みることができます (私のアプリケーションではそれほど頻繁ではありません)。しかし、バッファの送信を強制する(または送信が完了するまで待つ)方法がわかりません。助けてくれてありがとう!
解決
。あなたは、戻り値および/またはバッファに残っているバイト数を確認する必要があります。
for (int n=0; n<20; n++){
ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
if(sc.write(bb) > 0 && bb.remaining() == 0) {
// all data sent
} else {
// could not send all data.
}
bb.rewind();
}
他のヒント
あなたはの戻り値をチェックしていません。
sc.write(bb);
これはあなたのバッファで利用可能なデータよりも少ないかもしれません書き込まれたバイト数を返します。あなたはおそらくただの左があるかどうかを確認するためにあなたのByteBuffer上)(残り呼び出すことができますどのようにNIO作品のため。
私は、任意のNIOのプログラミングを行っていないが、たSocketChannelが非ブロックモードである場合のJavadocによると、sc.writeは()(あなたのように)全体のByteBufferを書きませんし、ソケットの出力バッファがいっぱいですます。
あなたがこんなに早く書いているので、、あなたの接続を氾濫されており、ネットワークや受信機が追いつかない可能性が非常に高いです。
それはまだより多くのデータの準備ができていない場合、私はプログラム的にチェックすることができれば、私は幸せになると思います。
あなたは、出力バッファがいっぱいであるかどうかを調べるために)sc.writeの戻り値を(チェックする必要があります。
あなたはどのパケットで終わるどのようなデータを制御することは想定しないでください。
より、ここで:<のhref = "https://stackoverflow.com/questions/453609/whats-the-best-way-to-monitor-a-socket-for-new-data-and-then-process - つまり、データ/ 453951#453951" >その後、新しいデータやプロセスのためのソケットを監視するための最良の方法は何そのデータですか
ノンブロッキング モードを使用しています。sc.configureBlocking(間違い);
ブロックを次のように設定します 真実 コードはそのまま動作するはずです。送信数とループを確認するために他の人がここで行った提案も機能します。