Search code examples
c#async-awaittcpclient

TCPClient async/await C#


I have several devices. The program must constantly ping these devices.

Faced a problem - if the connection is lost, then my program does not display anything except the first poll before the connection is lost, and if I restore the connection, then after 15 seconds the program will start to output data.

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++;
        }
    }

This is a new code, with my additions.

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

Solution

  • You're misusing the way that TCP Connect works. When you do a client.ConnectAsync() the operating system will take a period of time to actually time out. Your setting of tcp.SendTimeout = 1000; has no effect on the ConnectAsync() which is managed by the operating system which can be 20 seconds.

    So what's happening in this case is that you're bringing the server back on line before the connect has timed out and the connect connects.

    So unless want to wait 20 seconds to be alerted, you're going to need to run another timeout to cancel the pending Connect() and report that you are offline. Eg, if you don't get a response in 1 second, the report offline.

    Also if the connection fails due to it being actively refused you'll need to handle that test case as well. Refused generally means that your server is up, but the port is not listening. However it could also be a firewall actively refusing the connection, in which case you don't know if the server is up.

    Consider the following code example which achieves basic TCP monitoring of a port:

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