如何设置TcpClient的超时时间?


78

我有一个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秒的时间引发异常。我需要约一秒钟的时间吗?我将如何更改超时时间?

Answers:


95

您将需要使用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);

2
使用异步连接,然后通过“等待”将其“同步”有什么意义?我的意思是,我目前正在尝试了解如何通过异步读取来实现超时,但是解决方案不是完全禁用异步设计。应该使用套接字超时或取消令牌或类似的方法。否则,请改用连接/读取...
RoeeK 2014年

5
@RoeeK:问题的意思是:以编程方式为连接尝试选择任意超时。这不是有关如何执行异步IO的示例。
2014年

8
@RoeeK:这个问题的全部要点是TcpClient不提供具有可配置超时的同步连接功能,这是您建议的解决方案之一。这是启用它的解决方法。如果不重复自己,我不确定还有什么要说的。
2014年

你是绝对正确的。我可能已经看到在异步操作太多“WaitOnes”它真的没有必要..
RoeeK

2
@JeroenMostert感谢您指出这一点,但请记住,这不是生产级代码。亲爱的,请不要在您的生产系统中复制带有“类似这样的注释”的粘贴代码。=)
乔恩(Jon

81

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

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

3
ConnectAsync的另一个好处是Task.Wait可以接受CancellationToken在需要时立即停止,即使在超时之前也是如此。
伊利亚·巴拉霍夫斯基

8
.Wait将同步阻止,从而消除了“异步”部分的任何好处。 stackoverflow.com/a/43237063/613620是更好的完全异步实现。
Tim P.

9
@TimP。您在哪里看到问题中的“异步”一词?
西蒙·穆里耶

我认为这是一个很好的答案,但是我会返回return client.Connected; 我的测试案例表明,单靠等待不足以给出明确的答案
Walter Vehoeven

1
您刚刚将10位客户的响应时间从28秒减少到1.5秒!!!太棒了!
JeffS

16

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

这是正确的完全异步实现。
Tim P.

1
为什么不能使用ATask.Delay来创建在一定时间后完成的任务,而不是使用ACancellationTokenSource/TaskCompletionSource来提供延迟?(我尝试过并锁定了,但我不明白为什么)
丹尼尔(Daniel

任务何时取消?确定这可以在超时后取消阻止被调用,但是ConnectAsync()是否仍在线程池中的某个位置运行?
TheColonel19年

我也想知道@MondKin问题的答案
TheColonel26 '19

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);

7

上面的答案未涵盖如何彻底处理超时的连接。调用TcpClient.EndConnect,关闭成功但超时后的连接,并处置TcpClient。

可能是矫kill过正,但这对我有用。

    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();
    }

感谢您精心编写的代码。如果连接调用在超时之前失败,是否有可能引发SocketException?
Macke

它已经应该了。当Connect调用完成(成功或其他)或超时(以先发生者为准)时,WaitOne将释放。如果连接“快速失败”,检查!client.Connected将引发异常。
Adster

3

在NetworkStream上设置ReadTimeout或WriteTimeout属性以进行同步读取/写入。更新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
}

1

这是基于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)
{
    ...
}

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文章


1

正如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");
}
// ...

另外,我建议您使用提供的示例检查AsyncTcpClient C#库Server <> Client

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.