Click here to Skip to main content
Click here to Skip to main content

TcpDemon

, 9 Aug 2005
Rate this:
Please Sign up or sign in to vote.
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:
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:

    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:

    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:

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

Another one uses a ReaderWriterLock:

    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:

public static object Exchange(ref object, object);

But if you try and call it like:

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

Share

About the Author

Another Old Guy
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? Pinmembergoondoo279-Jun-10 13:49 
GeneralOther Reading... Pinmemberbob1697216-Aug-06 6:24 
Generaldemon or daemon Pinmemberbob1697216-Aug-06 6:12 
GeneralEasy To Use. Good Work! PinmemberRFID Chris1-Aug-06 5:12 
GeneralImplementing fixed-length messages PinmemberGuimaSun11-Jul-06 7:52 
GeneralRe: Implementing fixed-length messages PinmemberThe Marksman11-Jul-06 23:00 
GeneralNice one PinmemberVasudevan Deepak Kumar4-Apr-06 5:06 
Generaldetection breaking connection Pinmembermikepc19-Feb-06 22:01 
GeneralRe: detection breaking connection PinmemberMax Hajek (aka AzazelDev)19-Feb-06 23:39 
GeneralRe: detection breaking connection Pinmembermikepc20-Feb-06 0:14 
GeneralRe: detection breaking connection PinmemberMax Hajek (aka AzazelDev)19-Feb-06 23:41 
GeneralRe: detection breaking connection [modified] Pinmemberthepeto7322-May-06 19:53 
GeneralRe: detection breaking connection Pinmembergrosyann10-Aug-06 10:09 
GeneralThreading Pinmemberjanpub6-Sep-05 8:56 
GeneralNote about your note Pinmemberleppie9-Aug-05 20:36 
GeneralRe: Note about your note PinmemberMax Hajek (aka AzazelDev)9-Aug-05 21:21 
GeneralRe: Note about your note Pinmemberptmcomp15-Aug-05 7:33 

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

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

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 10 Aug 2005
Article Copyright 2005 by Another Old Guy
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid