Как установить тайм-аут для TcpClient?
У меня есть TcpClient, который я использую для отправки данных слушателю на удаленном компьютере. Удаленный компьютер иногда включается и иногда выключается. Из-за этого TcpClient не сможет подключаться часто. Я хочу, чтобы TcpClient перегрелся через одну секунду, поэтому не требуется много времени, когда он не может подключиться к удаленному компьютеру. В настоящее время я использую этот код для TcpClient:
try
{
TcpClient client = new TcpClient("remotehost", this.Port);
client.SendTimeout = 1000;
Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
data = new Byte[512];
Int32 bytes = stream.Read(data, 0, data.Length);
this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);
stream.Close();
client.Close();
FireSentEvent(); //Notifies of success
}
catch (Exception ex)
{
FireFailedEvent(ex); //Notifies of failure
}
Это работает достаточно хорошо для обработки задачи. Он отправляет его, если может, и получает исключение, если он не может подключиться к удаленному компьютеру. Однако, когда он не может подключиться, для исключения исключения требуется от десяти до пятнадцати секунд. Мне нужно это, чтобы тайм-аут в течение одной секунды? Как изменить время ожидания?
Ответы
Ответ 1
Вам нужно будет использовать метод async BeginConnect
TcpClient
вместо того, чтобы пытаться подключиться синхронно, что и делает конструктор. Что-то вроде этого:
var client = new TcpClient();
var result = client.BeginConnect("remotehost", this.Port, null, null);
var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
if (!success)
{
throw new Exception("Failed to connect.");
}
// we have connected
client.EndConnect(result);
Ответ 2
Начиная с .NET 4.5, TcpClient имеет классный ConnectAsync метод, который мы можем использовать таким образом, поэтому теперь он довольно прост:
var client = new TcpClient();
if (!client.ConnectAsync("remotehost", remotePort).Wait(1000))
{
// connection failure
}
Ответ 3
Другая альтернатива, использующая fooobar.com/questions/140036/...:
var timeOut = TimeSpan.FromSeconds(5);
var cancellationCompletionSource = new TaskCompletionSource<bool>();
try
{
using (var cts = new CancellationTokenSource(timeOut))
{
using (var client = new TcpClient())
{
var task = client.ConnectAsync(hostUri, portNumber);
using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cts.Token);
}
}
...
}
}
}
catch(OperationCanceledException)
{
...
}
Ответ 4
Следует обратить внимание на то, что вызов BeginConnect может завершиться с ошибкой до истечения времени ожидания. Это может произойти, если вы пытаетесь подключиться к локальной сети. Здесь изменена версия кода Jon...
var client = new TcpClient();
var result = client.BeginConnect("remotehost", Port, null, null);
result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(1));
if (!client.Connected)
{
throw new Exception("Failed to connect.");
}
// we have connected
client.EndConnect(result);
Ответ 5
В приведенных выше ответах не рассматривается вопрос о том, как правильно разобраться с временем установления соединения. Вызов TcpClient.EndConnect, закрытие соединения, которое завершается успешно, но после таймаута и утилизации TcpClient.
Это может быть излишним, но это работает для меня.
private class State
{
public TcpClient Client { get; set; }
public bool Success { get; set; }
}
public TcpClient Connect(string hostName, int port, int timeout)
{
var client = new TcpClient();
//when the connection completes before the timeout it will cause a race
//we want EndConnect to always treat the connection as successful if it wins
var state = new State { Client = client, Success = true };
IAsyncResult ar = client.BeginConnect(hostName, port, EndConnect, state);
state.Success = ar.AsyncWaitHandle.WaitOne(timeout, false);
if (!state.Success || !client.Connected)
throw new Exception("Failed to connect.");
return client;
}
void EndConnect(IAsyncResult ar)
{
var state = (State)ar.AsyncState;
TcpClient client = state.Client;
try
{
client.EndConnect(ar);
}
catch { }
if (client.Connected && state.Success)
return;
client.Close();
}
Ответ 6
Установите свойство ReadTimeout или WriteTimeout в NetworkStream для синхронного чтения/записи.
Обновление кода OP:
try
{
TcpClient client = new TcpClient("remotehost", this.Port);
Byte[] data = System.Text.Encoding.Unicode.GetBytes(this.Message);
NetworkStream stream = client.GetStream();
stream.WriteTimeout = 1000; // <------- 1 second timeout
stream.ReadTimeout = 1000; // <------- 1 second timeout
stream.Write(data, 0, data.Length);
data = new Byte[512];
Int32 bytes = stream.Read(data, 0, data.Length);
this.Response = System.Text.Encoding.Unicode.GetString(data, 0, bytes);
stream.Close();
client.Close();
FireSentEvent(); //Notifies of success
}
catch (Exception ex)
{
// Throws IOException on stream read/write timeout
FireFailedEvent(ex); //Notifies of failure
}
Ответ 7
Вот улучшение кода, основанное на решении mcandal. Добавлено исключение ловли для любого исключения из генерируемой client.ConnectAsync
задачи (например: SocketException, когда сервер недоступен)
var timeOut = TimeSpan.FromSeconds(5);
var cancellationCompletionSource = new TaskCompletionSource<bool>();
try
{
using (var cts = new CancellationTokenSource(timeOut))
{
using (var client = new TcpClient())
{
var task = client.ConnectAsync(hostUri, portNumber);
using (cts.Token.Register(() => cancellationCompletionSource.TrySetResult(true)))
{
if (task != await Task.WhenAny(task, cancellationCompletionSource.Task))
{
throw new OperationCanceledException(cts.Token);
}
// throw exception inside 'task' (if any)
if (task.Exception?.InnerException != null)
{
throw task.Exception.InnerException;
}
}
...
}
}
}
catch (OperationCanceledException operationCanceledEx)
{
// connection timeout
...
}
catch (SocketException socketEx)
{
...
}
catch (Exception ex)
{
...
}
Ответ 8
Если вы используете асинхронное ожидание и хотите использовать тайм-аут без блокировки, то альтернативный и более простой подход из ответа, предоставленного mcandal, состоит в том, чтобы выполнить соединение в фоновом потоке и дождаться результата. Например:
Task<bool> t = Task.Run(() => client.ConnectAsync(ipAddr, port).Wait(1000));
await t;
if (!t.Result)
{
Console.WriteLine("Connect timed out");
return; // Set/return an error code or throw here.
}
// Successful Connection - if we get to here.
См. Статью MSDN Task.Wait для получения дополнительной информации и других примеров.
Ответ 9
Как Саймон Mourier упоминалось, можно использовать ConnectAsync
метод TcpClient с Task
в дополнение и остановить работу как можно скорее.
Например:
// ...
client = new TcpClient(); // Initialization of TcpClient
CancellationToken ct = new CancellationToken(); // Required for "*.Task()" method
if (client.ConnectAsync(this.ip, this.port).Wait(1000, ct)) // Connect with timeout as 1 second
{
// ... transfer
if (client != null) {
client.Close(); // Close connection and dipose TcpClient object
Console.WriteLine("Success");
ct.ThrowIfCancellationRequested(); // Stop asynchronous operation after successull connection(...and transfer(in needed))
}
}
else
{
Console.WriteLine("Connetion timed out");
}
// ...