4

TCP受信バッファに入力データが残っているNettyで接続を閉じるときに、TCP FINフラグではなくTCP RSTフラグが設定されないようにする方法があるかどうか疑問に思っています。

使用例は次のとおりです。

  • クライアント (C で記述) は、多くのフィールドを含むデータ パケットを送信します。
  • サーバーはパケットを読み取り、初期フィールドでエラーに遭遇し、例外をスローします。
  • 例外ハンドラーは例外をキャッチし、エラー メッセージを書き込み、write フューチャーに close on write コールバックを追加します。

問題は:

受信バッファにデータが残っていると、Linux (または Java..) は RST フラグで TCP パケットにフラグを立てます。これにより、クライアントがデータを読み取ろうとすると、ソケットが閉じられているために読み取りエラーが発生することが判明するため、クライアントはデータを読み取ることができなくなります。

ストレート Java ソケットの場合、解決策は、閉じる前に socket.shutdownOutput() を呼び出すことだと思います。Netty またはこれを回避する方法に同等の機能はありますか?

単にソケットからの読み取りを続行すると、RST を回避するには不十分な場合があります。これは、close が呼び出されたときにバッファーにデータがある場合とない場合があるためです。

参考:http ://cs.baylor.edu/~donahoo/practical/CSockets/TCPRST.pdf

アップデート:

問題の別の参照と説明: http://docs.oracle.com/javase/1.5.0/docs/guide/net/articles/connection_release.html

shutdownOutput() を呼び出すと、(FIN を送信することにより) 接続をより整然と閉じるのに役立ちますが、クライアントがまだデータを送信している場合は、関係なく RST メッセージが送信されます (EJP からの回答を参照してください。shutdownOutput() に相当するものを利用できる場合があります)。 Netty 4+で。

解決策は、クライアントからすべてのデータを読み取ることです (ただし、特に悪意のあるクライアントの場合、クライアントが送信を完全に停止する時期を確認することはできません)、または応答を送信した後、接続を閉じる前に単に待機することです (回答を参照)評判の悪いから)。

4

2 に答える 2

1

SocketChannel私は専門家ではありませんが、Netty から基盤を入手できる場合は、電話することができます。channel.socket().shutdownOutput().

受信バッファにデータが残っていると、Linux (または Java..) は RST フラグで TCP パケットにフラグを立てます。これにより、クライアントがデータを読み取ろうとすると、ソケットが閉じられているために読み取りエラーが発生することが判明するため、クライアントはデータを読み取ることができなくなります。

私はこれを理解していません。TCP は、クライアントが FIN を取得する前に、ソケット受信バッファー内のすべてのデータを受信することを保証します。サーバーのソケット受信バッファーについて話している場合、それは close() によって破棄され、クライアントがさらに送信を試みると RST が取得され、それがIOException:接続リセットになります。これは、関連付ける接続がないためです。したがって、それを置く場所はありません。注: Java ではなく、これらすべてを行うのは TCP です。

しかし、それが悪い場合は、チャンネルを閉じる前にリクエスト全体を読む必要があるようです。

リクエスト全体を保持するのに十分な大きさになるように、ソケットの受信バッファを増やしてみることもできます。これにより、接続を閉じたいときにクライアントがまだ送信されていないことが保証されます。編集:リクエストがメガバイトであるため、これは機能しません。

于 2012-11-01T00:31:10.987 に答える
0

これを試すことができますか: サーバーがエラー メッセージを書き込んだ後、500 ミリ秒待ってから close()。クライアントがエラー メッセージを受信できるかどうかを確認します。

TCP 遅延確認応答が原因で、サーバー受信バッファ内のパケットが ACK されていないと推測しています。close() が呼び出された場合、これらのパケットに対する適切な応答は RST です。ただし、shutdownOutput() が呼び出された場合は、適切なクローズ プロセスです。パケットは最初に ACK されます。


編集:問題についてさらに学習した後の別の試み:

アプリケーション プロトコルは、クライアント リクエストがまだストリーミングされている間でも、サーバーはいつでも応答できるということです。したがって、クライアントは、ブロッキングモードを想定して、サーバーから読み取る別のスレッドを持つ必要があります。クライアントがサーバーからの応答を読み取るとすぐに、サーバーへのそれ以上の書き込みを停止するために、書き込みスレッドに割り込む必要があります。これは、単純にソケットを close() することで実行できます。

サーバー側では、すべての要求データが読み取られる前に応答が書き込まれ、後で close() が呼び出された場合、ほとんどの場合、RST がクライアントに送信されます。どうやらほとんどの TCP スタックは、受信バッファが空でないときに close() が呼び出された場合、RST を相手側に送信します。TCP スタックがそれを行わない場合でも、close() の直後により多くのデータが到着し、いずれにせよ RST がトリガーされます。

その場合、クライアントはサーバーの応答を読み取れない可能性が非常に高いため、問題が発生します。

したがって、サーバーは応答後すぐに close() できないため、クライアントが応答を受信するまで待機する必要があります。サーバーはどのようにそれを知っていますか?

まず、クライアントは完全な応答を受信したことをどのように知るのでしょうか? つまり、応答はどのように終了しますか? 応答が TCP FIN によって終了した場合、サーバーは、shutdownOutput() を呼び出して、応答後に FIN を送信する必要があります。応答が HTTP Content-Length ヘッダーなどによって自己終了する場合、サーバーは shutdownOutput() を呼び出す必要はありません。

クライアントは、プロトコルごとに完全な応答を受信した後、サーバーへのデータの送信を直ちに中止する必要があります。これは、接続を大雑把に切断することによって行われます。プロトコルは、よりエレガントな方法を設計しませんでした。FIN か RST のどちらでもかまいません。

したがって、サーバーは、応答を書き込んだ後、EOF またはエラーになるまでクライアントからの読み取りを続ける必要があります。次に、ソケットを close() できます。

ただし、悪意のある/壊れたクライアントとネットワークの問題を考慮して、このステップにはタイムアウトが必要です。ほとんどの場合、手順を完了するには数秒で十分です。

また、サーバーはフリーではないため、クライアントからの読み取りを望まない場合があります。サーバーは単にタイムアウトを過ぎてから、close() を待つことができます。

于 2012-10-31T23:26:51.737 に答える