2

I'm having trouble closing a tcpClient connection when calling Application.exit() from a windows form. The problem I'm having is when the form closes the application is still running due to TCP connected devices and I want to close these TCP connections that the app has. I'm using threads per advice on here and I'm successfully connecting devices. When I call server.Stop() I get the following error:

System.Net.Sockets.SocketException (0x80004005): A blocking operation was interrupted by a call to WSACancelBlockingCall

I've found that the error is linked to this line of code TcpClient client = server.AcceptTcpClient(); which I think keeps connections open when I use threads per each TCP connection.

I've tried using server.Shutdown() and also using tokens (the program class has one that calls StartServer() on launch but is blocked by the same error as the TCPService, it's code added below.

Also, If I close the connection from a connected device the application does close.

The full TCP class code is below:

 public class TCPService
{

    private const int port = 8045;
    private bool running;
    private static TcpListener server = null;
    private Thread t = null;

    public ammendDeviceCountDelegate handleDeviceCount;


    public void StartServer()
    {
        string hostName = Dns.GetHostName();
        IPAddress myIP = GetIPAddress(hostName);
        IPEndPoint localEndPoint = new IPEndPoint(myIP, port);
        server = new TcpListener(myIP, port);
        server.Start();
        running = true;
        StartListener();
    }

    public void stopServer()
    {
        running = false;
        server.Stop();
    }

    private void StartListener()
    {
        try
        {
            while (running)
            {
                TcpClient client = server.AcceptTcpClient();
                Console.WriteLine("Connected!");

                t = new Thread(new ParameterizedThreadStart(HandleDevice));
                t.Start(client);
             
            }

        }
        catch (SocketException e)
        {
            Console.WriteLine("Exception: {0}", e);         
        }
    }

    private void HandleDevice(Object obj)
    {
        TcpClient client = (TcpClient)obj;
        var stream = client.GetStream();
        string imei = String.Empty;

        string data = null;
        Byte[] bytes = new Byte[256];
        int i;

        handleDeviceCount(false);

        try
        {
            while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
            {
                string hex = BitConverter.ToString(bytes);
                data = Encoding.ASCII.GetString(bytes, 0, i);
                Console.WriteLine("{1}: Received: {0}", data, Thread.CurrentThread.ManagedThreadId);

                string str = "handshaky";
                Byte[] reply = System.Text.Encoding.ASCII.GetBytes(str);
                stream.Write(reply, 0, reply.Length);
                Console.WriteLine("{1}: Sent: {0}", str, Thread.CurrentThread.ManagedThreadId);
            }
            client.Close();
        }
        catch (Exception e)
        {
            Console.WriteLine("Exception: {0}", e.ToString());
            client.Close();
        }
    }

    private static IPAddress GetIPAddress(string hostname)
    {
        IPHostEntry host;
        host = Dns.GetHostEntry(hostname);

        foreach (IPAddress ip in host.AddressList)
        {
            if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                return ip;
            }
        }
        return IPAddress.None;
    }
}

Program start method:

 private static void startTCPServer()
    {
        tokenSource = new CancellationTokenSource();

        Task.Run(() =>
        {
            while (running)
            { 
                if (!tokenSource.Token.IsCancellationRequested)
                {
                    tcp.StartServer();
                    break;
                }
            }
        }, tokenSource.Token);
       
    }
832049823
  • 91
  • 6
  • Does this answer your question? [Proper way to stop TcpListener](https://stackoverflow.com/questions/365370/proper-way-to-stop-tcplistener) – Lzh Jun 28 '22 at 10:07
  • @Mzn - No, I'd already seen that thread before posting. – 832049823 Jun 28 '22 at 10:16
  • What's your problem? To handle the error, to explain, or to avoid? If you just want to close the server, then you have done. – shingo Jun 28 '22 at 10:46
  • If your problem is that AcceptTcpClient blocks the thread. Why would not `TcpListener.Pending` from Mzns link solve that? – JonasH Jun 28 '22 at 10:58
  • @JonasH The problem is that even using the isPending() results in the application window closing but not the application as I still have connected devices. Only closing each connection from the device does the application fully terminate. – 832049823 Jun 28 '22 at 11:26
  • @shingo The problem is that even when calling `server.Stop();` the application doesn't fully terminate due to active TCP connections which I want to close. – 832049823 Jun 28 '22 at 11:31
  • 1
    I think the server is stopped, but there's a pending thread (probably HandleDevice) and it causes your application not to close. You may debug your application to find the thread. Consider to set read-write timeout for the client. – shingo Jun 28 '22 at 11:58

1 Answers1

1

A separate thread for each listener is a bad idea if you have lots of listeners anyway. You should transition this completely to async await and use the CancellationToken when you close the app.

You just need to use token also in the handler of the client, then it will bail out.

public class TCPService
{
    private const int port = 8045;
    public CancellationToken Token {get; set;}

    public ammendDeviceCountDelegate handleDeviceCount;

    public async Task StartServer()
    {
        string hostName = Dns.GetHostName();
        IPAddress myIP = await GetIPAddress(hostName);
        IPEndPoint localEndPoint = new IPEndPoint(myIP, port);
        TcpListener server = null;  // server is local variable
        try
        {
            var server = new TcpListener(myIP, port);
            server.Start();
            await Listen;
        }
        finally
        {
            server?.Stop();
        }
    }

    private async Task Listen()
    {
        try
        {
            while (true)
            {
                Token.ThrowIfCancellationRequested();
                TcpClient client = await server.AcceptTcpClientAsync(Token);
                Console.WriteLine("Connected!");

                Task.Run(async () => await HandleDevice(client), Token);
            }
        }
        catch (SocketException e)
        {
            Console.WriteLine("Exception: {0}", e);         
        }
    }

    private async Task HandleDevice(TcpClient client)
    {
        string imei = String.Empty;

        string data = null;
        Byte[] bytes = new Byte[256];
        int i;

        handleDeviceCount(false);

        try
        {
            using (var stream = client.GetStream())
            {
                while ((i = await stream.ReadAsync(bytes, 0, bytes.Length, _token)) != 0)
                {
                    Token.ThrowIfCancellationRequested();
                    string hex = BitConverter.ToString(bytes);
                    data = Encoding.ASCII.GetString(bytes, 0, i);
                    Console.WriteLine("{1}: Received: {0}", data, Thread.CurrentThread.ManagedThreadId);

                    string str = "handshaky";
                    Byte[] reply = System.Text.Encoding.ASCII.GetBytes(str);
                    await stream.WriteAsync(reply, 0, reply.Length, Token);
                    Console.WriteLine("{1}: Sent: {0}", str, Thread.CurrentThread.ManagedThreadId);
                }
            }
        }
        catch (OperationCanceledException) { }
        catch (Exception e)
        {
            Console.WriteLine("Exception: {0}", e.ToString());
        }
        finally
        {
            client.Close();
        }
    }

    private async Task<IPAddress> GetIPAddress(string hostname)
    {
        IPHostEntry host;
        host = await Dns.GetHostEntryAsync(hostname, Token);

        foreach (IPAddress ip in host.AddressList)
        {
            if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
            {
                return ip;
            }
        }
        return IPAddress.None;
    }
}

You then start and stop it like this

CancellationTokenSource _tokenSource;

private static void startTCPServer()
{
    _tokenSource = new CancellationTokenSource();
    tcp.Token = tokenSource.Token;
    Task.Run(tcp.StartServer, tokenSource.Token);
}

private static void stopTCPServer()
{
    _tokenSource.Cancel();
}

Call stopTCPServer when your app closes.

Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • Thanks for the learning assist! I need to get to grips with asynchronous calls so this also helps. I see you've wrapped the stream inside handle device. Would I be thread safe to assign this to a class wide variable for sending content to the devices? – 832049823 Jun 28 '22 at 18:59
  • Probably not, unclear exactly what you intend to do with it. If you want to queue up response then a `ConcurrentQueue` would be OK – Charlieface Jun 28 '22 at 19:40
  • Aside from the handshake response I'd be looking to send string based information downstream to each connected client. – 832049823 Jun 29 '22 at 11:49
  • Sounds like a whole project. Begin coding, come back with a new question if you have problems. Are you sending the same data to all clients, or are clients requesting particular data and you are responding? You probably need to set up a state machine object for each client, to keep track of if it's handshaked correctly and so on. – Charlieface Jun 29 '22 at 12:08
  • Sending data to all clients. I've managed to set up a simple state manager via string based session id's which is passed up and down with each client request. – 832049823 Jun 29 '22 at 14:50