如何设置TcpClient的超时时间
本文关键字:TcpClient 超时 时间 设置 何设置 | 更新日期: 2023-09-27 18:18:24
我有一个TcpClient,我用它向远程计算机上的侦听器发送数据。远程计算机有时开,有时关。因此,TcpClient将经常连接失败。我希望TcpClient在一秒钟后超时,这样当它不能连接到远程计算机时就不会花费太多时间。目前,我对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
}
这对于处理任务来说足够好了。如果可以,它就发送它,如果不能连接到远程计算机,它就捕获异常。但是,当它无法连接时,需要10到15秒才能抛出异常。我需要它在一秒钟内停止?如何更改超时时间?
从。net 4.5开始,TcpClient有一个很酷的ConnectAsync方法,我们可以像这样使用它,所以现在它非常容易:
var client = new TcpClient();
if (!client.ConnectAsync("remotehost", remotePort).Wait(1000))
{
// connection failure
}
您需要使用TcpClient
的异步BeginConnect
方法,而不是尝试同步连接,这是构造函数所做的。像这样:
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);
使用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)
{
...
}
上面的答案没有涵盖如何干净地处理已超时的连接。调用TcpClient。结束连接,关闭一个成功的连接,但在超时后,并处理TcpClient。
这可能有点夸张,但这对我来说很有效。
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();
}
需要注意的一点是,在超时到期之前,BeginConnect调用可能会失败。如果您正在尝试本地连接,可能会发生这种情况。这是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);
这是一个基于并行解决方案的代码改进。为client.ConnectAsync
任务生成的任何异常添加异常捕获(例如:服务器不可达时的SocketException)
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)
{
...
}
如果使用async &等待并希望在不阻塞的情况下使用超时,那么McAndal提供的另一种更简单的方法是在后台线程上执行连接并等待结果。例如:
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.
查看任务。
正如Simon Mourier所提到的,可以将ConnectAsync
TcpClient的方法与Task
一起使用,并尽快停止操作。
例如:
// ...
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 of 1 second
{
// ... transfer
if (client != null) {
client.Close(); // Close the connection and dispose a TcpClient object
Console.WriteLine("Success");
ct.ThrowIfCancellationRequested(); // Stop asynchronous operation after successull connection(...and transfer(in needed))
}
}
else
{
Console.WriteLine("Connetion timed out");
}
// ...
此外,我建议检查AsyncTcpClient c#库,其中提供了一些示例,如Server <> Client
.
我正在使用这些泛型方法;他们可以为任何异步任务添加超时和取消令牌。如果你看到任何问题,请告诉我,这样我就可以相应地解决它。
public static async Task<T> RunTask<T>(Task<T> task, int timeout = 0, CancellationToken cancellationToken = default)
{
await RunTask((Task)task, timeout, cancellationToken);
return await task;
}
public static async Task RunTask(Task task, int timeout = 0, CancellationToken cancellationToken = default)
{
if (timeout == 0) timeout = -1;
var timeoutTask = Task.Delay(timeout, cancellationToken);
await Task.WhenAny(task, timeoutTask);
cancellationToken.ThrowIfCancellationRequested();
if (timeoutTask.IsCompleted)
throw new TimeoutException();
await task;
}
使用await RunTask(tcpClient.ConnectAsync("yourhost.com", 443), timeout: 1000);
Since 。. NET 5, ConnectAsync接受一个取消令牌作为开箱即用的附加参数[1]。这样,就可以简单地设置一个CancellationTokenSource并将其令牌交给connect方法。
超时可能通过捕获OperationCanceledException处理,通常与类似的情况(TaskCanceledException)。请注意,大部分清理工作都是由using块完成的。
const int TIMEOUT_MS = 1000;
using (TcpClient tcpClient = new TcpClient())
{
try
{
// Create token that will change to "cancelled" after delay
using (var cts = new CancellationTokenSource(
TimeSpan.FromMilliseconds(TIMEOUT_MS)
))
{
await tcpClient.ConnectAsync(
address,
port,
cts.Token
);
}
// Do something with the successful connection
// ...
}
// Timeout reached
catch (OperationCanceledException) {
// Do something in case of a timeout
}
// Network-related error
catch (SocketException)
{
// Do something about other communication issues
}
// Some argument-related error, disposed object, ...
catch (Exception)
{
// Do something about other errors
}
}
CancellationTokenSource可以通过一个小的扩展方法隐藏(额外的async/await开销很小):
public static class TcpClientExtensions
{
public static async Task ConnectAsync(
this TcpClient client,
string host,
int port,
TimeSpan timeout
)
{
// Create token that will change to "cancelled" after delay
using (var cts = new CancellationTokenSource(timeout))
{
await client.ConnectAsync(
host,
port,
cts.Token
);
}
}
}
[1] https://learn.microsoft.com/en us/dotnet/api/system.net.sockets.tcpclient.connectasync?view=net - 6.0
ConnectAsync的源代码。网6):https://github.com/dotnet/runtime/blob/v6.0.16/src/libraries/System.Net.Sockets/src/System/Net/Sockets/Socket.Tasks.cs L85-L126