2009-12-13 116 views
2

我的一个朋友问我一个问题:在连接的服务器端使用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上的阻塞,或者查看套接字本身以检查断开连接 - 几乎就像是一个错误。或者可能缺乏某项功能。 ;)

回答

1

服务器是否期望通过同一连接发送多个对象?因此,我没有看到这个代码是如何工作的,因为没有发送分隔符表示第一个对象开始和下一个对象结束。

如果只有一个对象正在发送并且连接关闭后,那么原始代码将起作用。

必须启动网络操作才能确定连接是否仍处于活动状态。我会做的是,而不是直接从网络流反序列化,而是缓冲到MemoryStream中。这将允许我检测连接何时丢失。我还会使用消息分帧在流上分隔多个响应。

 MemoryStream ms = new MemoryStream(); 

     NetworkStream ns = client.GetStream(); 
     BinaryReader br = new BinaryReader(ns); 

     // message framing. First, read the #bytes to expect. 
     int objectSize = br.ReadInt32(); 

     if (objectSize == 0) 
       break; // client disconnected 

     byte [] buffer = new byte[objectSize]; 
     int index = 0; 

     int read = ns.Read(buffer, index, Math.Min(objectSize, 1024); 
     while (read > 0) 
     { 
      objectSize -= read; 
      index += read; 
      read = ns.Read(buffer, index, Math.Min(objectSize, 1024); 
     } 

     if (objectSize > 0) 
     { 
      // client aborted connection in the middle of stream; 
      break; 
     } 
     else 
     { 
      BinaryFormatter bf = new BinaryFormatter(); 
      using(MemoryStream ms = new MemoryStream(buffer)) 
      { 
       object o = bf.Deserialize(ns); 
       ReceiveMyObject(o); 
      } 
     } 
+1

这允许通过解释读取调用的结果来进行断开连接检测。然而,我的好奇心来源于框架明显缺乏“另一种”方式来实现这一点,尤其是考虑到它提供的原始实施的优雅。 此外,原始代码正确划分了对象,因此可以发送多条消息。我认为这是BinaryFormatter序列化对象的原因 - 它本质上知道它何时“完成”。 (尽管您提供的代码确实包含了对分隔符的需求。) – 2009-12-14 05:28:18

+0

您能否告诉我您希望在框架中看到如何实现?框架只是套接字之上的一个包装器,它是一种传输机制。其他语义需要由应用程序来实现。 此外,BinaryFormatter需要一个缓冲区作为反序列化的输入,因此您需要给它一个确切的缓冲区来获取对象。否则,它不会按预期工作。 – feroze 2009-12-14 15:41:37

+0

你是对的 - 反序列化确实需要一个确切的缓冲区,否则它会痛苦地死去,而不是BinaryFormatter确保流完成的责任;在这种情况下,它也不是NetworkStream的。我想也许我一直在思考这个错误的方式,它不是框架的责任,而是我的责任。 ;) – 2009-12-17 00:01:55

1

是的,但如果在获取大小之前失去连接会怎么样?即,下面的行前右:

// message framing. First, read the #bytes to expect. 

int objectSize = br.ReadInt32(); 

ReadInt32()将无限期地阻塞线程。