web-dev-qa-db-ja.com

ランダム「既存の接続がリモートホストによって強制的に閉じられました。」 TCPリセット後

クライアントとサーバーの2つの部分があります。そして、クライアントからサーバーにデータ(サイズ> 5840バイト)を送信しようとすると、サーバーはデータを送り返します。私はこれを何回もループし、その間に毎秒待機します。サーバーアプリケーションがクラッシュするとき、クラッシュは非常にランダムなエラーのようです:

未処理の例外:System.IO.IOException:トランスポート接続からデータを読み取ることができません:既存の接続がリモートホストによって強制的に閉じられました。 --->

System.Net.Sockets.SocketException:既存の接続がリモートホストによって強制的に閉じられました

system.Net.Sockets.Socket.Receive(Byte []バッファー、Int32オフセット、Int32サイズ、SocketFlags socketFlags)

system.Net.Sockets.NetworkStream.Read(Byte [] buffer、Int32 offset、Int32 s ize)で

---内部例外スタックトレースの終了---

system.Net.Sockets.NetworkStream.Read(Byte [] buffer、Int32 offset、Int32 s ize)で

tCP_Server.Program.Main(String [] args)

クライアントコード(これはループ内にあります):

            try
            {
                Int32 port = 13777;
                using (TcpClient client = new TcpClient(ip, port))
                using (NetworkStream stream = client.GetStream())
                {
                    client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
                    var data = GenerateData(size);

                    sw.Start();
                    // Send the message to the connected TcpServer. 
                    stream.Write(data, 0, data.Length);

                    // Buffer to store the response bytes.
                    data = new Byte[size];

                    // Read the first batch of the TcpServer response bytes.
                    Int32 bytes = stream.Read(data, 0, data.Length);

                    sw.Stop();
                    Console.WriteLine(i + ": Done transporting " + size + " bytes to and from " + ip + " time: " +
                                      sw.ElapsedMilliseconds + " ms");
                    // Close everything.
                    stream.Close();
                    client.Close();
                }

            }
            catch (ArgumentNullException e)
            {
                Console.WriteLine("ArgumentNullException: {0}", e);
            }
            catch (SocketException e)
            {
                Console.WriteLine("SocketException: {0}", e);
            }

            sw.Reset();

サーバーコード:

            Byte[] bytes = new Byte[size];

            // Enter the listening loop. 
            for (int i = 0; i < numberOfPackages; i++)
            {
                using (TcpClient client = server.AcceptTcpClient())
                using (NetworkStream stream = client.GetStream())
                {
                    client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
                    // Loop to receive all the data sent by the client. 
                    while ((stream.Read(bytes, 0, bytes.Length)) != 0)
                    {
                        // Send back a response.
                        stream.Write(bytes, 0, size);

                    }
                    client.GetStream().Close();
                    client.Close();
                }


                Console.WriteLine("Receive data size " + size);

            }

Wiresharkを使用して、送信されたtcpパッケージを監視しましたが、プログラムがクラッシュする前にTCP RSTがクライアントからサーバーに送信されていることを発見しました。クライアントとホストの間にファイアウォールがないため、問題はありません。

クライアントとサーバーの両方のwiresharkファイルはこちらです: https://www.dropbox.com/sh/ctl2chq3y2c20n7/AACgIJ8IRiclqnyOyw8sqd9La?dl=

だから、TCP RSTを取り除くか、サーバーが何らかの方法でそれを処理してクラッシュしないようにする必要があります。

長い待ち時間を使用しようとしましたが、役に立ちません。データが5840バイト未満の場合、私が知っているように、クラッシュは発生しません。

提案やアイデアはありますか?

編集:次の変更で動作するようになった答えのおかげで:

サーバ:

// Loop to receive all the data sent by the client.
int k = 0;
while (k < size)
{
   int bytesRead = stream.Read(bytes, 0, bytes.Length);
   k += bytesRead;
}
// Send back a response.
stream.Write(bytes, 0, size);

クライアント側で受信する場合も同じです。最初にすべてのデータを送信してからサーバーに応答させたいので、これは私のアプリケーションで機能します。

11
TobiasW

私が見る2つの問題:

  1. sizeバイトの読み取りは実際にsizeバイトを読み取ると想定しています。ありません。 TCPはストリーミングプロトコルです。読み取りは少なくとも1バイトを読み取ります。これが唯一の保証です。
  2. コードはランダムにデッドロックします。クライアントはデータをサーバーに書き込みます。サーバーはそれをエコーバックします。しかし、クライアントはすべてを書き込むまで読み取りません。ネットワークバッファが取得できる以上の書き込みを行うと、デッドロックになります。クライアントは同時に読み取りと書き込みを行う必要があります。おそらく、データを読み戻すために別のスレッド/タスクが必要です。この問題を解決する良いパターンは、1つのライタータスク、1つのリーダータスクを開始し、Task.WhenAll/WaitAllを開始してそれらを結合することです。

どのような状況下でTCPスタックがRSTを送信するかわかりません。デッドロックが原因でタイムアウトが発生する可能性があります。

例外を飲み込んでいないのですか?

通常、接続を閉じると、バックグラウンドで正常なシャットダウンが実行されます。しかし、この時点で反対側がまだ書いているときに何が起こるかはわかりません。たぶん答えは、RSTが書き込みのレシーバーによって生成されることです。確かに、TCP仕様はこの質問に答えます。 この答え が信頼される場合、実際にShutdown(Read)/ Closeに続いて着信書き込みがRSTになります。接続。

両方の問題を修正し、調査結果を報告してください。

8
usr

IISルートエントリのキャッシュ制限を増やして、同じ問題を解決しました。キャッシュを無効にすることもできます。それが役に立てば幸い。

0
user2504655

次のようなHttpClientのランダムエラーを使用すると、この問題が発生しました。

System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
 ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote Host.
 ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote Host

または

 ---> System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send.

TCPスタック内の明示的輻輳通知、またはECNの問題。これはECN機能とも呼ばれます。明示的輻輳通知は、インターネットプロトコルおよび伝送制御プロトコルの拡張であり、定義されていますRFC 3168では、ECNはパケットを落とさずにネットワーク輻輳のエンドツーエンド通知を許可します。ECN機能が有効になっている場合は、無効にできます。

netsh interface tcp set global ecncapability=disabled

再起動は必要ありません。

0
Daniil Sokolyuk

私はエキスパートではありませんが、クライアントがstream.Close()を呼び出すと、サーバーがまだ書き込みを試みている間にストリームを閉じるとは思わないでしょうか。

// Send back a response.
stream.Write(bytes, 0, size);

また、サーバーが読み取りを認識して停止できるように、データの終わりにマークを付けるためにデータを配置することもできます。

0
user2527768