TcpClient.Close()仅适用于Thread.Sleep()

时间:2013-03-10 17:53:52

标签: c# tcp tcpclient thread-sleep

我有简单的服务器从客户端获取字符串并在屏幕上打印。 我也有简单的客户端,发送数据和关闭:

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包,建立连接并退出而不发送任何内容而不关闭套接字。

有人可以解释这个行为吗?睡眠有帮助吗?我做错了什么?

UPD:

使用此示例代码在关闭之前添加flush()确实有效,感谢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?

UPD:

这是服务器代码:

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);
            }
        }
    }

UPD:

添加偶数睡眠(1)会导致同时运行的30-50个客户端中的1个发生崩溃。 添加睡眠(10)似乎完全解决了,我无法抓住任何崩溃。 不明白,为什么socket需要这几毫秒来正确关闭。

5 个答案:

答案 0 :(得分:5)

TcpClient正在使用Nagle's algorithm并在通过网络发送之前等待更多数据。如果快速关闭套接字,则不会传输任何数据。

您有多种方法可以解决此问题:

NetworkStream有一个Flush方法用于刷新流内容(我不确定此方法是否可以对MSDN上的注释执行任何操作)

禁用Nagle算法:将TcpCLient的{​​{3}}设置为true。

最后一个选项是设置TcpClient的{​​{3}}。 NoDelay方法文档指出,在调用LingerState时使用了Close

答案 1 :(得分:4)

在几乎所有情况下,您应该在ShutdownSocket之前致电TcpClient,然后再处理它。粗暴地处置会破坏连接。

您的代码基本上包含TCP堆栈的竞争条件。

设置NoDelay也是对此的修复,但会影响性能。拨打Flush恕我直言仍会导致无序关机。不要这样做,因为它们只是通过隐藏症状来掩盖问题的黑客。致电Shutdown

我想强调Shutdown上调用的Socket是我所知道的唯一有效解决方案。甚至Flush只会强制数据进入网络。由于网络连接,它仍然可能丢失。调用Close后将不会重新传输,因为Close是套接字上的粗鲁终止。

不幸的是TcpClient has a bug迫使你去底层的Socket关闭它:

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

根据Reflector,如果您曾访问过GetStream,则会出现此问题,而Close不会关闭底层套接字。据我估计,这个错误产生的原因是开发人员并不真正了解Shutdown的重要性。很少有人知道,很多应用程序因此而错。 A related question.

答案 2 :(得分:1)

在您的服务器端代码中,您只调用Read()一次,但是当您调用read时,您无法假设数据可用。您必须继续循环读取,直到没有更多数据可用。请参阅下面的完整示例。

我尝试使用最少量的代码重现您的问题但无法做到。服务器每次都打印出客户端消息。没有特殊设置,例如NoDelay,也没有明确的Close()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);
    }
}

答案 3 :(得分:0)

UPD:

我在几台计算机上测试了这个bug,没有任何崩溃。好像它是我计算机上的本地错误。

ENDOFUPD

所以,我发现了关于再现这个bug的内容。 @Despertar - 您的代码运行良好。但它不是重现这个bug的条件。在客户端上,您需要发送数据并在其后退出。在您的代码中,许多客户端正在发送数据,并且在所有应用程序关闭之后。

这就是我在计算机上测试它的方法: 我有服务器(只接受连接和打印传入数据),客户端(只在结束退出时发送数据)和运行实用程序(运行客户端exe多次)。

因此,我启动服务器,将运行实用程序复制到clients文件夹并运行它。 运行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附近 - 这段代码将逐个运行许多客户端):

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();
}

(不要忘记更改exent 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);
    }
}

答案 4 :(得分:0)

我尝试过代码,在几台计算机上重现这个错误。没有人崩溃。好像这是我当地的计算机错误。

感谢大家帮助我。

无论如何,这太奇怪了。如果我发现我的计算机上存在这个错误的原因,我会写一下。