我的一個朋友問我一個問題:在連接的服務器端使用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