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

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

у меня есть устройство, работающее как сервер, который отправляет некоторые данные в очень нерегулярные периоды времени (невозможно определить тайм-аут чтения).
Я написал TCP-клиент для подключения к этому серверу и чтения данных. Он работает нормально, однако, когда что-то не так с сетью и сервером становится недоступным (например, когда я подключите сетевой кабель от моего компьютера), требуется около 10 секунд для приложения, чтобы "заметить", что нет подключения к серверу и выбросить исключение. (Я не знаю, почему именно 10 секунд? Где это определено? Могу я это изменить?)
Я хочу реагировать быстрее-скажем, через секунду после разрыва соединения.
Googling для ответа, однако, не предоставляет мне никакого рабочего решения.

тестовый код ниже, я пытаюсь сделать его на 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:
ответы @Карстена: хотя это выглядит многообещающим, это решение не работает...
Я сделал самое простое тестовое приложение для этого:

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 секунд:

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

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

6 ответов


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

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


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

цитата оттуда:

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

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

это очень старый поток, но это первый пост 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;
  }
}

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


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

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

Как только эти критерии выскочили, я просто выхожу из цикла, и поток заканчивается.

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