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

谁能解释这种行为?为什么睡眠有帮助?我究竟做错了什么?

升级版:

使用此示例代码,在关闭之前添加 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?

升级版:

这是服务器代码:

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) 似乎可以完全解决它,我无法捕捉到任何崩溃。不明白,为什么socket需要这几毫秒才能正确关闭。

4

5 回答 5

5

TcpClient使用的是Nagle 算法,并在通过网络发送数据之前等待更多数据。如果您将套接字关闭到快速,则不会传输任何数据。

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

NetworkStream有一种刷新流内容的Flush方法(我不确定这个方法是否对 MSDN 的评论有任何作用)

禁用 Nagle 算法:设置NoDelayTcpCLienttrue。

最后一个选项是设置LingerState. 方法文档指出,TcpClient在调用时使用CloseLingerStateClose

于 2013-03-10T18:07:07.040 回答
4

在几乎所有情况下,您都应该调用ShutdownaSocketTcpClient在处理它之前。粗鲁地处理会破坏连接。

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

设置NoDelay也是解决此问题的方法,但会损害性能。调用Flush恕我直言仍然会导致无序关闭。不要这样做,因为它们只是通过隐藏症状来掩盖问题的黑客。打电话Shutdown

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

不幸的是, TcpClient 有一个错误,它迫使您转到底层 Socket 以将其关闭:

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

根据 Reflector 的说法,如果你曾经访问过GetStream这个问题,并且 Close 不会关闭底层套接字。据我估计,这个错误是因为开发人员并不真正了解Shutdown. 很少有人知道,而且许多应用程序因此而出现故障。一个相关的问题。

于 2013-03-10T18:06:41.237 回答
1

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

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

升级版:

我已经在几台计算机上测试了这个错误,没有任何崩溃。似乎这是我计算机上的本地错误。

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附近 - 此代码将一一运行许多客户端):

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 回答
0

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

感谢大家试图帮助我。

无论如何,这太奇怪了。如果我能找出为什么我的电脑上存在这个错误,我会写下来。

于 2013-03-12T05:01:19.217 回答