Подтвердить что ты не робот

Как установить тайм-аут для 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
}

Это работает достаточно хорошо для обработки задачи. Он отправляет его, если может, и получает исключение, если он не может подключиться к удаленному компьютеру. Однако, когда он не может подключиться, для исключения исключения требуется от десяти до пятнадцати секунд. Мне нужно это, чтобы тайм-аут в течение одной секунды? Как изменить время ожидания?

4b9b3361

Ответ 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");
}
// ...