Legend
Legend

Reputation: 116860

Asycn TCP Server holds the socket but stops listening

I have the following async TCP server that I use to accept incoming requests from clients that want to upload files. After say about 1-4 hours, the server simply stops accepting any new connections. This has been baffling me because I am unable to reproduce the error deterministically. The rest of the threads in my program continue to function properly. There are no exceptions thrown whatsoever. Any suggestions on what could be happening?

Before the server goes dead, all I do see is that it manages to finish the pending requests and then stops. I have a hunch that the server is experiencing frequent network disconnects. How do I make this piece of code more robust to failures? I've added try-catch to both pieces of code that might fail but I feel I'm still missing something.

I setup another thread to check if this server releases the socket but it seems like the socket is in use even after it stops processing client requests. I verified this using netstat -a

internal class TCPServer
{
    private readonly int _listeningPort;
    private TcpListener listener;

    public TCPServer(int port)
    {
        _listeningPort = port;
        listener = new TcpListener(IPAddress.Any, _listeningPort);
        listener.Start(int.MaxValue);
    }

    public async void Start()
    {
        while (true)
        {
            Log.Verbose("Waiting for connections...");
            try
            {
                var tcpClient = await listener.AcceptTcpClientAsync();
                Task t = HandleConnectionAsync(tcpClient);
                await t;
            }
            catch (Exception exp)
            {
                Log.Fatal(exp.ToString());
            }
        }
    }

    private async Task HandleConnectionAsync(TcpClient tcpClient)
    {
        try
        {
            string outputFile = ""; // get a random string

            using (var stream = tcpClient.GetStream())
            using (var output = File.Create(outputFile))
            {
                //...

                while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
                {
                    // ...
                    await output.WriteAsync(buffer, 0, bytesRead);
                }
            }

            tcpClient.Close();
        }
        catch (Exception exp)
        {
            Log(exp.Message);
        }
    }

Upvotes: 4

Views: 1479

Answers (2)

usr
usr

Reputation: 171178

There are three problems:

  1. You process connections one by one. One faulty/hanging connection holds up the entire server. Simply remove await t;. You are already logging all errors in the worker function which is good.
  2. Make HandleConnectionAsync return very quickly so that accepting new connections can quickly resume. I'd sacrifice a tiny amount of efficiency and just say Task.Run(() => HandleConnectionAsync(tcpClient)) to be sure that processing can continue immediately. Right now all the file opening stuff is synchronous and holding up the accept workflow.
  3. Deterministically close tcpClient by wrapping it: using (tcpClient) .... That prevents zombie clients from hanging around in case of an error.

Upvotes: 6

NewDeveloper
NewDeveloper

Reputation: 130

You should check, if you are still connected.

Maybe this is useful: How to test for a broken connection of TCPClient after being connected?

Upvotes: -1

Related Questions