2009-12-13 105 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()將無限期地阻塞線程。