C# 如何设置 TcpClient 的超时时间?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17118632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 08:38:49  来源:igfitidea点击:

How to set the timeout for a TcpClient?

c#winformsnetworkingtcpclient

提问by msbg

I have a TcpClient which I use to send data to a listener on a remote computer. The remote computer will sometimes be on and sometimes off. Because of this, the TcpClient will fail to connect often. I want the TcpClient to timeout after one second, so it doesn't take much time when it can't connect to the remote computer. Currently, I use this code for the TcpClient:

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

This works well enough for handling the task. It sends it if it can, and catches the exception if it can't connect to the remote computer. However, when it can't connect, it takes ten to fifteen seconds to throw the exception. I need it to time out in around one second? How would I change the time out time?

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

采纳答案by Jon

You would need to use the async BeginConnectmethod of TcpClientinstead of attempting to connect synchronously, which is what the constructor does. Something like this:

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

回答by NeilMacMullen

One thing to take note of is that it is possible for the BeginConnect call to fail before the timeout expires. This may happen if you are attempting a local connection. Here's a modified version of Jon's code...

需要注意的一件事是,在超时到期之前,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);

回答by Adster

The answers above don't cover how to cleanly deal with a connection that has timed out. Calling TcpClient.EndConnect, closing a connection that succeeds but after the timeout, and disposing of the TcpClient.

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

It may be overkill but this works for me.

这可能有点矫枉过正,但这对我有用。

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

回答by Simon Mourier

Starting with .NET 4.5, TcpClient has a cool ConnectAsyncmethod that we can use like this, so it's now pretty easy:

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

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

回答by mcandal

Another alternative using https://stackoverflow.com/a/25684549/3975786:

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

回答by mcmcmc

Set ReadTimeout or WriteTimeout property on the NetworkStream for synchronous reads/writes. Updating OP's code:

在 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
}

回答by Dennis

Here is a code improvement based on mcandalsolution. Added exception catching for any exception generated from the client.ConnectAsynctask (e.g: SocketException when server is unreachable)

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

回答by Bob Bryan

If using async & await and desire to use a time out without blocking, then an alternative and simpler approach from the answer provide by mcandal is to execute the connect on a background thread and await the result. For example:

如果使用 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.

See the Task.Wait MSDN articlefor more info and other examples.

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

回答by V.7

As Simon Mouriermentioned, it's possible to use ConnectAsyncTcpClient's method with Taskin addition and stop operation as soon as possible.
For example:

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