2013-06-14 28 views
41

Mam TcpClient, którego używam do wysyłania danych do detektora na komputerze zdalnym. Komputer zdalny będzie czasem włączony, a czasami wyłączony. Z tego powodu TcpClient nie będzie się często łączyć. Chcę TcpClient do limitu czasu po jednej sekundzie, więc nie zajmuje dużo czasu, gdy nie może połączyć się z komputerem zdalnym. Obecnie używam tego kodu dla TcpClient:Jak ustawić limit czasu dla 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 
} 

Działa to wystarczająco dobrze do obsługi zadania. Wysyła go, jeśli może, i przechwytuje wyjątek, jeśli nie może połączyć się z komputerem zdalnym. Jednak gdy nie może się połączyć, odrzucić wyjątek trwa dziesięć do piętnastu sekund. Potrzebuję czasu, by zająć około sekundy? Jak zmienić limit czasu?

Odpowiedz

57

Musisz użyć metody asynchronicznej BeginConnect z TcpClient zamiast próbować połączyć się synchronicznie, czyli tego, co robi konstruktor. Coś takiego:

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

jaki jest sens korzystania z asynchroniczny połączyć i „synchronizować” go z powrotem czekać? Mam na myśli, obecnie próbuję zrozumieć, jak zaimplementować timeout z asynchronicznym odczytem, ​​ale rozwiązaniem nie jest całkowite wyłączenie projektu asynchronicznego. powinien używać limitów czasu na gniazda lub tokena anulowania lub coś w tym stylu. w przeciwnym razie użyj po prostu connect/read ... – RoeeK

+3

@RoeeK: Chodzi o to, co mówi pytanie: programowo wybierz dowolny czas oczekiwania na próbę połączenia. To nie jest przykład na wykonanie asynchronicznej operacji wejścia/wyjścia. – Jon

+5

@RoeeK: Cały punkt tego pytania jest taki, że 'TcpClient' nie oferuje funkcji synchronizacji połączenia z konfigurowalnym czasem oczekiwania, który jest jednym z twoich proponowanych rozwiązań. Jest to obejście umożliwiające jego włączenie. Nie jestem pewien, co jeszcze mogę powiedzieć bez powtarzania się. – Jon

6

Należy zwrócić uwagę na to, że wywołanie BeginConnect może się nie powieść przed upływem limitu czasu. Może się tak zdarzyć, jeśli próbujesz nawiązać połączenie lokalne. Oto zmodyfikowana wersja kodu Jona ...

 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

powyższych odpowiedzi nie wyczerpują jak czysto czynienia z połączeniem która wygasła. Wywołanie TcpClient.EndConnect, zamknięcie połączenia, które się powiedzie, ale po upływie limitu czasu, i usunięcie TcpClient.

Może to być przesada, ale działa to dla mnie.

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

Dzięki za opracowany kod. Czy jest możliwe wygenerowanie wyjątku SocketException, jeśli połączenie nie powiedzie się przed upływem limitu czasu? – Macke

+0

To już powinno. WaitOne zostanie zwolniony, gdy połączenie Connect zostanie zakończone (pomyślnie lub w inny sposób) lub upłynie limit czasu, w zależności od tego, co nastąpi wcześniej. Sprawdzanie dla! Client.Connected spowoduje zgłoszenie wyjątku, jeśli połączenie "szybko się nie powiedzie". – Adster

42

Począwszy od .NET 4.5, TcpClient ma fajne ConnectAsync metody, które możemy wykorzystać w ten sposób, więc teraz bardzo proste:

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

Dodatkową korzyścią w ConnectAsync jest to, że Task.Wait może zaakceptować Token Anulowania, aby natychmiast zatrzymać się w razie potrzeby, nawet przed upływem limitu czasu. –

+0

. Poczekaj synchronicznie, usuwając wszelkie korzyści z części "Asynchronizuj". https://stackoverflow.com/a/43237063/613620 to lepsza w pełni asynchroniczna implementacja. –

+2

@TimP. gdzie widziałeś pytanie "async" w pytaniu? –

4

Inną alternatywą użyciu 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

To jest poprawna, w pełni asynchroniczna implementacja. –

+0

Dlaczego nie można użyć zadania 'Task.Delay' w celu utworzenia zadania, które zakończy się po określonym czasie zamiast użycia' CancellationTokenSource/TaskCompletionSource' w celu dostarczenia opóźnienia? (Próbowałem i to blokuje, ale nie rozumiem dlaczego) – MondKin

2

Ustaw właściwość ReadTimeout lub WriteTimeout w NetworkStream dla synchronicznych odczytów/zapisów. Kod aktualizowania 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 
} 
1

Tutaj jest poprawa kodu na podstawie mcandal rozwiązania. Dodany wyjątek połowu żadnego wyjątku generowanego z zadania client.ConnectAsync (np: SocketException gdy serwer jest nieosiągalny)

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