Click here to Skip to main content
15,885,025 members
Articles / Programming Languages / C#
Article

TcpDemon

Rate me:
Please Sign up or sign in to vote.
4.81/5 (12 votes)
9 Aug 20055 min read 71.7K   418   56   17
This article shows a NET implementation of a typical TCP demon.

Introduction

When writing a server using TCP as transport layer, you typically need a component to take care of:

  • accepting incoming connections
  • reading data from connected clients
  • writing data to connected clients

This article presents a solution to this problem, relatively small in code size and complexity, using NET's asynchronous communication model. The class described, TcpDemon, is reusable and quite tested, so it should be no problem to incorporate it right away into your project.

Background

On UNIX environments, the approach traditionally taken to implement a TCP server relies on multiple processes. One process listens for connections and forks client handling processes whenever a client connects. The socket calls are blocking, i.e., in .NET terminology, synchronous, so using multiple processes is necessary in order to allow concurrency, e.g., the server to listen for other connections while serving one already established. In the absence of operating system supported threads (as historically was the case for UNIX systems), the only way to achieve concurrent execution was to use processes.

In the meantime, mainstream UNIX flavors like Linux offers thread support, so the multiple process approach has been changed to a multiple thread model. This reduces the overhead involved with process creation, but the general principle stays the same.

Windows, on the other hand, typically relies on another approach: asynchronous, event-driven communication. Within the Windows API, I/O completion ports serve to abstract that model. With the advent of .NET, all of this has been very nicely wrapped into framework classes. The asynchronous model hides most of the concurrency involved from the application developer. The operating system creates the needed threads on the fly, and, quite obviously, is better able to decide when to reuse existing threads and employ optimization schemes like thread pooling. All this goes on behind the scenes, though, the developer is presented with a set of methods each accepting an additional callback routine parameter. This callback is called when the method finishes. The callback routine runs in another thread, though, as the operating system does thread the method calls, so the application developer still needs to take care of thread synchronization issues. Although this already is rather comfortable, as opposed to managing all the threading oneself, the .NET framework does not offer any ready-made class encapsulating the main requirements of a typical TCP demon:

  • notification on client connects
  • notification on client disconnects
  • notification on data receival from clients
  • notification on communication errors
  • sending data to clients in a fire-and-forget manner

The class presented in this article encapsulates exactly these four requirements in a self-contained, rather straightforward manner. I have been using this code in several applications. None of them was high-load (many clients connecting and disconnecting, sending and receiving relatively huge amounts of data), but I wrote a stress test demo and the results are quite encouraging. (You will find the demo in the downloadable Zip file that goes with this article, along with the source for TcpDemon itself.)

Using TcpDemon

TcpDemon exposes a rather small public interface:

Construction:
C#
public TcpDemon(int port, int capacity)
  • port denotes which port the server should listen to - note that in the current implementation the server binds itself to all available interfaces, i.e., network cards in the machine it is running on.
  • capacity specifies how big the queue for pending connection requests should be. If you set this parameter to a very low value and many clients try to connect in a small amount of time, some of them will get no connection because the queue fills up quickly. Obviously, this is very application specific and has to be adapted to your needs.
Interface:

In order to allow to substitute for TcpDemon with a dummy class in testing, its interface has been extracted to:

C#
public interface IDemon
{
    void Start();
    void Stop();
    void Send(IPEndPoint client, byte[] buffer);
    ClientEvent OnConnected {get; set;}
    ClientEvent OnDisconnected {get; set;}
    ClientDataEvent OnData {get; set;}
    ClientErrorEvent OnError {get; set;}
}
  • Start() starts listening for incoming connection requests.
  • Stop() stops listening and closes all open client connections.
  • Send() sends the data contained in buffer to client.

The delegates used for the notification callbacks are defined as:

C#
public delegate void ClientEvent(IPEndPoint client);
public delegate void ClientDataEvent(IPEndPoint client, byte[] data);
public delegate void ClientErrorEvent(IPEndPoint client, SocketException error);

Beware that you need to set these delegates before you call Start. If you set them while the demon is running, an InvalidOperationException is thrown. This is a condition imposed because I wanted to avoid using thread synchronization primitives on each access to the delegates. You also should be aware of the fact that all these callbacks are potentially and very likely called in the context of another thread - so you need to take care of synchronization. The stress test demo supplied with the source code contains several examples. One of them uses the Interlocked class:

C#
void OnClientConnected(IPEndPoint client)
{
    Interlocked.Increment(ref mConnectionCount);
}

Another one uses a ReaderWriterLock:

C#
void OnClientData(IPEndPoint client, byte[] data)
{
    // ...
    mStatsLock.AcquireWriterLock(-1);
    mDataProcessed+= (ulong) data.Length;
    mStatsLock.ReleaseWriterLock();
    // ...
}

Note about thread synchronization:

On a slightly aside note, it may be worth mentioning that although C# seems to make thread synchronization very easy with its lock construct, it generally is not advisable to blindly use it. lock uses a critical section internally which locks reading access just as well as writing access - and that is typically not a good idea. ReaderWriterLock allows to control locking more finely, distinguishing between locking for read access and locking for write access. Interlocked contains static methods for atomically incrementing and decrementing integral values as well as swapping objects. With the latter, Interlocked.Exchange(), I happened to note about a weird little inconsistency: the MSDN help documents the overload:

C#
public static object Exchange(ref object, object);

But if you try and call it like:

C#
string mMyProtectedField;
//...
public string MyProtectedProperty
{
    get { return mMyProtectedField; }
    set { Interlocked.Exchange(ref mMyProtectedField, value); }
}

the compiler complains that the best overloaded match having two ints as parameters has at least one invalid argument. With the beta version of .NET 2.0, this problem disappears as the method is generic and thus uses strongly typed parameters.

History

  • 2005-08-07: released initial version to CodeProject.

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


Written By
Chief Technology Officer
Austria Austria
Started with programming about 20 years ago as a teenager. Has been doing software-development for some 16 years now commercially.
Is interested in design and process. Language specifics are sometimes fun, but they're never here to stay.
simply loves MS' new toys, .NET and C#.

Had his own company for a while, creating games for a living Smile | :)

Nowadays more Ronin-style: doing contract work together with a few good friends...

Comments and Discussions

 
QuestionHow (and where) should I forcefully disconnect a new connection? Pin
goondoo279-Jun-10 13:49
goondoo279-Jun-10 13:49 
I'm building a server that should only allow a fixed number of connections (let's say 5). Where in the code should I put a check to limit the number of users that can connect to the server?

I tried it in:

ClientConnection CreateClientConnection(IAsyncResult parameter)
{
Socket socket = m_Listener.EndAccept(parameter);

if(m_iConnectionCount > 5)
{
// Don't allow the connection
return null;
}
Interlocked.Increment(ref m_iConnectionCount);

ClientConnection result = new ClientConnection(socket);

m_ClientConnections.Add(result.EndPoint, result);
if(m_OnConnected != null)
m_OnConnected(result.EndPoint);

StartWaitingForData(result);
WaitForClient();
return result;
}

but that doesn't seem to work.

Any help?
John
GeneralOther Reading... Pin
bob1697216-Aug-06 6:24
bob1697216-Aug-06 6:24 
Generaldemon or daemon Pin
bob1697216-Aug-06 6:12
bob1697216-Aug-06 6:12 
GeneralEasy To Use. Good Work! Pin
RFID Chris1-Aug-06 5:12
RFID Chris1-Aug-06 5:12 
GeneralImplementing fixed-length messages Pin
GuimaSun11-Jul-06 7:52
GuimaSun11-Jul-06 7:52 
GeneralRe: Implementing fixed-length messages Pin
The Marksman11-Jul-06 23:00
The Marksman11-Jul-06 23:00 
GeneralNice one Pin
Vasudevan Deepak Kumar4-Apr-06 5:06
Vasudevan Deepak Kumar4-Apr-06 5:06 
Generaldetection breaking connection Pin
mikepc19-Feb-06 22:01
mikepc19-Feb-06 22:01 
GeneralRe: detection breaking connection Pin
Another Old Guy19-Feb-06 23:39
Another Old Guy19-Feb-06 23:39 
GeneralRe: detection breaking connection Pin
mikepc20-Feb-06 0:14
mikepc20-Feb-06 0:14 
GeneralRe: detection breaking connection Pin
Another Old Guy19-Feb-06 23:41
Another Old Guy19-Feb-06 23:41 
GeneralRe: detection breaking connection [modified] Pin
thepeto7322-May-06 19:53
thepeto7322-May-06 19:53 
GeneralRe: detection breaking connection Pin
grosyann10-Aug-06 10:09
grosyann10-Aug-06 10:09 
GeneralThreading Pin
janpub6-Sep-05 8:56
janpub6-Sep-05 8:56 
GeneralNote about your note Pin
leppie9-Aug-05 20:36
leppie9-Aug-05 20:36 
GeneralRe: Note about your note Pin
Another Old Guy9-Aug-05 21:21
Another Old Guy9-Aug-05 21:21 
GeneralRe: Note about your note Pin
ptmcomp15-Aug-05 7:33
ptmcomp15-Aug-05 7:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.