Как проверить на разрыв соединения TCPClient после подключения? - PullRequest
9 голосов
/ 04 октября 2011

Я боролся с одной проблемой целых 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
            }
        }
    }
}

Edit:
Ответ @ 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);
            }
        }
    }
}

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

Connected!
Подключено: True

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

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

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

Ответы [ 6 ]

16 голосов
/ 04 октября 2011

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

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

Свойство Connected получает состояние подключенияСокета по состоянию на последнюю операцию ввода / вывода.Когда он возвращает 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;
   }
}
7 голосов
/ 04 февраля 2015

Это очень старая тема, но это первая 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;
  }
}
6 голосов
/ 04 октября 2011

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

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

1 голос
/ 13 февраля 2015

На всякий случай кому-то еще нужно что-то простое и эффективное.

Это код, который я придумал

            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 () чуть ниже моего тайм-аута чтения дала бы мне довольно хорошее представление о том, что время никогда не истекло, и я не получил данных.Что не должно происходить.

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

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

0 голосов
/ 04 июня 2013

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

byte[] tmp = new byte[] {0};
...             
client.Send(tmp, 1, 0);
...
0 голосов
/ 04 октября 2011

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

...