我的一个朋友问我一个问题:在连接的服务器端使用NetworkStream类时,如果客户端断开连接,NetworkStream无法检测到它。在使用NetworkStream时检测客户端TCP断开类别
剥了下来,他的C#代码是这样的:
List<TcpClient> connections = new List<TcpClient>();
TcpListener listener = new TcpListener(7777);
listener.Start();
while(true)
{
if (listener.Pending())
{
connections.Add(listener.AcceptTcpClient());
}
TcpClient deadClient = null;
foreach (TcpClient client in connections)
{
if (!client.Connected)
{
deadClient = client;
break;
}
NetworkStream ns = client.GetStream();
if (ns.DataAvailable)
{
BinaryFormatter bf = new BinaryFormatter();
object o = bf.Deserialize(ns);
ReceiveMyObject(o);
}
}
if (deadClient != null)
{
deadClient.Close();
connections.Remove(deadClient);
}
Thread.Sleep(0);
}
代码工作,在客户端可以成功连接和服务器可以读取发送给它的数据。但是,如果远程客户端调用tcpClient.Close(),则服务器不会检测到断开连接 - client.Connected保持为true,并且ns.DataAvailable为false。
提供的堆栈溢出搜索和答案 - 由于没有调用Socket.Receive,套接字未检测到断开连接。很公平。我们可以解决的是:
foreach (TcpClient client in connections)
{
client.ReceiveTimeout = 0;
if (client.Client.Poll(0, SelectMode.SelectRead))
{
int bytesPeeked = 0;
byte[] buffer = new byte[1];
bytesPeeked = client.Client.Receive(buffer, SocketFlags.Peek);
if (bytesPeeked == 0)
{
deadClient = client;
break;
}
else
{
NetworkStream ns = client.GetStream();
if (ns.DataAvailable)
{
BinaryFormatter bf = new BinaryFormatter();
object o = bf.Deserialize(ns);
ReceiveMyObject(o);
}
}
}
}
(我已经离开了异常处理代码的简洁。)
此代码的工作,但是,我不会把这个解决方案“优雅”。我知道的另一个优雅的解决方案是为每个TcpClient产生一个线程,并允许BinaryFormatter.Deserialize(néeNetworkStream.Read)调用阻塞,这将正确检测断开连接。尽管如此,这确实具有为每个客户端创建和维护线程的开销。
我感觉我错过了一些秘密,真棒的答案,它会保留原始代码的清晰度,但避免使用额外的线程来执行异步读取。虽然,也许NetworkStream类从来没有被设计用于这种用法。任何人都可以点亮一下吗?
更新:只是想澄清,我很感兴趣,看看是否在.NET框架具有覆盖此使用的NetworkStream的溶液(即轮询和避免阻塞) - 显然这是可以做到; NetworkStream可以轻松包装在提供功能的支持类中。这似乎很奇怪,该框架本质上要求您使用线程来避免NetworkStream.Read上的阻塞,或者查看套接字本身以检查断开连接 - 几乎就像是一个错误。或者可能缺乏某项功能。 ;)
这允许通过解释读取调用的结果来进行断开连接检测。然而,我的好奇心来源于框架明显缺乏“另一种”方式来实现这一点,尤其是考虑到它提供的原始实施的优雅。 此外,原始代码正确划分了对象,因此可以发送多条消息。我认为这是BinaryFormatter序列化对象的原因 - 它本质上知道它何时“完成”。 (尽管您提供的代码确实包含了对分隔符的需求。) – 2009-12-14 05:28:18
您能否告诉我您希望在框架中看到如何实现?框架只是套接字之上的一个包装器,它是一种传输机制。其他语义需要由应用程序来实现。 此外,BinaryFormatter需要一个缓冲区作为反序列化的输入,因此您需要给它一个确切的缓冲区来获取对象。否则,它不会按预期工作。 – feroze 2009-12-14 15:41:37
你是对的 - 反序列化确实需要一个确切的缓冲区,否则它会痛苦地死去,而不是BinaryFormatter确保流完成的责任;在这种情况下,它也不是NetworkStream的。我想也许我一直在思考这个错误的方式,它不是框架的责任,而是我的责任。 ;) – 2009-12-17 00:01:55