it-swarm.com.de

Wie kann ich das Timeout für einen TcpClient einstellen?

Ich habe einen TcpClient, den ich zum Senden von Daten an einen Listener auf einem Remote-Computer verwende. Der Remote-Computer ist manchmal eingeschaltet und manchmal ausgeschaltet. Aus diesem Grund kann der TcpClient häufig keine Verbindung herstellen. Ich möchte, dass der TcpClient nach einer Sekunde abläuft, sodass es nicht lange dauert, wenn er keine Verbindung zum Remote-Computer herstellen kann. Derzeit verwende ich diesen Code für den 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
}

Dies funktioniert gut genug, um die Aufgabe zu erledigen. Es sendet es, wenn dies möglich ist, und fängt die Ausnahme ab, wenn keine Verbindung zum Remote-Computer hergestellt werden kann. Wenn keine Verbindung hergestellt werden kann, dauert es zehn bis fünfzehn Sekunden, bis die Ausnahme ausgelöst wird. Ich brauche es für eine Auszeit in etwa einer Sekunde? Wie würde ich die Timeout-Zeit ändern?

54
msbg

Sie müssten die async BeginConnect - Methode von TcpClient verwenden, anstatt zu versuchen, die Verbindung synchron herzustellen. Dies tut der Konstruktor. Etwas wie das:

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

Beginnend mit .NET 4.5 verfügt TcpClient über eine coole ConnectAsync - Methode, die wir wie folgt verwenden können, sodass es jetzt ziemlich einfach ist:

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

Eine andere Alternative mit 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)
{
    ...
}
10
mcandal

In den obigen Antworten wird nicht beschrieben, wie Sie mit einer Verbindung, die eine Zeitüberschreitung festgestellt hat, sauber umgehen. TcpClient.EndConnect aufrufen, eine nach dem Timeout erfolgreiche Verbindung schließen und den TcpClient verwerfen.

Es mag übertrieben sein, aber das funktioniert für mich.

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

Zu beachten ist, dass der BeginConnect-Aufruf möglicherweise vor Ablauf des Timeouts fehlschlägt. Dies kann passieren, wenn Sie eine lokale Verbindung versuchen. Hier ist eine modifizierte Version von Jons Code ...

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

Legen Sie für synchrones Lesen/Schreiben die Eigenschaft ReadTimeout oder WriteTimeout im NetworkStream fest. OP-Code aktualisieren:

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
}
2
mcmcmc

Hier ist eine Codeverbesserung basierend auf mcandal solution. Es wurde ein Ausnahmefang für jede durch die client.ConnectAsync-Task generierte Ausnahme hinzugefügt (z. B. SocketException, wenn der Server nicht erreichbar ist).

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

Wenn Sie async & await verwenden und ein Timeout ohne Blockierung verwenden möchten, besteht ein alternativer und einfacherer Ansatz aus der Antwort von mcandal darin, die Verbindung in einem Hintergrund-Thread auszuführen und auf das Ergebnis zu warten. Zum Beispiel:

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.

Weitere Informationen und andere Beispiele finden Sie im Task.Wait MSDN-Artikel .

0
Bob Bryan

Wie Simon Mourier erwähnt wurde, ist es möglich, die Methode ConnectAsync TcpClient mit Task zusätzlich zu verwenden und den Vorgang so schnell wie möglich zu stoppen.
Zum Beispiel:

// ...
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");
}
// ...
0
V.7