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?
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);
À 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
}
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)
{
...
}
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();
}
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);
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
}
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)
{
...
}
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.
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");
}
// ...