2011-10-04 34 views
9

Tôi đã chiến đấu với một vấn đề trong cả 3 ngày tôi không thể tìm thấy giải pháp nào, xin hãy giúp :)
Tôi làm việc với ngôn ngữ Visual Studio 2010 và C#.Làm cách nào để kiểm tra kết nối bị hỏng của TCPClient sau khi được kết nối?

Tôi có một thiết bị hoạt động như một máy chủ, gửi một số dữ liệu trong một khoảng thời gian rất bất thường (không thể xác định thời gian chờ đọc).
Tôi đã viết một máy khách TCP để kết nối với máy chủ đó và đọc dữ liệu. Nó hoạt động OK, tuy nhiên khi có sự cố với mạng và máy chủ không khả dụng (ví dụ khi tôi cắm cáp mạng từ máy tính của tôi), mất khoảng 10 giây để ứng dụng "thông báo" không có kết nối với máy chủ và ném một ngoại lệ. (Tôi không biết tại sao chính xác 10 giây? Nó được xác định ở đâu? Tôi có thể thay đổi nó không?)
Tôi muốn phản ứng nhanh hơn - giả sử sau một giây sau khi kết nối bị hỏng.
Googling cho câu trả lời tuy nhiên không cung cấp cho tôi bất kỳ giải pháp làm việc.

Mã thử nghiệm dưới đây, tôi cố gắng làm cho nó trên 2 chủ đề: một là đọc dữ liệu, thứ hai là tìm kiếm trạng thái kết nối và nên báo động cho tôi khi nó bị hỏng. Nó không hoạt động cho cả lớp TCPClient lẫn Socket. Tôi đã cố gắng để đọc/ghi một số dữ liệu với tcpClient.SendTimeout = 100;stream.WriteTimeout = 100; nhưng nó dường như không hoạt động.

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 
      } 
     } 
    } 
} 

Edit:
@ câu trả lời Carsten của: mặc dù có vẻ đầy hứa hẹn, giải pháp này không làm việc ...
tôi đã ứng dụng thử nghiệm đơn giản nhất cho rằng:

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); 
      } 
     } 
    } 
} 

Kết quả đang theo dõi, hiển thị:

Đã kết nối!
Đã kết nối: True

cộng với số đơn đặt hàng của tôi sau mỗi một giây. Khi tôi ngắt kết nối cáp mạng, phải mất 8 giây để bắt đầu in:

Đã ngắt kết nối: mã lỗi 10054!
Đã kết nối: False

do đó 8 giây Tôi không biết rằng kết nối bị mất. Có vẻ như ping là lựa chọn tốt nhất ở đây, nhưng tôi sẽ thử nghiệm một giải pháp khác.

+0

Có thể trùng lặp: [Làm cách nào tôi có thể kiểm tra xem ổ cắm (TCP) có được ngắt kết nối trong C#?] Hay không (http://stackoverflow.com/questions/515458/how-can-i-check-whether-a -tcp-socket-is-disconnect-in-c) – ladenedge

+2

@ladenedge Có thể trùng lặp ... ngoại trừ việc này có nhiều mã ví dụ hơn để giúp các ppl khác học theo cách khác.Tôi biết bình luận của bạn là cũ, tuy nhiên nó là bực bội khi những người như bạn làm cho vô dụng và khuyến khích các ý kiến ​​không hiệu quả. – Euthyphro

Trả lời

6

Câu trả lời đơn giản. Bạn không thể. Tcp được thực hiện theo cách không cho phép điều này. Tuy nhiên, cách thông thường để đạt được điều này là gửi ping với khoảng thời gian ngắn hơn tin nhắn. Vì vậy, nói rằng, bất cứ khi nào bạn nhận được một tin nhắn từ máy chủ, bạn bắt đầu một đồng hồ đếm ngược 1 phút, sau đó bạn gửi một lệnh "ping" (nếu bạn chưa nhận được một tin nhắn mới ở giữa). Nếu bạn không nhận được phản hồi cho "ping" của bạn trong vòng 30 giây, bạn kết luận rằng kết nối bị hỏng. Về mặt lý thuyết, bạn sẽ có thể thực hiện điều này ở cấp gói (tcp gửi ACK mà bạn có thể kiểm tra), nhưng tôi không biết điều đó có thể thực hiện được trong C# hay bất kỳ ngôn ngữ lập trình nào cho điều đó hay không. vấn đề, hoặc nếu bạn cần làm điều đó trong phần vững/phần cứng.

+0

Cảm ơn bạn đã trả lời. Cho đến nay nó trông giống như ping là lựa chọn tốt nhất, tuy nhiên khi đào cho câu trả lời tôi đã tìm thấy một số trang mà nó đã được chỉ là một giải pháp xấu - không biết tại sao. – mj82

+0

Tôi nghĩ rằng bạn đang thiếu hiểu biết một cái gì đó, hoặc tôi (đó là rất nhiều có thể btw xD). Theo tôi hiểu, những gì bạn vừa cập nhật bài đăng của mình lên ** là ** ping, chỉ một cách thông minh hơn là tự mình thực hiện. Plus, 8 giây cho đến khi bạn phát hiện ra một ngắt kết nối không có vẻ xấu cả. Ví dụ: máy chủ IRC, sử dụng tối đa 5 phút giữa các lệnh ping (!). – Alxandr

+0

Tôi đã cập nhật bài đăng của mình để hiển thị, giải pháp do Carsten König đề xuất không hoạt động. Nó cố gắng làm 'tcpClient.Client.Send (...)' mỗi giây, vì vậy tôi đã được mong đợi rằng sau khi cáp bị ngắt kết nối nó sẽ ném ngoại lệ hoặc một cái gì đó sau một giây - nó không. Mặt khác, ping "bình thường" cách, với lớp Ping, sẽ làm việc. Vì vậy, bài viết cập nhật của tôi không bằng pinging :) – mj82

0

Có một tùy chọn ổ cắm được gọi là SO_KEEPALIVE từ quá trình thực hiện unix unix có giao thức TCP gửi các đầu dò không thường xuyên để đảm bảo đầu kia của đường ống vẫn đang lắng nghe. Nó có thể được thiết lập cùng với tần suất các đầu dò được thiết lập thông qua Socket.IOControl

+0

Eh, KEEPALIVEs là flaky lúc tốt nhất. Họ đôi khi có thể gây ra vấn đề hơn là họ giải quyết - Tôi chỉ không thể nhớ tại sao hoặc nguồn gốc là gì - nhưng sử dụng cẩn thận. –

14

Tôi nghĩ đây là một câu hỏi thường xảy ra. Đây có thể là lý do tại sao MSDN tài liệu thực sự đưa ra một câu trả lời tốt cho điều này - xem Socket.Connected

Trích từ đó:

Thuộc tính kết nối được trạng thái kết nối của Socket như của cuối cùng I/O hoạt động. Khi nó trả về false, Socket là không bao giờ được kết nối hoặc không còn kết nối nữa.

Giá trị của thuộc tính được kết nối phản ánh trạng thái của kết nối là của hoạt động gần đây nhất. Nếu bạn cần xác định trạng thái hiện tại của kết nối, hãy thực hiện một lệnh không chặn, không byte Gửi cuộc gọi. Nếu cuộc gọi trả về thành công hoặc ném một mã lỗi WAEWOULDBLOCK (10035), thì ổ cắm vẫn được kết nối; nếu không, ổ cắm không còn được kết nối.

với mã này mẫu:

// .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); 

và motification thẳng về phía trước cho một phần mở rộng-phương pháp:

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

Nếu tôi đọc quyền này, điều đó có nghĩa là (về cơ bản) bằng cách thực hiện 'Gửi (byte mới [0])' phương thức Gửi đợi cho 'ACK' cơ bản đến để trả lời gói được gửi đi? – Alxandr

+0

@Carsten König: Cảm ơn bạn đã trả lời. Có vẻ đầy hứa hẹn, nhưng tôi sợ không làm việc. Tôi đã chỉnh sửa bài đăng của mình để giải thích sự cố với giải pháp này. – mj82

0

nó sẽ được chính xác và làm việc nếu bạn gõ như sau:

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

Đây là một chủ đề rất cũ, nhưng đó là IRST SO bài mà đã đưa ra khi tôi đã tìm kiếm cho câu hỏi này và tôi tìm thấy một giải pháp hữu ích hơn ở một nơi khác, vì vậy tôi nghĩ rằng tôi muốn gửi một liên kết đến nó để giúp đỡ người khác trong tương lai:

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 đã đăng câu trả lời này có hiệu quả đối với tôi:

// 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

Chỉ trong trường hợp bất kỳ ai khác cần điều gì đó đơn giản và hiệu quả.

Đây là mã tôi đã đưa ra

  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); 
       } 
      } 

Những gì tôi nhận được là mẹ đẻ lỗi 10035 mỗi khi đọc thất bại nhưng sẽ chặn.

Khi kết nối được chuyển vào thùng rác, tôi ngay lập tức nhận được lợi tức không có dữ liệu.

Đặt readLag.AddSeconds() xuống ngay dưới thời gian chờ đã đọc của tôi sẽ cho tôi một ý tưởng hay rằng thời gian không bao giờ trôi qua và tôi không có dữ liệu. Điều gì không nên xảy ra.

Ngay sau khi tiêu chí này xuất hiện, tôi chỉ cần loại bỏ vòng lặp và chuỗi kết thúc.

Hy vọng điều này sẽ giúp bất kỳ ai khác ngoài đó.

Các vấn đề liên quan