Introduction
This article describes a library that provides an easy to use a TCP server component for the .NET environment that does not have the limitations that many .NET TCP server tutorials have.
Background
For one project I worked on the client had a requirement for multiple TCP server interfaces (4 in all), when I googled how to do a TCP Server in C# I found that there wasn't an easy to use TCP server component (like there is in the Delphi/C++ Builder environment) for the .NET framework (TcpListener class does half the job, but there was nothing to manage the connected clients).
None of the C# TCP server tutorials I found were written with the idea of code sharing between multiple TCP servers projects. I also found that all the tutorials had various limitations (some would only support only one connected client, others would create a new thread for each connection meaning the thread limit is the connection limit), so i decided to write my own without these limitations.
Using the code
The source contains 2 projects, TcpServer, and TestApp (TestApp is provided as an example, you can otherwise ignore it). Add the TcpServer project into your solution, after compiling you should find the TcpServer
component in the toolbox ready to be placed onto a form, service, component or other container. Alternatively you could manually add the component to the toolbox and use as a component or manually add the project reference, then and call the constructor directly.
To start the TCP server you'll have to call a sequence of code like this;
TcpServer tcpServer1 = new TcpServer();
private void openTcpPort(int port)
{
tcpServer1.Port = port;
tcpServer1.Open();
}
private void closeTcpPort()
{
tcpServer1.Close();
}
Calling Open
()
will open the specified TCP port for listening and start the 2 internal threads that handle the main functions of the TCP server. Conversely Close()
stops the 2 internal threads and closes the port, releasing all system resources.
The Port
property is the TCP port opened or to be opened, note that changing Port
while the port is open could cause some undesired consequences. The property IsOpen
will tell you if the port is open before changing it.
There are 3 callback events OnConnect
, OnDataAvailable
and OnError
.
OnConnect
is called when a new client connects, OnDataAvailable
is called when a previously connected client has something in its buffer ready to read.
Both OnConnect
and OnDataAvailable
will pass a TcpServerConnection
class (which is essentially a slim rap-around for the TcpClient
class) that represents the client that connected/has data available.
Both events are called from a thread specifically created for them, neither event will be called again (for that client) while this thread is still active (i.e. when you have multiple callbacks at once it will be for different clients). If there is any data still in the buffer after returning from one of these callbacks the OnDataAvailable
event will be almost immediately called again.
Example callback 1;
public delegate void invokeDelegate();
private void tcpServer1_OnDataAvailable(tcpServer.TcpServerConnection connection)
{
byte[] data = readStream(connection.Socket);
if (data != null)
{
string dataStr = Encoding.ASCII.GetString(data);
invokeDelegate del = () =>
{
handleInput(dataStr);
};
Invoke(del);
data = null;
}
}
protected byte[] readStream(TcpClient client)
{
NetworkStream stream = client.GetStream();
while (stream.DataAvailable)
{
}
return null;
}
Example callback 2;
private void tcpServer1_OnDataAvailable(tcpServer.TcpServerConnection connection)
{
byte[] data = readStream(connection.Socket);
if (data != null)
{
string dataStr = Encoding.ASCII.GetString(data);
string reply;
lock(someKeyObject)
{
reply = getReply(dataStr);
}
connection.sendData(reply);
data = null;
}
}
As you can from this example the
TcpServerConnection
has a property called
Socket
, this is a
TcpClient
class, so you can use it in the same you would if you were writing a TCP client program (minus the, often messy, reconnection logic).
Also illustrated above, the TcpServerConnection
has a sendData()
function, this will take a string, which will be converted using a given System.Text.Encoding
encoding (ASCII by default), which is queued for one of the TcpServer
's internal threads to send down the stream. You can, of course, send data directly using the TcpClient
's functions, though if you do it is recommended that you do not use either of the send functions in this library, otherwise it could lead to packets being sent out of order.
The TcpServer
's OnError
callback is called directly from one of the 2 threads that operate the TCP Server. It is unlikely you will see this event called except in extreme cases (e.g. you've run out of memory). Calls to this event are triggered by unhandled errors, the Exception causing this is passed as a parameter to the event.
The other properties of TcpServer
are as follows;
MaxSendAttempts
; when calling the Send
()
function or the TcpServerConnection
's sendData()
function one of the TcpServer
's internal threads will process the actual sending, if an IOException
occurs while attempting to send it will retry up to MaxSendAttempts
times before discarding the message.
Connections
; (read only) this is a list of
TcpServerConnection
s representing all the connected clients. Calling this property will give you a copy of the internal list.
IdleTime
; when either of the internal threads detects there is no work to do they will wait for up to this many milliseconds before check again. This value should always be low, there should be no reason to increase it above the default, if you have high traffic then this should be set very low.
MaxCallbackThreads
; this represents the maximum number of callback threads to allow at any one time. What this should be depends on how many threads your hardware and version of Windows can handle, and how many other things are running on the server. The default value of 100 is conservative, if you have high traffic you should increase it.
VerifyConnectionInterval
; this is how often (in milliseconds) the TcpServer
should verify that a client is still connected. The verifying process can take some time so if you have high traffic this should be set high, however problems arise when a client drops out and tries to reconnect before the TcpServer
has verified the previous connection (usually leading to the new connection dropping out). I.e. this value is indicative of how long a client that has to wait before they can successfully reconnect. So you should set this to the minimum value you can get away with.
Encoding
; Every
TcpServerConnection
has a property of the same name, which is the
System.Text.Encoding
used to encode data passed through sendData()
or Send()
. The property on the TcpServer
represents the default for all new connections, by directly changing this on the TcpServer
it will change all clients that have the default encoding to the new econding. To change the default without changing the clients use the
SetEncoding()
function with the
changeAllClients
parameter set to false.
Encoding
is Encoding.ASCII
by delault.
As noted above, the TcpServer
also has a Send()
function. It will send the message passed to it to all connected clients (the closest thing to a broadcast you can do in TCP). Calling this is the same as calling the sendData()
function on every item in Connections
. The string passed will be converted to a byte array according to the Encoding
property of each client.
History
2013-04-08 - Released to CodeProject.
2014-03-11 - Fixed calling close now always correctly clears the connections list (Thanks Bart - Member 10655793).