web-dev-qa-db-fra.com

Comment définir le délai d'attente pour un TcpClient?

J'ai un TcpClient que j'utilise pour envoyer des données à un écouteur sur un ordinateur distant. L'ordinateur distant sera parfois allumé et parfois éteint. De ce fait, le TcpClient ne parviendra pas souvent à se connecter. Je veux que le TcpClient expire au bout d'une seconde, de sorte que la connexion à l'ordinateur distant ne prend pas beaucoup de temps. Actuellement, j'utilise ce code pour le 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
}

Cela fonctionne assez bien pour gérer la tâche. Il l'envoie s'il le peut et intercepte l'exception s'il ne peut pas se connecter à l'ordinateur distant. Cependant, lorsqu'il ne peut pas se connecter, il faut dix à quinze secondes pour lever l'exception. J'ai besoin que ça passe dans environ une seconde? Comment pourrais-je changer le temps d'arrêt?

54
msbg

Vous devez utiliser la méthode async BeginConnect de TcpClient au lieu de tenter de vous connecter de manière synchrone, comme le fait le constructeur. Quelque chose comme ça:

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

À partir de .NET 4.5, TcpClient a une méthode cool ConnectAsync que nous pouvons utiliser comme ceci, donc c’est maintenant assez simple:

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

Une autre alternative utilisant 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

Les réponses ci-dessus n'indiquent pas comment gérer proprement une connexion qui a expiré. Appelez TcpClient.EndConnect, fermez une connexion qui aboutit mais après le délai d'attente et supprimez TcpClient.

C'est peut-être exagéré, mais cela fonctionne pour moi.

    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

Il convient de noter qu’il est possible que l’appel BeginConnect échoue avant l’expiration du délai. Cela peut arriver si vous essayez une connexion locale. Voici une version modifiée du code 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);
7
NeilMacMullen

Définissez les propriétés ReadTimeout ou WriteTimeout sur NetworkStream pour les lectures/écritures synchrones. Mise à jour du code de l'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
}
2
mcmcmc

Voici une amélioration de code basée sur mcandal solution. Ajout d’une capture d’exception pour toute exception générée à partir de la tâche client.ConnectAsync (par exemple: SocketException lorsque le serveur est inaccessible)

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

Si vous utilisez async & wait et souhaitez utiliser un délai d’attente sans blocage, une autre solution plus simple tirée de la réponse fournie par mcandal consiste à exécuter la connexion sur un fil d’arrière-plan et à attendre le résultat. Par exemple:

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.

Voir l'article Task.Wait MSDN pour plus d'informations et d'autres exemples.

0
Bob Bryan

Comme mentionné Simon Mourier, il est possible d'utiliser la méthode de ConnectAsync de TcpClient avec Task de plus et d'arrêter l'opération le plus rapidement possible.
Par exemple:

// ...
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