2011-10-04 169 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()设置为稍低于我的读取超时时间会给我一个相当不错的主意,即时间从未过去,并且我没有数据。哪些不应该发生。

不久,当这个标准弹出时,我刚刚退出循环,线程结束。

希望这可以帮助其他任何人。