Как проверить на разрыв соединения TCPClient после подключения?

Я боролся с одной проблемой целых 3 дня, я не могу найти решение, пожалуйста, помогите:)
Я работаю с Visual Studio 2010 и языком C#.

У меня есть устройство, работающее как сервер, которое отправляет некоторые данные в очень нерегулярные периоды времени (невозможно определить тайм-аут чтения).
Я написал 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
            }
        }
    }
}

Редактировать:
Ответ @ Carsten: хотя это выглядит многообещающе, это решение не работает...
Я сделал простейшее тестовое приложение для этого:

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

Результаты следующие, он отображает:

Связано!
Подключено: True

плюс мой номер заказа каждую секунду. Когда я отсоединяю сетевой кабель, для начала печати требуется 8 секунд:

Отключено: код ошибки 10054!
Подключено: Ложь

поэтому на 8 секунд я не в курсе, что связь потеряна. Похоже, что пинг является лучшим вариантом здесь, но я опробую другие решения.

6 ответов

Решение

Простой ответ Ты не можешь Tcp сделан таким образом, что не позволяет этого. Тем не менее, нормальный способ добиться этого - отправлять ping с более коротким интервалом, чем сообщения. Итак, скажем, что всякий раз, когда вы получаете сообщение от сервера, вы запускаете часы с обратным отсчетом в 1 минуту, а затем отправляете команду "ping" (если между вами не было получено новое сообщение). Если вы не получите ответ на свой "пинг" в течение 30 секунд, вы решаете, что соединение разорвано.

Теоретически, вы должны быть в состоянии сделать это на уровне пакета (tcp отправляет ACK, который вы должны иметь возможность проверить), но я не знаю, возможно ли это в C#, или на каком-либо другом языке программирования в этом отношении, или если вам нужно сделать это в прошивке / оборудовании.

Я думаю, что это вопрос, который часто возникает. Возможно, именно поэтому документы MSDN действительно дают на это хороший ответ - см. Socket.Connected.

Цитата оттуда:

Свойство Connected возвращает состояние соединения Socket с момента последней операции ввода-вывода. Когда он возвращает false, Socket либо никогда не был подключен, либо больше не подключен.

Значение свойства Connected отражает состояние соединения на момент последней операции. Если вам необходимо определить текущее состояние соединения, выполните неблокирующий нулевой байт отправляющий вызов. Если вызов успешно возвращается или выдает код ошибки 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);

и прямая мотивация для метода расширения:

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

Это очень старая тема, но это первая публикация 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;
  }
}

Это будет правильно и работает, если вы введете следующее:

byte[] tmp = new byte[] {0};
...             
client.Send(tmp, 1, 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() чуть ниже моего тайм-аута чтения даст мне довольно хорошее представление о том, что время никогда не истекло, и я не получил данных. Что не должно случиться.

Вскоре, когда этот критерий появился, я просто выхожу из цикла, и поток заканчивается.

Надеюсь, это поможет кому-то еще.

Существует опция сокета под названием SO_KEEPALIVE из первоначальной реализации Unix, в которой протокол TCP отправляет случайные зонды, чтобы убедиться, что другой конец канала все еще прослушивает. Это может быть установлено вместе с тем, как часто датчики устанавливаются через Socket.IOControl

Другие вопросы по тегам