.net 关闭TcpListener和TcpClient连接的正确顺序(哪一侧应为主动关闭)
我看到上面写着: 因此,发起终止的对等方——即首先调用close()——将以TIME_WAIT状态结束。[……] 但是,服务器上有很多处于TIME_WAIT状态的套接字可能会出现问题,因为这可能最终会阻止新连接被接受。[……] 相反,请设计应用程序协议,使连接终止始终从客户端启动。如果客户机始终知道何时已读取所有剩余数据,则可以启动终止序列。例如,浏览器从内容长度HTTP头知道何时已读取所有数据并可以启动关闭。(我知道,在HTTP 1.1中,它会将其打开一段时间,以便可能的重用,然后将其关闭。) 我想使用TcpClient/TcpListener实现这一点,但不清楚如何使其正常工作 方法1:双方关闭 这是大多数MSDN示例说明的典型方式-双方调用.net 关闭TcpListener和TcpClient连接的正确顺序(哪一侧应为主动关闭),.net,sockets,tcpclient,tcplistener,.net,Sockets,Tcpclient,Tcplistener,我看到上面写着: 因此,发起终止的对等方——即首先调用close()——将以TIME_WAIT状态结束。[……] 但是,服务器上有很多处于TIME_WAIT状态的套接字可能会出现问题,因为这可能最终会阻止新连接被接受。[……] 相反,请设计应用程序协议,使连接终止始终从客户端启动。如果客户机始终知道何时已读取所有剩余数据,则可以启动终止序列。例如,浏览器从内容长度HTTP头知道何时已读取所有数据并可以启动关闭。(我知道,在HTTP 1.1中,它会将其打开一段时间,以便可能的重用,然后将其关闭。)
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);
}
}
}
现在我不再有任何等待时间
,但在CLOSE\u WAIT
、FIN\u WAIT
等不同阶段,我确实会留下套接字,这些都需要很长时间才能消失
方法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();
var tcpClient=listener.EndAcceptTcpClient(ar);
ThreadPool.QueueUserWorkItem(委托
{
var stream=tcpClient.GetStream();
ReadSomeData(流);
写媒体(流);
Thread.Sleep(100);//这正是TCP的工作方式,您无法避免。您可以为服务器上的等待时间或等待时间设置不同的超时,但仅此而已
之所以这样,是因为在TCP上,一个数据包可以到达一个你很久以前就已经关闭的套接字。如果你已经在同一个IP和端口上打开了另一个套接字,它会接收到前一个会话的数据,这会把它弄糊涂的。特别是考虑到大多数人认为TCP是可靠的:
如果您的客户机和服务器都正确地实现了TCP(例如,正确地处理干净的关机),那么客户机或服务器是否关闭了连接实际上并不重要。因为这听起来像是您管理了双方,所以这不应该是个问题
您的问题似乎与服务器的正确关闭有关。当套接字的一侧关闭时,另一侧将读取长度为0
的-这是您的消息,表明通信已结束。您很可能在服务器代码中忽略了这一点-这是一种特殊情况,表示“您现在可以安全地处置此套接字,请立即执行”
在您的情况下,服务器端关闭似乎是最合适的
但是,TCP相当复杂,互联网上的大多数样本都是严重缺陷(尤其是C的样本),例如,不难找到C++的好样本。忽略协议的许多重要部分。我有一个简单的示例,它可能对您有用-它仍然不是完美的TCP,但它比MSDN示例要好得多,例如
以下哪种方法是正确的,为什么?(假设我希望客户是“主动关闭”方)
在理想情况下,您可以让服务器向客户端发送一个带有特定操作码的RequestDisconnect
数据包,然后客户端在收到该数据包后关闭连接来处理该数据包。这样,您就不会在服务器端出现陈旧的套接字(因为套接字是资源,而资源是有限的,所以拥有过时的是一件坏事)
如果客户端随后通过处理套接字(或调用Close()
执行其断开连接顺序,如果您使用的是TcpClient
,则客户端将在服务器上将套接字置于Close\u WAIT
状态,这意味着连接正在关闭过程中
有没有更好的方法来实现方法3?我们希望关闭由客户端启动(这样客户端就有了等待的时间),但是当客户端关闭时,我们还希望关闭服务器上的连接
是。如上所述,让服务器发送数据包,请求客户端关闭与服务器的连接
我的场景实际上与web服务器相反;我有一个单独的客户端,可以连接和断开与许多不同远程机器的连接。我宁愿服务器的连接停留在时间上,而不是等待,以释放客户端上的资源。在这种情况下,我应该让服务器执行活动关闭,并在客户端上执行睡眠/关闭吗
是的,如果这是您想要的,请随时在服务器上调用Dispose
,以摆脱客户端
另一方面,您可能希望研究使用原始对象,而不是TcpClient
,因为它非常有限。如果您直接对套接字进行操作,您可以使用sendsync
和所有其他用于套接字操作的异步方法。使用手动Thread.Sleep
调用是我应该避免的事情-这些操作操作本质上是异步的-在您向流写入某些内容之后断开连接应该在SendAsync
的回调中进行,而不是在睡眠之后进行。Paul,您自己也做了一些伟大的研究。我也在这方面做了一些工作。我发现关于时间等待主题的一篇非常有用的文章是这:
它有一些特定于Linux的问题,但所有TCP级别的内容都是通用的
最终双方都应该关闭(即完成FIN/ACK握手),因为您不希望FIN\u WAIT或close\u WAIT状态延迟,这只是“坏”TCP。我会避免使用RST强制关闭连接,因为这可能会在其他地方造成问题
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();
tcpClient.Client.Shutdown(SocketShutdown.Send);