【问题标题】:TCPClient async/await C#TCPClient 异步/等待 C#
【发布时间】:2020-02-01 09:40:37
【问题描述】:

我有几个设备。程序必须不断 ping 这些设备。

遇到一个问题——如果连接丢失,那么我的程序除了连接丢失前的第一次轮询外什么都不显示,如果我恢复连接,那么 15 秒后程序将开始输出数据。

public async Task Start(string ip)
    {
        textBox1.AppendText("Begin");
        textBox1.AppendText("\r\n");
        Stopwatch watch = new Stopwatch();

        int i = 0;

        while (true)
        {
            watch.Restart();
            using (TcpClient tcp = new TcpClient())
            {
                tcp.SendTimeout = 1000;
                try
                {
                    await tcp.ConnectAsync("192.168.127.23", 10001);
                }
                catch (SocketException)
                {
                    Debug.Assert(!tcp.Connected);
                }

                watch.Stop();
                if (tcp.Connected)
                {
                    textBox1.AppendText(i.ToString() + ") " + watch.ElapsedMilliseconds.ToString() + " ms");
                    textBox1.AppendText("\r\n");
                }
                else
                {
                    textBox1.AppendText(string.Format("{0}) Offline", i));
                }
            }

            await Task.Delay(1000);
            i++;
        }
    }

这是我添加的新代码。

public async Task Start(string ip)
    {   
        while (true)
        {
            for (int i = 0; i < devicesListActivity.Count; i++)
            {
                devicesListActivity[i].DevicesList.DevicesTotalPing++;

                string ipAdresDevice = devicesListActivity[i].DevicesList.DevicesName;
                int portDevice = devicesListActivity[i].DevicesList.DevicesPort;
                int activeDevice = devicesListActivity[i].DevicesList.DevicesActiv;
                int imageDevice = devicesListActivity[i].DevicesList.DevicesImage;
                int sendTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeSend;
                int respTimeDevice = devicesListActivity[i].DevicesList.DevicesTimeResp;

                var cts = new CancellationTokenSource(sendTimeDevice);
                var ct = cts.Token;

                var t = await Task.Run<ServerStatus>(() =>
                {
                    try
                    {
                        using (TcpClient client = new TcpClient())
                        {
                            client.ConnectAsync(ipAdresDevice, portDevice).Wait(sendTimeDevice);
                            ct.ThrowIfCancellationRequested();
                            client.Close();
                            return ServerStatus.Available;
                        }
                    }
                    catch (AggregateException ex) when (ex.InnerException.GetType() == typeof(SocketException))
                    {
                        if (((SocketException)ex.InnerException).SocketErrorCode == SocketError.ConnectionRefused)
                            return ServerStatus.Refused;
                        else
                        {
                            throw new Exception("Server did not respond");
                        }

                    }
                    catch (OperationCanceledException)
                    {
                        return ServerStatus.TimeOut;
                    }
                }, ct);

                switch (t)
                {
                    case ServerStatus.Available:
                        devicesListActivity[i].DevicesList.DevicesSuccessPing++;
                        textBox1.AppendText($"{DateTime.Now.ToString()} Server available" + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) +" %");
                        textBox1.AppendText("\r\n");
                        break;
                    case ServerStatus.Refused:
                        textBox1.AppendText($"{DateTime.Now.ToString()} Server refused connection." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
                        textBox1.AppendText("\r\n");
                        break;
                    case ServerStatus.TimeOut:
                        textBox1.AppendText($"{DateTime.Now.ToString()} Server did not respond." + " " + ipAdresDevice + string.Format(" [{0}/{1}]", devicesListActivity[i].DevicesList.DevicesSuccessPing, devicesListActivity[i].DevicesList.DevicesTotalPing) + " " + System.Math.Round((double)(devicesListActivity[i].DevicesList.DevicesSuccessPing / devicesListActivity[i].DevicesList.DevicesTotalPing * 100)) + " %");
                        textBox1.AppendText("\r\n");
                        break;
                }

                // Wait 1 second before trying the test again
                await Task.Delay(1000);
            }
        }
    }

【问题讨论】:

  • 您描述了当前的行为。你真正想看到/实现的行为是什么?
  • @MouseOnMars 我想看看当连接失败时,每次尝试都会离线显示。很抱歉我忘了添加这个。

标签: c# async-await tcpclient


【解决方案1】:

您误用了 TCP Connect 的工作方式。当您执行client.ConnectAsync() 时,操作系统将需要一段时间才能实际超时。您对tcp.SendTimeout = 1000; 的设置对操作系统管理的ConnectAsync() 没有影响,可以为20 秒。

所以在这种情况下发生的情况是,您在连接超时并且连接已连接之前使服务器重新上线。

因此,除非要等待 20 秒才能收到警报,否则您将需要运行另一个超时来取消挂起的 Connect() 并报告您处于离线状态。例如,如果您在 1 秒内没有得到响应,则报告离线。

此外,如果连接由于被主动拒绝而失败,您还需要处理该测试用例。拒绝通常意味着您的服务器已启动,但端口未在侦听。但是也可能是防火墙主动拒绝连接,在这种情况下您不知道服务器是否已启动。

考虑以下实现端口的基本 TCP 监控的代码示例:

private async void btnTest_Click(object sender, EventArgs e)
{
    int timeOut = 2000;

    while (true)
    {
        using (TcpClient client = new TcpClient())
        {
            var ca = client.ConnectAsync("127.0.0.1", 9999);
            await Task.WhenAny(ca, Task.Delay(timeOut));
            client.Close();
            if (ca.IsFaulted || !ca.IsCompleted)
                listBox1.Items.Add($"{DateTime.Now.ToString()} Server offline.");
            else
                listBox1.Items.Add($"{DateTime.Now.ToString()} Server available.");
        }
        // Wait 1 second before trying the test again
        await Task.Delay(1000);
    }
}

【讨论】:

  • 非常感谢,现在它几乎可以做到,但有一个问题。如果连接丢失,则操作员throw 会出现异常。我添加了这个throw new Exception("Server did not respond");,但是当然没有输出。请告诉我如何解决它。感谢您的帮助。
  • 刚刚发现了一个有趣的点。我稍微更改了您的代码以满足我的需要,现在发生了奇怪的事情,我想 ping 两台设备,一台肯定可以,而第二台不行。前3次正常显示非工作设备未连接,然后显示有连接。告诉我有什么问题。我在我的问题中添加了新代码。
  • 有时会立即显示有连接。
  • 很可能我破坏了您的代码中的某些内容,因为现在即使您禁用工作设备,它仍然显示有连接... =)
  • 嗨。您需要在调试器中关闭运行时异常。我们将异常用作程序流程的一部分,这是不受欢迎的,但对于 Socket,您别无选择,因为这就是套接字的设计方式。这些异常不会在生产版本中发生。
猜你喜欢
  • 2014-07-29
  • 2017-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-26
  • 1970-01-01
  • 2013-06-27
  • 2023-03-12
相关资源
最近更新 更多