After some investigation, I implemented an asynchronous TCP server as per the following example. During my investigation I was unable to find an example that cleanly shuts down the server; after some experimenting I was able to furnish my code with this functionality. I would appreciate a review of the service code for whether I might be doing something stupid/dangerous. (Please note that I have stripped out parameter validation, etc. in order to improve readability).
public class AsyncTcpServer : IDisposable
{
public class DataReceivedEventArgs : EventArgs
{
public NetworkStream Stream { get; private set; }
public DataReceivedEventArgs(NetworkStream stream)
{
Stream = stream;
}
}
public event EventHandler<DataReceivedEventArgs> OnDataReceived;
public AsyncTcpServer(IPAddress address, int port)
{
_listener = new TcpListener(address, port);
}
public void Start()
{
_listener.Start();
_isListening = true;
WaitForClientConnection();
}
public void Stop()
{
_isListening = false;
_listener.Stop();
}
public void Dispose()
{
Stop();
}
private void WaitForClientConnection()
{
_listener.BeginAcceptTcpClient(HandleClientConnection, _listener);
}
private void HandleClientConnection(IAsyncResult result)
{
if (!_isListening)
{
return;
}
var server = result.AsyncState as TcpListener;
var client = _listener.EndAcceptTcpClient(result);
WaitForClientConnection();
OnDataReceived?.Invoke(this, new DataReceivedEventArgs(client.GetStream()));
}
private readonly TcpListener _listener;
private volatile bool _isListening = false;
}
The following test verifies the asynchronous nature of the service (test completes under 10 seconds for 5x client connections blocking for 5 seconds each).
[TestMethod]
public void TestSendReceiveDataFromMultipleClientsConcurrently()
{
using (var server = new AsyncTcpServer(IPAddress.Any, 54001))
{
server.OnDataReceived += (sender, e) =>
{
var bytesRead = 0;
do
{
// Read buffer, discarding data
bytesRead = e.Stream.Read(new byte[1024], 0, 1024);
}
while (bytesRead > 0 && e.Stream.DataAvailable);
// Simulate long running task
Thread.Sleep(5000);
var response = Encoding.ASCII.GetBytes("Who's there?");
e.Stream.Write(response, 0, response.Length);
};
server.Start();
var tasks = new List<Task>();
for (var i = 0; i < 5; ++i)
{
tasks.Add(Task.Run(() =>
{
var response = new byte[1024];
using (var client = new TcpClient())
{
client.Connect("127.0.0.1", 54001);
using (var stream = client.GetStream())
{
var request = Encoding.ASCII.GetBytes("Knock, knock...");
stream.Write(request, 0, request.Length);
stream.Read(response, 0, response.Length);
Assert.AreEqual("Who's there?", Encoding.ASCII.GetString(response).TrimEnd('\0'));
}
}
}));
}
Assert.IsTrue(Task.WaitAll(tasks.ToArray(), 10000));
}
}