2012-03-13 81 views
4

我在寫一個小型(C#)客戶端應用程序,它使用TCP/IP連接將數據發送到遠程服務器。我使用的是標準的.Net TcpClient對象,並且希望將連接從客戶端打開,因爲我定期向服務器提交數據包。但是,服務器可能會關閉連接,在這種情況下,我需要知道在發送下一個數據包之前重新連接。.Net Socket不響應遠程斷開連接?

使用Wireshark的,我可以看到(只)以下的對話時,服務器將終止連接:

server >>> FIN, ACK
                ACK <<< client

我做什麼看到的是我的客戶擁有自己的FIN響應,完成連接關機。結果是我的客戶端程序只發現連接在發送下一個數據包後關閉。

有什麼辦法,我可以建立的TcpClient或其基礎插槽,以完成斷開連接,並提供一些反饋,使我的客戶端代碼知道發送下一個數據包之前重新連接?

添加響應於評論如下: 我的發送代碼很簡單 - 保持所述TcpClient的和的NetworkStream構件變量的對象,具有包含(基本上)以下的成員函數:

bool sent = false; 
byte[] buffer = Encoding.UTF8.GetBytes(dataString); 
while (!sent) 
{ 
    try 
    { 
     m_outStream.Write(buffer, 0, buffer.Length); 
     sent = true; 
    } 
    catch (Exception ex) 
    { 
     if (m_outStream != null) { m_outStream.Dispose(); } 
     m_client = new TcpClient(AddressFamily.InterNetwork); 
     m_client.Connect(ipAddress, ipPort); 
     m_outStream = m_client.GetStream(); 
    } 
} 

通過初始化m_client和m_outStream,每次只執行一次傳遞。然後使用Wireshark,我可以看到服務器發送一個標記爲FIN, ACK的數據包,客戶端用ACK響應。

下次我打電話給我的功能時,數據用PSH, ACK發出,而服務器用RST, ACK作出響應,但不讀取傳入數據。客戶沒有例外。

然後,我打電話給我的函數的第二時間,併產生一個異常導致連接被重新啓動。

回答

4

一般來說,你應該能夠使用Connected財產上TcpCient實例:

在這裏看到:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.connected.aspx

但是:

,因爲連接的屬性只反映了狀態在 連接的最近操作的,你應該嘗試發送 或接收消息DET呃目前的狀態。消息 發送失敗後,此屬性不再返回true。請注意,這個 行爲是有意設計的。你不能可靠地測試 連接的狀態,因爲在測試和發送的時間/接收, 連接可能已丟失。您的代碼應該假設連接了 套接字,並且正常處理失敗的傳輸。

請嘗試以下方法確保Connected標誌來保持最近的狀態:

var tcpClient = new TcpClient(); 
tcpClient.Connect(); 

var stream = tcpClient.GetStream(); 

// buffer size need to be > 0 
int[] buffer = new int[1]; 
stream.Read(buffer, 0, 0); 

if(!tcpClient.Connected) 
    // do something 

基於反編譯,應該可以從流中讀取0字節,至少沒有支票防止這種情況的.NET Framework TcpClient。但是,從框架調用的實際從網絡流中讀取的外部代碼可能不會大聲朗讀。

確保雙方的TcpClientDispose和你做後Stream,處分TcpClient不處置Stream的,所以你需要手動待辦事項這一點,之後,將所有資源都被釋放了(後GC)。

+0

感謝這個評論,但該文件指出,連只是作爲最後的讀/寫的事實。我擔心的是TcpClient正在將連接置於半棄狀態。 – Nick 2012-03-13 11:38:51

+0

我已經更新了我的答案,讓你知道如何去做這件事。但總的來說,框架處理Tcp連接的方式是,假設當它們不是時,它是開放和優雅地處理的。您還負責清理我的最後一段中解釋的'TcpClient'和'Stream'。 – ntziolis 2012-03-13 11:57:48

+0

非常感謝您的更新 - 我猜'stream.Read'調用與我發現的Socket調用基本相同,但是處於更高級別的抽象。 – Nick 2012-03-13 12:03:55

0

我發現瞭解決眼前問題的部分答案。

雖然我仍然不知道如果我能得到我的TcpClient完成斷線,我可以可靠地發現使用下面的代碼插座是否已斷開:

if (m_client.Client.Poll(1000, SelectMode.SelectRead) 
&& (m_client.Client.Available == 0)) 
{ 
    // Connection has gone - reconnect! 
    m_client = new TcpClient(AddressFamily.InterNetwork); 
    m_client.Connect(ipAddress, ipPort); 
} 
else 
{ 
    // Connection is good, nothing to do 
} 
+0

不錯,只有一點注意:不是專注於檢測連接是否打開,最好的做法是假定它是開放的,而是準備好代碼以優雅地處理異常,因爲沒有辦法告訴它是否已經關閉您的支票以及您發送/接收數據的時間,因此無論如何您都需要處理這些例外情況。 – ntziolis 2012-03-13 12:08:26

+0

是的,我的代碼被設置爲處理異常。我的問題是,在連接只有一半關閉的情況下,我的第一個數據包_is_只會被服務器使用RST標誌拒絕。但是,這不足以引發例外。只有當我_next_試圖發送一個數據包時發生異常,那個時候我已經丟失了前面的數據包。 – Nick 2012-03-13 12:33:23

+0

好吧,這聽起來像你做錯了什麼,肯定有一個例外,你可以發佈你的代碼? – ntziolis 2012-03-13 12:43:37

3

從MSDN TcpClient.Connected屬性:
類型:System.Boolean
如果客戶端套接字連接到最近一次操作的遠程資源,則爲true;否則,是錯誤的。

這意味着,您將不得不向服務器發送一些數據以檢測斷開的連接。從緩衝區中讀取時,讀取不起作用。

參見一個相關的問題(https://stackoverflow.com/a/25680975/2505186)我的答案,
鏈接別人的,其中描述了檢測連接狀態以適當的方式的回答:你 How to check if TcpClient Connection is closed?

重要:
客戶端當服務器這樣做時,不會自動關閉連接。連接處於CLOSE_WAIT狀態,然後在客戶端,在服務器端處於FIN_WAIT2狀態。請參閱維基百科文章Transmission Control Protocol中的相關部分。使用上面鏈接的答案中的代碼,您可以檢測到連接即將關閉。此外,您可以完成關閉程序,然後根據需要重新打開。

1

我用來檢測連接狀態的方法就是這個。

static class SocketExtensions 
    { 
     /// <summary> 
     /// Extension method to tell if the Socket REALLY is closed 
     /// </summary> 
     /// <param name="socket"></param> 
     /// <returns></returns> 
     public static bool IsConnected(this Socket socket) 
     { 
      try 
      { 
       return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0); 
      } 
      catch (SocketException) { return false; } 
     } 
    } 

當我想關閉連接,我致電以下。關閉基礎流,然後關閉客戶端對象。 我把它放在trys和catch中,以確保每次嘗試關閉它們。 注:PeerStream在這種情況下,是的NetworkStream(從Client.GetStream())

/// <summary> 
     /// Method will disconnect this peer forcefully 
     /// </summary> 
     public void Disconnect() 
     { 
      try 
      { 
       PeerStream.Close(); 
      } 
      catch (Exception ee) 
      { 
      } 
      try 
      { 
       _client.Client.Disconnect(false); 
      } 
      catch (Exception ee) 
      { 

      } 
     } 
相關問題