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