2013-06-14 16 views
41

Ho un TcpClient che uso per inviare dati a un listener su un computer remoto. Il computer remoto a volte si accende e talvolta si spegne. Per questo motivo, TcpClient non riesce a connettersi spesso. Voglio che il TcpClient scada dopo un secondo, quindi non ci vuole molto tempo quando non riesce a connettersi al computer remoto. Attualmente, utilizzo questo codice per TcpClient:Come impostare il timeout per un 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 
} 

Questo funziona abbastanza bene per la gestione del compito. Lo invia se possibile e rileva l'eccezione se non riesce a connettersi al computer remoto. Tuttavia, quando non riesce a connettersi, occorrono da dieci a quindici secondi per generare un'eccezione. Ho bisogno che scada tra un secondo circa? Come cambierei il time out?

risposta

57

È necessario utilizzare il metodo asincrono BeginConnect di TcpClient invece di tentare di connettersi in modo sincrono, ovvero ciò che fa il costruttore. Qualcosa di simile a questo:

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

qual è il punto di usare asincrono collegano, e "sincronizzare" indietro con aspettare? Voglio dire, al momento sto cercando di capire come implementare il timeout con lettura asincrona, ma la soluzione non disabilita completamente la progettazione asincrona. dovrebbe utilizzare timeout di socket o token di cancellazione o qualcosa del genere. altrimenti, usa solo connetti/leggi invece ... – RoeeK

+3

@RoeeK: Il punto è quello che dice la domanda: selezionare a livello di programmazione un timeout arbitrario per il tentativo di connessione. Questo non è un esempio su come fare un IO asincrono. – Jon

+5

@RoeeK: Il punto fondamentale di questa domanda è che 'TcpClient' non offre una funzione di sincronizzazione con un timeout configurabile, che è una delle soluzioni proposte. Questa è una soluzione alternativa per abilitarlo. Non sono sicuro di cos'altro dire senza ripetermi. – Jon

6

Una cosa da prendere nota di è che è possibile per la chiamata BeginConnect a fallire prima del timeout. Questo può accadere se stai tentando una connessione locale. Ecco una versione modificata del codice di 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

Le risposte di cui sopra non riguardano come gestire in modo pulito una connessione scaduta. Chiamando TcpClient.EndConnect, chiudendo una connessione che riesce ma dopo il timeout e eliminando TcpClient.

Potrebbe essere eccessivo ma questo funziona per me.

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

Grazie per il codice elaborato. È possibile generare la SocketException se la chiamata di connessione fallisce prima del timeout? – Macke

+0

Già dovrebbe. WaitOne verrà rilasciato quando la chiamata di connessione viene completata (con successo o in altro modo) o trascorre il timeout, a seconda dell'evento che si verifica per primo. Il controllo per! Client.Connected solleverà l'eccezione se la connessione "fallito veloce". – Adster

42

partire con .NET 4.5, TcpClient ha un metodo fresco ConnectAsync che possiamo usare in questo modo, quindi è ora abbastanza facile:

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

Ulteriori vantaggi in ConnectAsync è che Task.Wait può accettare un Termine di Cancellazione per interrompere immediatamente in caso di necessità anche prima del timeout. –

+0

. Wait bloccherà in modo sincrono, rimuovendo qualsiasi vantaggio dalla porzione "Async". https://stackoverflow.com/a/43237063/613620 è una migliore implementazione completamente asincrona. –

+2

@TimP. dove hai visto la parola "asincrona" nella domanda? –

4

Un'altra alternativa utilizzando https://stackoverflow.com/a/25684549/3975786:

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) 
{ 
    ... 
} 
+0

Questa è l'implementazione corretta completamente asincrona. –

+0

Perché non è possibile usare un 'Task.Delay' per creare un'attività che termina dopo un certo tempo invece di usare' DeleteTokenSource/TaskCompletionSource' per fornire il ritardo? (Ho provato e blocca, ma non capisco perché) – MondKin

2

Imposta la proprietà ReadTimeout o WriteTimeout su NetworkStream per letture/scritture sincrone. Codice Aggiornamento del PO:

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 
} 
1

Ecco un miglioramento codice basato su mcandal soluzione. Aggiunto eccezione cattura alcuna eccezione generata dal compito client.ConnectAsync (es: SocketException quando il server non è raggiungibile)

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) 
{ 
    ... 
}