如何设置TcpClient的超时?

msb*_*sbg 68 c# networking tcpclient winforms

我有一个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
}
Run Code Online (Sandbox Code Playgroud)

这对于处理任务非常有效.如果可以,它会发送它,如果它无法连接到远程计算机,则捕获异常.但是,当它无法连接时,抛出异常需要十到十五秒.我需要它在大约一秒钟内超时?我怎样才能改变超时时间?

Jon*_*Jon 83

您需要使用异步BeginConnect方法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);
Run Code Online (Sandbox Code Playgroud)

  • @RoeeK:这个问题的重点是`TcpClient`不提供具有可配置超时的同步连接功能,这是您提出的解决方案之一.这是启用它的解决方法.如果不重复自己,我不确定还有什么可说的. (6认同)
  • @RoeeK:重点是问题所在:以编程方式为连接尝试选择任意超时.这不是关于如何执行异步IO的示例. (5认同)
  • 使用异步连接有什么意义,并将其与Wait"同步"回来?我的意思是,我目前正在尝试了解如何使用异步读取实现超时,但解决方案并非完全禁用异步设计.应该使用套接字超时或取消令牌或类似的东西.否则,只需使用连接/读取... (2认同)
  • @JeroenMostert 感谢您指出这一点,但让我们记住这不是生产级代码。人们,请不要在您的生产系统中复制带有注释“类似这样的东西”的粘贴代码。=) (2认同)

Sim*_*ier 73

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

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

  • .Wait将同步阻止,删除"异步"部分的任何好处./sf/answers/3026594441/是一个更好的完全异步实现. (4认同)
  • @TimP.你在哪里看到问题中的"异步"这个词? (4认同)
  • ConnectAsync的另一个好处是Task.Wait可以接受CancellationToken,以便在超时之前立即停止. (3认同)
  • 您刚刚将我对 10 个客户的响应时间从 28 秒缩短到 1.5 秒!!!惊人的! (2认同)

小智 12

使用/sf/answers/1797918461/的另一种方法:

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)
{
    ...
}
Run Code Online (Sandbox Code Playgroud)


Nei*_*len 8

需要注意的一点是,在超时到期之前,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);
Run Code Online (Sandbox Code Playgroud)


Ads*_*ter 7

上面的答案不包括如何干净地处理已超时的连接.调用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();
    }
Run Code Online (Sandbox Code Playgroud)