开发者

How to test for a broken connection of TCPClient after being connected?

开发者 https://www.devze.com 2023-04-10 20:37 出处:网络
I\'ve been fighting with one problem for a whole 3 days I can\'t find any solution, please help :) I work with Visual Studio 2010 and C# language.

I've been fighting with one problem for a whole 3 days I can't find any solution, please help :)

I work with Visual Studio 2010 and C# language.

I have a device working like a server, that sends some data in a very irregular periods of time (not possible to define any read timeout).

I wrote a TCP client to connect to that server and read data. It works OK, however when something is wrong with the network and server becomes unavailable (e.g. when I plug out the network cable from my computer), it takes about 10 seconds for application to "notice" there is no connection to the server and throw an exception. 开发者_JS百科(I don't know why exactly 10 seconds? Where it's defined? Can I change it?)

I want to react faster - let say after one second after connection broken.

Googling for answer however doesn't provide me any working solution.

The test code is below, I try to make it on 2 threads: one is reading data, the second one is looking for connection status and should alarm me when it's broken. It's not working neither for TCPClient nor Socket class. I've tried to read / write some data with tcpClient.SendTimeout = 100; and stream.WriteTimeout = 100; but it doesn't seem to work.

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:

@carsten's answer: although it looks promising, this solution do not work...

I made the simplest test application for that:

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

The results are following, it displays:

Connected!

Connected: True

plus my order number every one second. When I disconnect network cable, it takes 8 seconds to start printing:

Disonnected: error code 10054!

Connected: False

so by 8 seconds I'm not aware that the connection is lost. It looks like pinging is the best option here, yet I'll test another solutions.


I think this is a question that often comes around. This might be why MSDN docs really give a good answer to this - see Socket.Connected

Quote from there:

The Connected property gets the connection state of the Socket as of the last I/O operation. When it returns false, the Socket was either never connected, or is no longer connected.

The value of the Connected property reflects the state of the connection as of the most recent operation. If you need to determine the current state of the connection, make a nonblocking, zero-byte Send call. If the call returns successfully or throws a WAEWOULDBLOCK error code (10035), then the socket is still connected; otherwise, the socket is no longer connected.

with this sample code:

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

and the straight forward motification for an extension-method:

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


This is a very old thread, but it's the first SO post that came up when I searched for this question and I found a more useful solution somewhere else, so I thought I'd post a link to it to help others in the future:

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 posted this answer that worked for me:

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


Simple answer. You can't. Tcp is made in a way which doesn't allow this. However, the normal way to achieve this is to send ping's with shorter interval than messages. So, say, that whenever you get a message from the server, you start a clock that count down 1 min, then you send a "ping" command (if you haven't received a new message in between). If you don't receive a response to your "ping" within 30 seconds, you conclude that the connection is broken.

Theoretically, you should be able to do this on a package-level (tcp sends ACK which you should be able to check for), but I don't know if that's possible in C#, or any programming language for that matter, or if you need to do that in firmware/hardware.


it will be correct and working if you type the following:

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


Just in case anyone else needs something simple and effective.

This is the code I came up with

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

What I got was native error 10035 every time a read failed but would block.

When the connection was trashed, I instantly got a return with no data.

Setting the readLag.AddSeconds() to just below my read timeout would give me a pretty good idea that the time never elapsed, and I got no data. Which should not happen.

Soon as this criteria popped up, I just kick out of the loop and the thread ends.

Hope this helps anyone else out there.


There is a socket option called SO_KEEPALIVE from the oginal unix implemetnation that has the TCP protocol send occasional probes to make sure the other end of the pipe is still listening. It can be set along with how often the probes are set via Socket.IOControl

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号