2

私は最近、ノンブロッキングソケットを備えたJava NIOベースのサーバーを作成していて、データの書き込みに関していくつかの問題にぶつかりました。ノンブロッキング書き込みがByteBufferの一部またはすべてのバイトの書き込みに失敗する状況があることは、今ではわかっています。

私が現在このようなシナリオを処理する方法は、バッファーを巻き戻すか圧縮して、後で次の選択の反復で再度送信しようとすることです。ただし、これによりパフォーマンスが大幅に低下するため、データを高速に送信することが不可欠です。

私は次のようなものを使用しようとしました:

ByteBuffer bb = ...;
SocketChannel sc = ...;
while(bb.remaining() > 0) { 
 sc.write(bb); 
} 

ただし、これに伴う問題は、0バイトを書き込んでも、whileループを終了する可能性があるという事実です。理由はわかりませんが、write()のようです。メソッドは、実際にすべてのバイトを送信したかどうかに関係なく、ByteBufferの制限に達します。

この書き込み方法で私が抱えていたもう1つの問題は、負荷が高い場合に、ブロッキング書き込みを試みていなくても、バッファオーバーフロー例外が発生することがあることです。

ブロッキング書き込みを適切に実行する方法と、SocketChannel.write(ByteBuffer)がバッファをオーバーフローさせる可能性のある条件についてのアドバイスがどうしても必要です(制限に達したときに停止しないでください)。

前もって感謝します。

編集:sc.write(bb)が0バイトを書き込んだとしても、バッファ内の位置をbb.limit()に設定する理由はまだわかりません。私の唯一の手段は、書き込みの試行に失敗した後、バッファを巻き戻すことです。

4

4 に答える 4

2

非ブロッキング IO を使用する場合は、通常、低レイテンシまたは高スループットのいずれかを求めます。

圧縮してバッファー内のデータを移動しないでください。必要な長さのバッファーを割り当ててください (通常、1 つのメッセージに収まるように、メッセージ ヘッダーを個別に)。そして、内容を完全にソケットに書き込んだ後、それらを破棄します。GC を受け入れることができない場合は、サイズが 2 倍に増加するバッファーのプールを使用できます。

スループットが主な関心事である場合は、ソケットに直接書き込みを試みるのではなく、セレクターでソケットの書き込み可能性を登録し、ソケットが書き込み可能なときに書き込みを試みてください。これを実現するには、送信待ちのバッファのキューを維持する必要があります。このキューが空になるまで、ソケットの書き込み可能性を登録したままにします。この場合、アプリケーション内のシステムコールの数を最小限に抑えるため、スキャッター/ギャザータイプの IOを使用することをお勧めします。

write requester thread:
  ioloop.submit(buffer[]{msgheaderbuf, msgbodybuf}, sock);
selector thread (ioloop):
  submit(buffer[] bufs, socket sock):
    queue.enqueue(bufs);
    selector.register(sock, WRITABLE);
    selector.wakeup();

待ち時間が重要な場合は、まずソケットに直接書き込みを試み、書き込みが失敗した場合にのみ、データをキューに入れ、上記で説明したようにソケットの書き込み可能性を登録します。これには、(書き込み可能イベントのトリガーの結果として) ソケットが直接およびセレクター スレッド内から書き込まれるのを防ぐために、追加のミューテックスが必要になります。

write requester thread:
  ioloop.submit(buffer[]{msgheaderbuf, msgbodybuf}, sock);
selector thread (ioloop):
  submit(buffer[] bufs, socket sock):
    size = sock.write(bufs);
    while (!bufs.empty() && !bufs[0].remaining()): bufs.pop_front();
    if (bufs.empty()) return;

    queue.enqueue(bufs);
    selector.register(sock, WRITABLE);
    selector.wakeup();
于 2013-01-10T08:13:07.653 に答える
0

これは、バッファの書き込み方法に関係しています。この問題は、マルチスレッドの同時実行構成でバッファを書き込んでいる場合によく発生します。

あなたが何かをするなら

ByteBuffer bb = HashMap.get(ByteBufferForXParameter);

100 バイトのソース ByteBuffer があり、スレッド 1 が (同じソースから) bb を書き込んでおり、スレッド 2 も別のバッファー bb1 (ただし同じソース バッファーから) を書き込もうとしているとします。これらは位置と競合する可能性があるため、thread1ループを 1 回繰り返し、0 ~ 10 バイトを書き込みました。スレッド 2 が書き込みを開始し、10 ~ 20 バイトなどから書き込む可能性があります。

バッファー サイズが小さい場合、スレッド 2 が書き込みを開始する前に、バッファー全体がスレッド 1 によって書き込まれる可能性があります。したがって、書き込みを試みると、bb1.hasRemaining() が false として取得され、ループから抜け出します。

于 2016-07-17T13:35:56.020 に答える
-1

write(bb)は、出力ソケットバッファにスペースがない場合に0を返します。そのバッファが解放されるまで待つ必要があります。最初の考えは作ることThread.sleep(little time)ですが、これはブロッキングソケットを使用するよりも明らかに悪いです。

ブロッキングソケットを使用できない場合は、プログラムをリファクタリングして、非同期部分で構成されるようにする必要があります。1つのメソッドは書き込みを開始するだけで、ソケット出力バッファーに空きスペースがあり、より多くのデータを書き込むことができる場合は、他のメソッドが呼び出されます。 、そしてすべてのバッファデータが送信されたら、別のバッファなどを書き込むメソッドを呼び出す必要があります。

Javaは、書き込み(および読み取り)が可能であるときに通知を受け取るアプローチを提供します- (nio java.nio.channels.Selector1)およびasynchronous channels(nio2、Java 7以降でのみ使用可能)。非同期プログラミングは、スレッドの操作と同期も必要とするため複雑です。したがって、非同期サーバーを最初から作成することは、初心者の作業ではありません。開始する準備ができているライブラリを選択します。例は次のとおりです。Netty-多くの機能を備えた複雑なもの。df4j-非同期計算用のライブラリ。例としてnio1およびnio2へのインターフェイスがあります(私が開発しました)。

于 2013-01-10T07:43:08.810 に答える
-1

私たちも同じ問題に直面しています。私たちの場合、クライアントが遅いように見え、バッファがいっぱいになり、SocketChannel.write は最終的に EAGAIN になります。

次は前述のwhileループのせいで、ビジーウェイトに入り、EAGAINを返し続け、その副作用としてCPUに高負荷がかかります。

Thread.sleep は一時的な回避策であり、この状況を処理するためにコード全体をリファクタリングする必要があります。

于 2016-03-03T16:58:42.707 に答える