87

我有一个 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
}

这足以处理任务。如果可以,它会发送它,如果它无法连接到远程计算机,它会捕获异常。但是,当它无法连接时,需要十到十五秒才能抛出异常。我需要它在一秒钟左右超时吗?我将如何更改超时时间?

4

9 回答 9

103

您需要使用 asyncBeginConnect方法TcpClient而不是尝试同步连接,这是构造函数所做的。像这样的东西:

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);
于 2013-06-14T23:30:50.553 回答
103

从 .NET 4.5 开始,TcpClient 有一个很酷的ConnectAsync方法,我们可以像这样使用它,所以现在非常简单:

var client = new TcpClient();
if (!client.ConnectAsync("remotehost", remotePort).Wait(1000))
{
    // connection failure
}
于 2015-06-18T13:53:17.900 回答
22

使用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)
{
    ...
}
于 2017-04-05T16:44:50.203 回答
9

上面的答案不包括如何干净地处理已超时的连接。调用 TcpClient.EndConnect,关闭成功但超时后的连接,并处理 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();
    }
于 2014-10-08T16:30:59.663 回答
8

需要注意的一件事是 BeginConnect 调用可能在超时到期之前失败。如果您尝试本地连接,则可能会发生这种情况。这是乔恩代码的修改版本......

        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);
于 2014-03-18T15:22:47.080 回答
2

正如Simon Mourier 所提到的,可以使用ConnectAsyncTcpClient 的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");
}
// ...

另外,我建议使用提供的一些示例检查AsyncTcpClientServer <> Client C# 库,例如.

于 2019-01-23T16:15:42.763 回答
2

这是基于mcandal解决方案的代码改进。为任务生成的任何异常添加了异常捕获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)
{
    ...
}
于 2018-02-09T05:43:04.647 回答
1

如果使用 async & await 并希望在不阻塞的情况下使用超时,那么 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.

有关更多信息和其他示例,请参阅Task.Wait MSDN 文章

于 2018-12-12T03:42:58.720 回答
0

我正在使用这些通用方法;他们可以为任何异步任务添加超时和取消令牌。如果您发现任何问题,请告诉我,以便我可以相应地解决它。

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);
于 2021-08-31T17:22:36.360 回答