关闭TcpListener和TcpClient连接的正确顺序(哪一方应该是活动关闭)

Pau*_*ell 18 .net sockets tcplistener tcpclient

在上一个问题中读到了这个答案:

因此,启动终止的对等体 - 即首先调用close() - 将最终处于TIME_WAIT状态.[...]

但是,在服务器上处于TIME_WAIT状态的许多套接字可能会出现问题,因为它最终可能会阻止接受新连接.[...]

相反,请设计应用程序协议,以便始终从客户端启动连接终止.如果客户端总是知道它何时读取了所有剩余数据,则它可以启动终止序列.例如,浏览器在读取所有数据时可以从Content-Length HTTP标头中获知,并且可以启动关闭.(我知道在HTTP 1.1中它会保持打开一段时间以便重用,然后关闭它.)

我想使用TcpClient/TcpListener来实现它,但目前尚不清楚如何使其正常工作.

方法1:双方关闭

这是大多数MSDN示例所示的典型方式 - 双方调用Close(),而不仅仅是客户端:

private static void AcceptLoop()
{
    listener.BeginAcceptTcpClient(ar =>
    {
        var tcpClient = listener.EndAcceptTcpClient(ar);

        ThreadPool.QueueUserWorkItem(delegate
        {
            var stream = tcpClient.GetStream();
            ReadSomeData(stream);
            WriteSomeData(stream);
            tcpClient.Close();   <---- note
        });

        AcceptLoop();
    }, null);
}

private static void ExecuteClient()
{
    using (var client = new TcpClient())
    {
        client.Connect("localhost", 8012);

        using (var stream = client.GetStream())
        {
            WriteSomeData(stream);
            ReadSomeData(stream);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我运行20个客户端之后,套装软件显示了很多插座的客户端和服务器都停留在TIME_WAIT,这需要相当一段时间才能消失.

在此输入图像描述

方法2:只是客户关闭

根据上面的引用,我删除了Close()我的监听器上的调用,现在我只依赖于客户端关闭:

var tcpClient = listener.EndAcceptTcpClient(ar);

ThreadPool.QueueUserWorkItem(delegate
{
    var stream = tcpClient.GetStream();
    ReadSomeData(stream);
    WriteSomeData(stream);
    // tcpClient.Close();   <-- Let the client close
});

AcceptLoop();
Run Code Online (Sandbox Code Playgroud)

现在我不再有任何TIME_WAIT的,但我得到剩下的各个阶段插座CLOSE_WAIT,FIN_WAIT等还需要很长的时间才能消失.

TCPView,一切都关闭了

方法3:给客户时间先关闭

这次我在关闭服务器连接之前添加了一个延迟:

var tcpClient = listener.EndAcceptTcpClient(ar);

ThreadPool.QueueUserWorkItem(delegate
{
    var stream = tcpClient.GetStream();
    ReadSomeData(stream);
    WriteSomeData(stream);
    Thread.Sleep(100);      // <-- Give the client the opportunity to close first
    tcpClient.Close();      // <-- Now server closes
});

AcceptLoop();
Run Code Online (Sandbox Code Playgroud)

这似乎更好 - 现在只有客户端套接字TIME_WAIT; 服务器套接字已正确关闭:

在此输入图像描述

这似乎与先前链接的文章所说的一致:

因此,启动终止的对等体 - 即首先调用close() - 将最终处于TIME_WAIT状态.

问题:

  1. 哪种方法是正确的方法,为什么?(假设我希望客户成为'主动关闭'一方)
  2. 有没有更好的方法来实施方法3?我们希望关闭由客户端启动(以便客户端留下TIME_WAIT),但是当客户端关闭时,我们还希望关闭服务器上的连接.
  3. 我的场景实际上与Web服务器相反; 我有一个客户端连接和断开许多不同的远程计算机.我宁愿服务器卡住了连接TIME_WAIT,以释放客户端上的资源.在这种情况下,我应该让服务器执行主动关闭,并将睡眠/关闭放在我的客户端上吗?

完整的代码自己尝试一下:

https://gist.github.com/PaulStovell/a58cd48a5c6b14885cf3

编辑:另一个有用的资源:

http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html

对于确实建立出站连接以及接受入站连接的服务器,黄金法则是始终确保如果需要发生TIME_WAIT,它最终会在另一个对等体而不是服务器上.无论出于什么原因,最好的方法是永远不要从服务器发起主动关闭.如果您的对等体超时,则使用RST中止连接而不是关闭它.如果您的对等方发送无效数据,则中止连接等.这个想法是,如果您的服务器永远不会启动活动关闭,它永远不会累积TIME_WAIT套接字,因此永远不会遇到它们导致的可伸缩性问题.虽然它' 很容易看到如何在发生错误情况时中止连接正常连接终止?理想情况下,您应该在协议中设计一种方法,让服务器告诉客户端它应该断开连接,而不是简单地让服务器发起主动关闭.因此,如果服务器需要终止连接,则服务器发送应用程序级别"我们已完成"消息,客户端将此消息作为关闭连接的理由.如果客户端无法在合理的时间内关闭连接,则服务器将中止连接.客户端将关闭连接的消息作为理由.如果客户端无法在合理的时间内关闭连接,则服务器将中止连接.客户端将关闭连接的消息作为理由.如果客户端无法在合理的时间内关闭连接,则服务器将中止连接.

在客户端上事情稍微复杂一点,毕竟,有人必须发起一个主动关闭以干净地终止TCP连接,如果它是客户端,那么TIME_WAIT将结束.但是,将TIME_WAIT结束在客户端上有几个优点.首先,如果出于某种原因,由于TIME_WAIT中套接字的累积,客户端最终会遇到连接问题,那么它只是一个客户端.其他客户不会受到影响.其次,快速打开和关闭到同一服务器的TCP连接是低效的,因此在TIME_WAIT问题之外尝试维持连接的时间较长而不是较短的时间段是有意义的.不要设计客户端每分钟连接到服务器的协议,并通过打开新连接来实现.相反,使用持久连接设计并仅在连接失败时重新连接,如果中间路由器拒绝在没有数据流的情况下保持连接打开,那么您可以实现应用程序级别ping,使用TCP保持活动状态或仅接受路由器正在重置连接; 好处是你没有累积TIME_WAIT套接字.如果您在连接上所做的工作自然是短暂的,那么请考虑某种形式的"连接池"设计,从而保持连接的开放和重用.最后,如果您必须快速打开和关闭从客户端到同一服务器的连接,那么您可以设计一个可以使用的应用程序级别关闭序列,然后以一个中断关闭的方式执行此操作.您的客户可以发送"我已完成"的消息,

Lua*_*aan 4

这就是 TCP 的工作原理,你无法避免它。您可以为服务器上的 TIME_WAIT 或 FIN_WAIT 设置不同的超时,但仅此而已。

原因是在 TCP 上,数据包可能会到达您很久以前关闭的套接字。如果您已经在相同的 IP 和端口上打开了另一个套接字,它将接收到前一个会话的数据,这会造成混乱。特别是考虑到大多数人认为 TCP 是可靠的:)

如果您的客户端和服务器都正确实现了 TCP(例如,正确处理干净关闭),那么客户端或服务器是否关闭连接并不重要。因为听起来你管理双方,所以这不应该是一个问题。

您的问题似乎与服务器上的正确关闭有关。当插座的一侧闭合时,另一侧将Read具有长度0- 这就是通信结束的消息。您很可能在服务器代码中忽略这一点 - 这是一种特殊情况,表示“您现在可以安全地处置此套接字,立即执行”。

就您而言,服务器端关闭似乎是最合适的。

但实际上,TCP 相当复杂。互联网上的大多数示例都存在严重缺陷(尤其是 C# 示例 - 例如,找到一个好的 C++ 示例并不难)并且忽略了协议的许多重要部分,这无济于事。我有一个可能对你有用的简单示例 - https://github.com/Luaancz/Networking/tree/master/Networking%20Part%201它仍然不是完美的 TCP,但它比例如,MSDN 示例。