Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# TCP Server

0.00/5 (No votes)
8 Apr 2013 1  
A light weight easy to use .NET TCP server library

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(); //in constructor (auto added if added as a component)
 
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)
            {
                //call stream.Read(), read until end of packet/stream/other termination indicator
                //return data read as byte array
            }
            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 TcpServerConnections 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).

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here