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:
- Only one successful connection is expected.
- 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.
- A handshake being in progress should not prevent the loop from accepting a new connection.
- 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);
}
}