TCP. It's a key component of the foundation of the internet. We take it for granted every day while we're online. Sometimes, it becomes necessary to leverage this amazing protocol to implement reliable and fast communication. Here's a brief explanation of how I used the .NET framework's TCPListener to create a simple TCP server in the form of Easy-TCP-Server, a .NET core library which handles incoming TCP data from multiple clients via 'channels' of connections between the clients and the server.
The Use Case
The solution is built in order to allow the project which references it to quickly spin up a TCP server which can then listen for any incoming TCP traffic. The aim is to make it easy to create an endpoint that any client can send TCP requests to and receive responses from on a persistent, managed connection. (If needed.) This may be useful for many different reasons, with examples like using remote clients to trigger the execution of a remote process, or sending data to a server for persistence in a database to name just a few.
I'll go into more detail about the overall development of the project in another post at some point, but for now, I want to explain how I was able to accept multiple connections on a .NET TCPListener.
Accepting multiple clients
The documentation's main example for TCPListener demonstrates how to listen for a request, receive and receive it on the same thread. For our use case, we want to be able to receive any incoming request and then handle it on a separate thread, to prevent our listener from blocking.
First of all, let's initialize a TCPListener
Listener = new TcpListener(IPAddress.Parse("127.0.0.1!), 12400); Listener.Start();
Here we create a listener which will be listening on the localhost, on port 12400, before starting it.
Now we need to start the listener and create a loop that while running, accepts incoming TCP clients before we do something with the incoming request. .NET presents a couple of options for receiving TCPClient connections within the TCPListener class, with the main one being AcceptTcpClient(). This will allow us to listen out for incoming requests on our specified hostname and port, and would be fine if we wanted to block the current thread in order to handle the traffic, like below:
while (true) { var client = await Listener.AcceptTcpClient(); //Do Something }
But we don't want to block. We want to accept requests asynchronously so that on each request, we can make a different thread for handling the incoming traffic. To achieve this, we can still use a while loop, but instead of using AcceptTcpClient() let's use AcceptTcpClientAsync(). Using this method, the thread will no longer be blocked, as we are awaiting any incoming requests. When a request is received, we can fire off our handler logic in the form of a Task, so that we are not blocking the program further. This will allow multiple clients to hit our listener and be handled.
while (true) { var client = await Listener.AcceptTcpClientAsync(); await Task.Run(() => //Do Something ); }
Here is a more detailed example of the implementation
public class Server
{
bool _running;
public bool Running
{
get
{
return _running;
}
set
{
_running = value;
}
}
public Server()
{
Listener = new TcpListener("127.0.0.1", 12400);
}
public async void Start()
{
try
{
Listener.Start(); Running = true;
while (Running)
{
var client = await Listener.AcceptTcpClientAsync();
await Task.Run(() => //Do something);
}
}
catch(SocketException)
{
throw;
}
}
public void Stop()
{
Listener.Stop();
Running = false;
}
}
Here, I've provided a means to start the listener's accept loop and a means to gracefully close it. I've also added a handler for any SocketExceptions that may be thrown.
There it is. A simple TCPListener that can accept multiple connections and handle them on a separate thread. This is a more simplified version of the server I wrote for the Easy-TCP-Server library, the source of which is available for your use here.
Top comments (1)
Why would you
await
theTask.Run
? Wouldn't that still block the loop from iterating to the next call toAcceptTcpClientAsync
?