7

クライアントから文字列を取得して画面に出力する単純なサーバーがあります。また、データを送信して閉じる単純なクライアントもあります。

static void Main()
{
        var client = new TcpClient("localhost", 26140);

        var stream = client.GetStream();
        Byte[] data = System.Text.Encoding.UTF8.GetBytes("CALC qwer"); 
        stream.Write(data, 0, data.Length);
        stream.Close();
        client.Close();
        //Thread.Sleep(100);
}

コメントを外した文字列 'Thread.Sleep(100)' を使用すると、問題なく動作します。しかし、コメントすると、クライアントが文字列を送信しないことがあります (5 ~ 10 回のうち 1 回)。Wireshark と netstat を見て、クライアントが SYN,ACK パッケージを送信し、接続を確立して、何も送信せずにソケットを閉じずに終了することに気付きました。

誰かがこの振る舞いを説明できますか? なぜ睡眠が役立つのですか?私は何を間違っていますか?

更新:

Fox32 に感謝します。

しかし、その後、最初のコードに戻りました。

var client = new TcpClient("localhost", 26140);
client.NoDelay = true;
var stream = client.GetStream();
var writer = new StreamWriter(stream);
writer.WriteLine("CALC qwer");
writer.Flush();
stream.Flush();
stream.Close();
client.Close();

NoDelay を使用しても機能しません。それは悪いです-ネットワークストリーム経由でStreamWriterを使用していますか?

更新:

サーバーコードは次のとおりです。

static void Main(string[] args)
    {
        (new Server(26140)).Run();
    }

サーバークラス:

public void Run()
    {
        var listener = new TcpListener(IPAddress.Any, port);
        listener.Start();
        while (true)
        {
            try
            {
                var client = listener.AcceptTcpClient();
                Console.WriteLine("Client accepted: " + client.Client.RemoteEndPoint);
                var stream = client.GetStream();
                stream.ReadTimeout = 2000;
                byte[] buffer = new byte[1000];
                stream.Read(buffer, 0, 1000);
                var s = Encoding.UTF8.GetString(buffer);
                Console.WriteLine(s);
            }
            catch (Exception ex)
            {
                Console.WriteLine("ERROR! " + ex.Message);
            }
        }
    }

更新:

Sleep(1) を追加すると、同時に実行されている 30 ~ 50 のクライアントのうちの 1 つでクラッシュが発生します。そして、Sleep(10) を追加すると完全に解決するようで、クラッシュをキャッチできません。ソケットが正しく閉じるのにこの数ミリ秒が必要な理由がわかりません。

4

5 に答える 5

5

NagleのアルゴリズムTcpClientを使用しており、データをネットワーク経由で送信する前に、さらにデータを待機します。ソケットを高速に閉じると、データは送信されません。

この問題を解決するには、複数の方法があります。

には、ストリームコンテンツをフラッシュするためNetworkStreamFlushメソッドがあります(このメソッドがMSDNのコメントから何かを実行するかどうかはわかりません)

Nagleのアルゴリズムを無効にします。をtrueに設定NoDelayTcpCLientます。

最後のオプションは、のを設定するLingerStateことですTcpClientCloseメソッドのドキュメントには、LingerState呼び出し中に使用されると記載されていますClose

于 2013-03-10T18:07:07.040 に答える
4

ほとんどすべての場合、あなたはそれを処分する前Shutdownに、Socketまたはを呼び出すことになっています。TcpClient失礼に破棄すると、接続が切断されます。

コードには基本的にTCPスタックとの競合状態が含まれています。

設定NoDelayもこれを修正しますが、パフォーマンスが低下します。IMHOを呼び出すFlushと、それでも無秩序なシャットダウンが発生します。それらは症状を隠すことによって問題を塗りつぶす単なるハックなので、それをしないでください。を呼び出しShutdownます。

私が知っている唯一の有効な解決策は、Shutdownで呼ばれることであることを強調したいと思います。データをネットワークに強制するだけでも。ネットワークの障害により、それでも失われる可能性があります。ソケットの失礼なキルであるため、呼び出された後は再送信されません。SocketFlushCloseClose

残念ながら、TcpClientにはバグがあり、基盤となるソケットに移動してシャットダウンする必要があります。

tcpClient.Client.Shutdown();
tcpClient.Close();

Reflectorによると、これまでにアクセスしたことがある場合、GetStreamこの問題が発生し、Closeは基になるソケットを閉じません。私の推定では、このバグは、開発者がの重要性を本当に知らなかったために発生しましたShutdown。知っている人はほとんどおらず、多くのアプリはそれが原因でバグがあります。関連する質問。

于 2013-03-10T18:06:41.237 に答える
1

サーバー側のコードでは、一度しか呼び出しRead()ていませんが、read を呼び出したときにデータが利用可能になるとは想定できません。利用可能なデータがなくなるまで、ループで読み取りを続ける必要があります。以下の完全な例を参照してください。

最小限のコードで問題を再現しようとしましたが、再現できませんでした。サーバーは毎回クライアントのメッセージを出力します。andなどの特別な設定はNoDelayなく、明示的なClose()orもなく、すべてのリソースが適切に破棄されることを保証するステートメントFlush()のみです。Using

class Program
{
    static int port = 123;
    static string ip = "1.1.1.1";
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        StartServer();
        waitHandle.WaitOne();

        for (int x=0; x<1000; x++)
        {
            StartClient(x);
        }

        Console.WriteLine("Done starting clients");
        Console.ReadLine();
    }

    static void StartClient(int count)
    {
        Task.Factory.StartNew((paramCount) =>
        {
            int myCount = (int)paramCount;

            using (TcpClient client = new TcpClient(ip, port))
            {
                using (NetworkStream networkStream = client.GetStream())
                {
                    using (StreamWriter writer = new StreamWriter(networkStream))
                    {
                        writer.WriteLine("hello, tcp world #" + myCount);
                    }
                }
            }
        }, count);
    }

    static void StartServer()
    {
        Task.Factory.StartNew(() => 
        {
            try
            {
                TcpListener listener = new TcpListener(port);
                listener.Start();

                Console.WriteLine("Listening...");
                waitHandle.Set();

                while (true)
                {
                    TcpClient theClient = listener.AcceptTcpClient();

                    Task.Factory.StartNew((paramClient) => {
                        TcpClient client = (TcpClient)paramClient;

                        byte[] buffer = new byte[32768];
                        MemoryStream memory = new MemoryStream();
                        using (NetworkStream networkStream = client.GetStream())
                        {
                            do
                            {
                                int read = networkStream.Read(buffer, 0, buffer.Length);
                                memory.Write(buffer, 0, read);
                            }
                            while (networkStream.DataAvailable);
                        }

                        string text = Encoding.UTF8.GetString(memory.ToArray());
                        Console.WriteLine("from client: " + text);
                    }, theClient);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }, TaskCreationOptions.LongRunning);
    }
}
于 2013-03-10T18:48:57.327 に答える
0

このバグをいくつかのコンピューターで再現して、コードを試しました。誰もクラッシュしません。私のローカル コンピューターのバグのようです。

私を助けてくれてありがとう。

とにかく、それはとても奇妙です。このバグが私のコンピューターに存在する理由がわかったら、それについて書きます。

于 2013-03-12T05:01:19.217 に答える
0

更新:

このバグを複数のコンピューターでテストしましたが、何もクラッシュしませんでした。私のコンピューターのローカルバグのようです。

ENDOFPD

それで、このバグの再現について私が見つけたこと。@Despertar - あなたのコードはうまくいきます。ただし、このバグの状態を再現するものではありません。クライアントでは、データを送信して終了する必要があります。そして、あなたのコードでは、多くのクライアントがデータを送信しており、すべてのアプリケーションが閉じています。

これは、コンピューターでこれをテストする方法です。サーバー(接続を受け入れて着信データを出力するだけ)、クライアント(終了時にデータを送信するだけ)、およびユーティリティを実行しています(クライアントexeを数回実行します)。

そこで、サーバーを起動し、実行中のユーティリティをクライアント フォルダーにコピーして実行します。ulility を実行すると、150 のクライアントがサーバーに接続し始め、そのうちの 5 ~ 10 が停止します (サーバー コンソールにエラーが表示されます)。また、クライアントで Thread.Sleep() のコメントを外しても問題なく動作し、エラーは発生しません。

このバージョンのコードを再現できる人はいますか?

クライアントコード:

private static void Main(string[] args)
{
try
{
    using (TcpClient client = new TcpClient(ip, port))
    {
        using (NetworkStream networkStream = client.GetStream())
        {
            using (StreamWriter writer = new StreamWriter(networkStream))
            {
                writer.WriteLine("# hello, tcp world #");
                writer.Flush();
            }
            networkStream.Flush();
            networkStream.Close();
        }
        client.Close();
        //Thread.Sleep(10);
     }
     }
     catch (Exception ex)
     {
         Console.WriteLine(ex.Message);
     }
}

クライアントを数回実行するコード (exe ファイルにコンパイルし、クライアントの exe の近くに置く - このコードは、多くのクライアントを 1 つずつ実行します):

static void Main(string[] args)
{
    string path = "YOU_CLIENT_PROJECT_NAME.exe";
    for (int i = 0; i < 150; i++ )
    {
        Console.WriteLine(i);
        Process.Start(path);
        Thread.Sleep(50);
    }
    Console.WriteLine("Done");
    Console.ReadLine();
}

(パスを正しいexeファイル名に変更することを忘れないでください)

サーバーコード:

class Program
{

    static int port = 26140;
    static AutoResetEvent waitHandle = new AutoResetEvent(false);

    static void Main(string[] args)
    {
        StartServer();
        waitHandle.WaitOne();
        Console.ReadLine();
    }

    static void StartServer()
    {
        Task.Factory.StartNew(() =>
        {
            try
            {
                TcpListener listener = new TcpListener(port);
                listener.Start();

                Console.WriteLine("Listening...");
                waitHandle.Set();

                while (true)
                {
                    TcpClient theClient = listener.AcceptTcpClient();

                    Task.Factory.StartNew(paramClient =>
                    {
                        try
                        {
                            TcpClient client = (TcpClient) paramClient;
                            byte[] buffer = new byte[32768];
                            MemoryStream memory = new MemoryStream();
                            using (NetworkStream networkStream = client.GetStream())
                            {
                                networkStream.ReadTimeout = 2000;
                                do
                                {
                                    int read = networkStream.Read(buffer, 0, buffer.Length);
                                    memory.Write(buffer, 0, read);
                                } while (networkStream.DataAvailable);
                                string text = Encoding.UTF8.GetString(memory.ToArray());
                            }
                        }
                        catch (Exception e)
                        {
                            Console.WriteLine("ERROR: " + e.Message);
                        }
                    }, theClient);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }, TaskCreationOptions.LongRunning);
    }
}
于 2013-03-11T08:02:24.760 に答える