Cómo establecer el tiempo de espera para un TcpClient?

Tengo un TcpClient que utilizo para enviar datos a un oyente en una computadora remota. La computadora remota a veces estará encendida y, a veces, apagada. Debido a esto, el TcpClient no podrá conectarse con frecuencia. Quiero que el TcpClient exceda el tiempo de espera después de un segundo, por lo que no lleva mucho tiempo cuando no se puede conectar a la computadora remota. Actualmente, uso este código para el 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 } 

Esto funciona lo suficientemente bien para manejar la tarea. Lo envía si puede, y atrapa la excepción si no se puede conectar a la computadora remota. Sin embargo, cuando no se puede conectar, toma de diez a quince segundos arrojar la excepción. Necesito tiempo de espera en alrededor de un segundo? ¿Cómo cambiaría el tiempo de espera?

BeginConnect utilizar el método asincrónico BeginConnect de TcpClient lugar de intentar conectarse de forma síncrona, que es lo que hace el constructor. Algo como esto:

 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); 

Comenzando con .NET 4.5, TcpClient tiene un método ConnectAsync genial que podemos usar así, por lo que ahora es bastante fácil:

 var client = new TcpClient(); if (!client.ConnectAsync("remotehost", remotePort).Wait(1000)) { // connection failure } 

Una cosa a tener en cuenta es que es posible que la llamada BeginConnect falle antes de que expire el tiempo de espera. Esto puede suceder si está intentando una conexión local. Aquí hay una versión modificada del código de 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); 

Las respuestas anteriores no cubren cómo lidiar limpiamente con una conexión que ha excedido el tiempo de espera. Llamando a TcpClient.EndConnect, cerrando una conexión que tiene éxito pero después del tiempo de espera, y desechando el TcpClient.

Puede ser exagerado, pero esto funciona para mí.

  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(); } 

Otra alternativa que usa https://stackoverflow.com/a/25684549/3975786 :

 var timeOut = TimeSpan.FromSeconds(5); var cancellationCompletionSource = new TaskCompletionSource(); 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) { ... } 

Establezca la propiedad ReadTimeout o WriteTimeout en NetworkStream para lecturas / escrituras sincrónicas. Actualizando el código de 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 } 

Aquí hay una mejora del código basada en la solución mcandal . client.ConnectAsync excepción agregada para cualquier excepción generada desde la tarea client.ConnectAsync (por ejemplo: SocketException cuando el servidor no está disponible)

 var timeOut = TimeSpan.FromSeconds(5); var cancellationCompletionSource = new TaskCompletionSource(); 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) { ... }