This post is part of a series, to see the other posts in the series go to the series index.
The first two posts (post 1, post 2) in this series were really to just set the stage and help explain problems with TCP servers that do blocking. The code and method is not wrong, they are okay for some scenarios, but if you want a really robust model, a model that fixes the issues with them without any extra technical issues being added you need to use a non-blocking server.
We start the server the same way, however instead of using the blocking AcceptTcpClient method we use the non-blocking method BeginAcceptTcpClient which implements the async or begin/end (I’ll call it async from here on) pattern. Mark Pearl has a great post explaining the async pattern.
When a client connects, it runs the code associated when we setup the async and in there we call the EndAcceptTcpClient to end the async process and get the TcpClient implementation.
We repeat this process with reading from the client by switching from Read to BeginRead.
The advantage of this model is the only blocking point is the Readline so shutting the server is very easy, we have no thread management to worry about (it is using threads under the covers, but we do not need to care), and clients can connect now and send data at any time – it doesn’t matter!
This pattern does require a lot more code and it is more complex to setup, but once you understand the async pattern (call begin > pass the info we need in state > call end to get more info we need) it is pretty easy
class Program { private const int BufferSize = 4096; static void Main(string[] args) { var tcpServer = new TcpListener(IPAddress.Any, 9000); try { tcpServer.Start(); tcpServer.BeginAcceptTcpClient(ClientConnection, tcpServer); Console.WriteLine("Press enter to shutdown"); Console.ReadLine(); } finally { tcpServer.Stop(); } } private static void ClientConnection(IAsyncResult asyncResult) { Console.WriteLine("Client connection"); var tcpServer = asyncResult.AsyncState as TcpListener; var tcpClientConnection = tcpServer.EndAcceptTcpClient(asyncResult); tcpServer.BeginAcceptTcpClient(ClientConnection, tcpServer); var stream = tcpClientConnection.GetStream(); var buffer = new byte[BufferSize]; var clientData = new ClientReadData() { Buffer = buffer, Stream = stream }; stream.BeginRead(buffer, 0, BufferSize, ClientRead, clientData); } private class ClientReadData { public byte[] Buffer { get; set; } public NetworkStream Stream { get; set; } } private static void ClientRead(IAsyncResult asyncResult) { var clientReadData = asyncResult.AsyncState as ClientReadData; var amountRead = clientReadData.Stream.EndRead(asyncResult); var buffer = new byte[BufferSize]; var clientData = new ClientReadData() { Buffer = buffer, Stream = clientReadData.Stream }; clientReadData.Stream.BeginRead(buffer, 0, BufferSize, ClientRead, clientData); var message = Encoding.ASCII.GetString(clientReadData.Buffer, 0, amountRead); Console.WriteLine("Client sent: {0}", message); } }