Home > Software engineering >  What's the best way to establish and validate a connection from a client without blocking?
What's the best way to establish and validate a connection from a client without blocking?

Time:12-08

I have a TcpListener server that can open a connection with a client by calling server.AcceptTcpClientAsync(). Unfortunately, there is random, unwanted traffic connecting to the server that I don't care about, so I've added a method, Task<bool> Handshake(TcpClient client), to validate that the client should be able to talk to the server.

The naive way to get a connection is:

async Task<TcpClient> GetClient(TcpListener server)
{
   while (true)
   {
      var client = await server.AcceptTcpClientAsync();
      if (await Handshake(client))
      {
         return client;
      }
      client.Dispose();
   }
}

Unfortunately, the handshake process takes a nonzero amount of time to timeout when waiting on a spurious connection, and if it takes longer than it takes for the next connection to show up, pending connections will stack up and never get serviced.

I know that APIs like Task.WhenAny allow for multiple async operations to be running at once, but I can't seem to get a proper model for this connection process worked out.

What I want:

  1. Only one successful connection is expected.
  2. When a connection arrives, attempt a handshake. If the handshake is successful, return it and dispose any other pending connections. If not, dispose the connection.
  3. A handshake being in progress should not prevent the loop from accepting a new connection.
  4. It's always possible for the number of handshakes in progress to be 0, 1, or more than 1.

Is there any good way to express this in code?

CodePudding user response:

To begin, you can use Task.Run to execute the task so that you don't have to await there. Then you can use ContinueWith as a callback to handle successful handshake. Next, You can maintain a list of connections, to be disposed. Also, I would suggest adding some delay while looping,


async Task<TcpClient> GetClient(TcpListener server)
{ 
   List<Task<Client>> _clients=new(); 
   bool handshakeCompleted=false;
   Client workingClient=null;
   while (!handshakeCompleted)
   {
      var client = Task.Run(async ()=> await server.AcceptTcpClientAsync());
      client.ContinueWith(async ()=>
      {
         if (await Handshake(client))
         {
           handshakeCompleted=true;
           workingClient=client;
         }
      });
      _clients.Add(client);
      await Task.Delay(10);
   }
 foreach(var c in _clients.Where(c!=workingClient)) c.Dispose();
 return workingClient;
}

CodePudding user response:

You can use Channel for safe cross thread communication

var channel = Channel.CreateBounded<TcpClient>(100);
var writer = channel.Writer;
var reader = channel.Reader;

_ = GetClient(server);

var successClient = await Task.Run(async () =>
{
    await foreach (var client in reader.ReadAllAsync())
    {
        if (await Handshake(client))
        {
            return client;
        }
        client.Dispose();
    }
    return null;
});

//mark the channel that no more data is going to be written to it
writer.TryComplete();

//run through the remaining clients in the channel and dispose of them
await foreach (var client in reader.ReadAllAsync())
{
    client.Dispose();
}

//use successClient here


async Task<TcpClient> GetClient(TcpListener server)
{
    while (true)
    {
        var client = await server.AcceptTcpClientAsync();
        await writer.WriteAsync(client);
    }
}

  • Related