I have a simple TCP server that is able to listen for and accept multiple connections on a port. It then continuously waits for data to read from its connections. It uses a wrapper class for a TcpClient called ConnectedClient for convenience, and a list (dictionary) of ConnectedClients to keep track of all the connections. It basically goes like this:
/* this method waits to accept connections indefinitely until it receives
the signal from the GUI thread to stop. When a connection is accepted, it
adds the connection to the list and calls a method called ProcessClient,
which returns almost immediately.*/
public void waitForConnections() {
// this method has access to a TcpListener called listener that was started elsewhere
try {
while (!_abort) {
TcpClient socketClient = listener.AcceptTcpClient();
//Connected client constructor takes the TcpClient as well as a callback that it uses to print status messages to the GUI if
ConnectedClient client = new ConnectedClient(socketClient, onClientUpdate);
clients.Add(client.id, client);
ProcessClient(client);
}
}
catch (Exception e) {
onStatusUpdate("Exception Occurred: " + e.Message);
}
}
/* This method doesn't do much other than call BeginRead on the connection */
private void ProcessClient(ConnectedClient client) {
try {
// wrapper class contains an internal buffer for extracting data as well as a TcpClient
NetworkStream stream = client.tcpClient.GetStream();
stream.BeginRead(client.buffer, 0, client.tcpClient.ReceiveBufferSize, new AsyncCallback(StreamReadCompleteCallback), client);
}
catch (Exception ex) {
onStatusUpdate(ex.Message);
}
}
In my callback function, StreamReadCompleteCallback, I call EndRead, checking the return value of EndRead to detect whether the connection has been closed. If the return value is greater than zero, I extract/process the read data and call BeginRead again on the same client. If the return value is zero, the connection has been closed and I remove the connection (delete from list, close the TcpClient, etc).
private void StreamReadCompleteCallback(IAsyncResult ar) {
ConnectedClient client = (ConnectedClient)ar.AsyncState;
try {
NetworkStream stream = client.tcpClient.GetStream();
int read = stream.EndRead(ar);
if (read != 0) {
// data extraction/light processing of received data
client.Append(read);
stream.BeginRead(client.buffer, 0, client.tcpClient.ReceiveBufferSize, new AsyncCallback(StreamReadCompleteCallback), client);
}
else {
DisconnectClient(client);
}
}
catch (Exception ex) {
onStatusUpdate(ex.Message);
}
}
All of this works fine, I can accept connections and read from multiple client devices, etc.
My question is: This method of continuously reading from connected clients causes each connection to have a worker thread that is waiting for BeginRead to return.
So if I have 10 connections, I have 10 BeginReads going.
It seems wasteful to have so many worker threads sitting around waiting to read. Is there some other, better way to accomplish this? I eventually run out of memory to add connections if I have a high number of active connections.
Would having a thread that polls the DataAvailable property of each connection until something shows up, and then makes a thread to read/process be a solution?
Or is creating all these worker threads not as big of a deal as I think?