91 votes

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

J'ai un TcpClient que j'utilise pour envoyer des données à un listener sur un ordinateur distant. L'ordinateur distant est parfois allumé, parfois éteint. De ce fait, le TcpClient ne parvient pas toujours à se connecter. Je veux que le TcpClient se termine au bout d'une seconde, de façon à ce qu'il ne prenne pas trop de temps lorsqu'il n'arrive pas à se connecter à l'ordinateur distant. 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. Elle l'envoie si elle le peut, et attrape l'exception si elle ne peut pas se connecter à l'ordinateur distant. Cependant, lorsqu'elle ne peut pas se connecter, il lui faut dix à quinze secondes pour lancer l'exception. J'ai besoin d'un délai d'environ une seconde ? Comment puis-je modifier le délai d'expiration ?

105voto

Jon Points 194296

Vous devez utiliser la méthode asynchrone BeginConnect méthode de TcpClient au lieu de tenter de se connecter de manière synchrone, ce que 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);

104voto

Simon Mourier Points 49585

À partir de .NET 4.5, TcpClient dispose d'une fonction cool ConnectAsync que nous pouvons utiliser comme ceci, donc c'est maintenant assez facile :

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

22voto

mcandal Points 367

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

9voto

Adster Points 163

Les réponses ci-dessus ne couvrent pas la manière de traiter proprement une connexion qui a expiré. L'appel de TcpClient.EndConnect, la fermeture d'une connexion qui réussit mais après le délai d'attente, et la mise au rebut de TcpClient.

C'est peut-être exagéré, mais ça marche 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();
    }

8voto

NeilMacMullen Points 551

Il convient de noter qu'il est possible que l'appel BeginConnect échoue avant l'expiration du délai. Cela peut se produire si vous tentez 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);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X