2011-10-04 114 views
9

我一直在爭取整個3天的一個問題我找不到任何解決方案,請幫助:)
我使用Visual Studio 2010和C#語言。如何在連接後測試TCPClient斷開的連接?

我有一個像服務器一樣工作的設備,它在非常不規則的時間段內發送一些數據(不可能定義任何讀取超時)。
我寫了一個TCP客戶端連接到該服務器並讀取數據。它工作正常,但是當網絡出現問題並且服務器變得不可用時(例如,當我從計算機中拔出網線時),應用程序需要大約10秒鐘才能「注意到」,並且沒有連接到服務器並拋出一個例外。 (我不知道爲什麼正好10秒?它在什麼地方被定義了?我可以改變它嗎?)
我想反應更快 - 比方說連接斷開一秒後。
谷歌搜索答案然而不提供任何工作解決方案。

測試代碼如下,我嘗試在2個線程上進行測試:一個是讀取數據,另一個是查找連接狀態,當它斷開時應該報警。它不適用於TCPClient和Socket類。我試圖用tcpClient.SendTimeout = 100;stream.WriteTimeout = 100;讀取/寫入一些數據,但它似乎不起作用。

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 

namespace TCPClient 
{ 
    class Program 
    { 
     static volatile bool _continue = true; 
     static TcpClient tcpClient; 
     static NetworkStream stream; 

     static void Main(string[] args) 
     { 
      try 
      { 
       //client thread - listen from server 
       Thread tcpListenThread = new Thread(TcpListenThread); 
       tcpListenThread.Start(); 

       //connection checking thread 
       Thread keepAliveThread = new Thread(KeepAliveThread); 
       keepAliveThread.Start(); 

       while (_continue) 
       { 
        if (Console.ReadLine() == "q") 
        { 
         _continue = false; 
        } 
       } 

       keepAliveThread.Join(2000); 
       if (keepAliveThread.IsAlive) 
       { 
        Console.WriteLine("Thread keepAlive has been aborted..."); 
        keepAliveThread.Abort(); 
       } 

       tcpListenThread.Join(2000); 
       if (tcpListenThread.IsAlive) 
       { 
        Console.WriteLine("Listen thread has been aborted..."); 
        tcpListenThread.Abort(); 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("\n" + ex.Message); 
      } 

      Console.WriteLine("\nHit any key to quit..."); 
      Console.Read(); 
     } 

     private static void TcpListenThread() 
     { 
      string server = "172.20.30.40"; 
      int port = 3000; 

      try 
      { 
       using (tcpClient = new TcpClient()) 
       { 
        tcpClient.Connect(server, port); 

        if (tcpClient.Connected) 
         Console.WriteLine("Successfully connected to server"); 

        using (stream = tcpClient.GetStream()) 
        { 

         while (_continue) 
         { 
          Byte[] data = new Byte[1024]; 
          Int32 bytes = stream.Read(data, 0, data.Length); 
          string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes); 

          Console.WriteLine("Received: {0}, responseData); 
         } 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Listen thread exception! " + ex.Message); 
      } 
     } 


     private static void KeepAliveThread() 
     { 
      while (_continue) 
      { 
       if (tcpClient != null) 
       { 
        try 
        { 
         //...what to put here to check or throw exception when server is not available??... 
        } 
        catch (Exception ex) 
        { 
         Console.WriteLine("Disconnected..."); 
        } 
       } 

       Thread.Sleep(1000); //test for connection every 1000ms 
      } 
     } 
    } 
} 

編輯:
@卡斯滕的回答是:雖然它看起來很有希望,這種解決方案不工作...
我做了最簡單的測試應用程序:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Net.Sockets; 
using System.Threading; 

namespace TCPClientTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      try 
      { 
       string server = "172.20.30.40"; 
       int port = 3000; 

       using (TcpClient tcpClient = new TcpClient()) 
       { 
        tcpClient.Connect(server, port); 

        int i = 0; 
        while (true) 
        { 
         // This is how you can determine whether a socket is still connected. 
         bool blockingState = tcpClient.Client.Blocking; 
         try 
         { 
          byte[] tmp = new byte[1]; 

          tcpClient.Client.Blocking = false; 
          tcpClient.Client.Send(tmp, 0, 0); 
          Console.WriteLine("Connected!"); 
         } 
         catch (SocketException e) 
         { 
          // 10035 == WSAEWOULDBLOCK 
          if (e.NativeErrorCode.Equals(10035)) 
           Console.WriteLine("Still Connected, but the Send would block"); 
          else 
          { 
           Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode); 
          } 
         } 
         finally 
         { 
          tcpClient.Client.Blocking = blockingState; 
         } 

         Console.WriteLine("Connected: {0} ({1})", tcpClient.Client.Connected, i++); 

         Thread.Sleep(1000); 
        } 
       } 
      } 
      catch (Exception ex) 
      { 
       Console.WriteLine("Global exception: {0}", ex.Message); 
      } 
     } 
    } 
} 

結果顯示如下:

連接!
連接:真

加上我的訂單號每隔一秒。當我斷開網絡電纜時,開始打印需要8秒鐘:

Disonnected:error code 10054!
連接:假

so by 8 seconds我不知道連接丟失。看起來pinging是最好的選擇,但我會測試另一種解決方案。

+0

可能的重複:[如何檢查(TCP)套接字是否(在C#中)(dis)連接?](http://stackoverflow.com/questions/515458/how-can-i-check-whether-a -tcp-socket-is-disconnected-in-c) – ladenedge

+2

@ladenedge可能重複...除了這個有更多的示例代碼來幫助其他人以不同的方式學習。我知道你的評論已經過時了,但是當你這樣的人毫無用處並且不鼓勵那些效率不高的評論時,這令人沮喪。 – Euthyphro

回答

6

簡單的答案。你不能。 Tcp的製作方式不允許這樣做。但是,實現這一點的正常方式是發送ping的時間間隔比消息更短。所以,比如說,每當你從服務器收到一條消息時,你就開始一個倒計時1分鐘的時鐘,然後你發送一個「ping」命令(如果你之間沒有收到新消息)。如果您在30秒內沒有收到對「ping」的響應,則認爲連接中斷。從理論上講,你應該可以在包級別上做到這一點(tcp發送你應該能夠檢查的ACK),但是我不知道C#或者其他編程語言是否可行問題,或者如果你需要在固件/硬件中做到這一點。

+0

謝謝你的回答。到目前爲止,看起來pinging是最好的選擇,但是當挖掘答案時,我發現一些頁面被指出是一個不好的解決方案 - 不知道爲什麼。 – mj82

+0

我認爲你很想理解某些東西,或者我是(這是非常可能的btw xD)。據我所知,你剛剛更新你的帖子到**是** ping,只是在一個更聰明的方式比自己實際做。另外,8秒直到你發現斷開連接聽起來不錯。例如IRC服務器,在ping(!)之間最多使用5分鐘。 – Alxandr

+0

我已更新我的帖子以顯示,由CarstenKönig提出的解決方案不起作用。它試圖每秒鐘執行'tcpClient.Client.Send(...)',所以我期望在斷開電纜之後它會在一秒鐘之後拋出異常或什麼 - 它不會。另一方面,Ping類的「正常」方式將起作用。所以,我的更新帖子不等於ping :) – mj82

0

有一個名爲SO_KEEPALIVE的套接字選項,它由TCP協議發送偶然探測以確保管道的另一端仍在監聽。可以通過套接字來設置探針的頻率。IOControl

+0

呃,KEEPALIVEs最好是片狀的。他們有時可能會導致問題,而不是解決問題 - 我只是不記得爲什麼或來源是什麼 - 但謹慎使用。 –

14

我認爲這是一個經常出現的問題。這可能是爲什麼MSDN文檔確實給一個好的答案,這個 - 看看從那裏Socket.Connected

報價:

connected屬性獲取套接字的連接狀態的 最後一個I/O操作。當它返回false時,Socket不是連接就是 ,或者不再連接。

連接屬性的值反映了最近操作時連接 的狀態。如果您需要確定連接的當前狀態 ,請發送無阻塞的零字節 發送呼叫。如果調用成功返回或拋出WAEWOULDBLOCK錯誤代碼(10035),那麼套接字仍然連接;否則, 插座不再連接。

與此示例代碼:

// .Connect throws an exception if unsuccessful 
client.Connect(anEndPoint); 

// This is how you can determine whether a socket is still connected. 
bool blockingState = client.Blocking; 
try 
{ 
    byte [] tmp = new byte[1]; 

    client.Blocking = false; 
    client.Send(tmp, 0, 0); 
    Console.WriteLine("Connected!"); 
} 
catch (SocketException e) 
{ 
    // 10035 == WSAEWOULDBLOCK 
    if (e.NativeErrorCode.Equals(10035)) 
     Console.WriteLine("Still Connected, but the Send would block"); 
    else 
    { 
     Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode); 
    } 
} 
finally 
{ 
    client.Blocking = blockingState; 
} 

Console.WriteLine("Connected: {0}", client.Connected); 

和延期法的直線前進motification:

public static bool IsConnected(this Socket client) 
{ 
    // This is how you can determine whether a socket is still connected. 
    bool blockingState = client.Blocking; 
    try 
    { 
     byte [] tmp = new byte[1]; 

     client.Blocking = false; 
     client.Send(tmp, 0, 0); 
     return true; 
    } 
    catch (SocketException e) 
    { 
     // 10035 == WSAEWOULDBLOCK 
     if (e.NativeErrorCode.Equals(10035)) 
      return true; 
     else 
     { 
      return false; 
     } 
    } 
    finally 
    { 
     client.Blocking = blockingState; 
    } 
} 
+0

如果我讀了這個權利,這是否意味着(基本上)通過執行Send(new byte [0]),Send方法等待底層的'ACK'作爲對發送包的響應而到達? – Alxandr

+0

@CarstenKönig:謝謝你的回答。它看起來很有前途,但恐怕不行。我編輯了我的帖子來解釋這個解決方案的問題。 – mj82

0

如果您鍵入以下它將是正確的工作:

byte[] tmp = new byte[] {0}; 
...    
client.Send(tmp, 1, 0); 
... 
5

這是一個很老的線程,但它是f IRST SO職位,來到了,當我搜索了這個問題,我發現了一個更有效的解決方案別的地方,所以我想我會發佈一個鏈接,以幫助其他人在未來:

https://social.msdn.microsoft.com/Forums/en-US/c857cad5-2eb6-4b6c-b0b5-7f4ce320c5cd/c-how-to-determine-if-a-tcpclient-has-been-disconnected?forum=netfxnetcom&prof=required

ElFenix發佈這個工作適合我的答案:

// Detect if client disconnected 
if (tcp.Client.Poll(0, SelectMode.SelectRead)) 
{ 
    byte[] buff = new byte[1]; 
    if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0) 
    { 
    // Client disconnected 
    bClosed = true; 
    } 
} 
0

以防萬一其他人需要一些簡單而有效的東西。

這是我想出了

  while (true) 
      { 

       string s = null; 
       DateTime readLag = DateTime.Now; 
       try 
       { 
        s = streamIn.ReadLine(); 
       } 
       catch (Exception ex) 
       { 
        SocketException sockEx = ex.InnerException as SocketException; 
        if (sockEx != null) 
        { 
         OnDebug("(" + sockEx.NativeErrorCode + ") Exception = " + ex); 
        } 
        else 
        { 
         OnDebug("Not SockEx :" + ex); 
        } 
       } 



       if (enableLog) OnDebug(s); 
       if (s == null || s == "") 
       { 
        if (readLag.AddSeconds(.9) > DateTime.Now) 
        { 
         break; 
        } 
       } 
       else 
       { 
        CommandParser(s); 
       } 
      } 

我得到的是每一個讀取失敗,但會阻止時本機錯誤10035什麼的代碼。

當連接被丟棄時,我立即得到沒有數據的回報。

將readLag.AddSeconds()設置爲稍低於我的讀取超時時間會給我一個相當不錯的主意,即時間從未過去,並且我沒有數據。哪些不應該發生。

不久,當這個標準彈出時,我剛剛退出循環,線程結束。

希望這可以幫助其他任何人。