Click here to Skip to main content
15,867,308 members
Articles / Web Development / HTML

TcpComms Communications Library

Rate me:
Please Sign up or sign in to vote.
5.00/5 (13 votes)
27 Jul 2015CPOL17 min read 24K   1.2K   40   8
Library to enable simple & secure client/server communications

Introduction

This project was inspired by Mehdi Gholam's WCF Killer article and it's foundations are built on it. I have written various versions of this for different projects over the years, all of them just barely doing what was needed in the specific project and finally decided it was time to turn the idea into something re-useable.

Contents

  1. Background
  2. Solution Outline
  3. Using The Demo
  4. Using The Code
    1. Message Headers
    2. Base Class
    3. The Client
    4. The Server
    5. The ClientHandler
    6. The ClientService
    7. The SessionCollection
  5. Points of Interest
  6. History

 

Background

WCF is great, but when you just need a few remote operations in a client/server project going through all the hassle off contracts, creating hosts and clients and configuring it all feels like hard-labour for what you're getting in return. I wanted something that I could drop-in & voila, I could pass objects between a client & server without all that work. Mehdi's WCF Killer is excellent but a bit limited when it comes to 'dropping it in anywhere'. It doesn't support sessions so multiple calls to the server incur the overhead of creating a new connection every time, and if you want to pass more than one type to the server and return more than one possible type you need to go through reflection to figure out what was sent/receved which is bit of a pain and slows things down. Another thing I almost always need is that the communications be secure, so whatever I used should support encryption out of the box without a ton of configuration.

So my requirements are:

  • Must support both sessions and once-off requests: Sessions speed up requests from the same client and enable me to add authentication when needed.
  • Must support both requests that expect a response from the server and fire-and-forget messages from the client.
  • Must support encryption if needed with both client & server authentication being optional.
  • Must provide a simple way for both the client and the server tell the remote side what is being sent so that multiple operation types can be supported simply.

 

Solution Outline

Encryption

To enable encryption the constructor of both the client and server have a useSsl parameter (false by default) which enables-or disables SSL encryption. If you just set useSsl and do nothing else the library uses it's own built-in certificate to enable ssl encryption without worrying about server or client's identity.

On both client and server you have the option to add a validation callback to validate the remote party's certificate if you need server and/or client validation.

If you need server validation supply your own certificate to the serverCert parameter in the server's constructor and a validation callback to the validationCallBack parameter in the client's constructor. Once you've done this the server will use the certificate you supplied for Ssl communications and the client will call your validationCallBack to verify the server's identity during session creation.

If you need to validate clients supply your own certificate to the clientCertificate parameter in the client's constructor and a validation callback to the validationCallback parameter in the server's constructor. Once you've done this the client will send your certificate to the server during session creation and the server will call your validationCallBack to verify the client's identity.

To Session or Not to Session

 By default both client & server use sessions. To disable sessions & close connections directly after a request/response just set the useSession parameter in both the client & server's constructor to false.

To Respond or Not to Respond

All the 'Send' methods in the client have an 'isOneWay' parameter (False by default) that tells the method and the server if a response to the message is expected. If this is set to true the client will wait for a response from the server and the server will submit the request for processing and return a result to the client. If it is set to false the client will send the message without waiting for a response and the server will submit the message for processing and either wait for more messages from the client (if sessions are enabled) or close the connection (if sessions are not enabled). 

What are you sending me

To enable the client and server to understand multiple message types without reflection the Send method on the client has an 'operationId' parameter. This value is passed to the server so that the server can simply check the value of the id and directly cast the received object to the correct type and perform the processing required on it. Similarly when the sever sends a response the response is accompanied by an operation id to tell the client what type of object is being returned. This is quite interesting because it means that the server is not limited to a single response-type for each request, it enables requests like "What is your favourite mode of transport and your favourite brands for that mode?" and responses like "Cars: Ferrari, Mercedes" or "Planes: Boeing, Airbus". There are other ways to do this but it's interesting nonetheless.

To make things simple and legible create an enum in a dll that will be shared between the client and server and define all your operation ids in the enum, when you call Send on the client just cast your enum value to an int in the send method and on the sever cast the int back to your enum before you work with it.

Serialization

Internally Mehdi Gholam's fastBinaryJSON serializer is used to serialize/deserialize the objects being sent/received. I chose to embed his code into the library instead of referencing it to keep everything together in one dll. Under most circumstances you won't need to mess with serialization/deserialization as Mehdi's serializer does a brilliant job. If you do need to provide specific information to the serializer to handle your objects you can use the 'SetJsonParameters' method on the client & server to add your own custom BJSONParameters. For more information about this parameter see Mehdi's article.

 

Using the Demo

The demo contains 2 executables: TestClient.exe and TestServer.exe. Run both programs and then start the server before making requests from the client. The demo apps will let you turn SSL or or off, set some basic server options and send some requests to the server. The test server app has 2 numerical options: Max Sessions and Max Processing Threads. Max Sessions limits the maximum number of connections permitted to the server and Max Processing Threads limits the number of threads the server creates to process requests from clients.

Image 1

 

Using the code

Message Headers

The client and server use slightly different message headers. Both send the length of the serialized data and the operation id but the client also sends a MessageMode flag to tell the server:

  • If the client is configured to use sessions or not, and
  • If the client expects a response from the server

The MessageMode header is also used for keep-alives in a session when it is sent by the client on its own to tell the server it is still connected. More about keep-alives later.

In both cases the header is very small (9 bytes for a header from the client and 8 bytes for a header from the server) so it hardly affects the total message size.

Client Header:

Image 2

Server Header:

Image 3

 

Base Class

Both client & server inherit from a simple base class (NetworkBase) containing a few fields & methods common to both. There's nothing special about it and the source in the code download is well documented.

 

The Client

The client is implemented in the NetworkClient class and provides simple methods to communicate with the server. All configuration properties are provided in the construtor.

Constructor

The client is initialized through a rather bulky contructor. Luckily almost all the parameters are optional and I'd suggest using named parameters when supplying values for optional ones to keep your code simple.

C#
/// <summary>
/// Client Constructor.
/// </summary>
/// <param name="host">Hostname or IP Address of the server to connect to.</param>
/// <param name="useSession">True to maintain sessions, False to close a connection after
///  processing a request.</param>
/// <param name="port">Server port to connect to.</param>
/// <param name="connnectTimeout">Connection Timeout.</param>
/// <param name="sendTimeout">Send/Receive Timeout.</param>
/// <param name="chunkSize">Data packet size.</param>
/// <param name="maxSessionDuration">Maximum time (in milliseconds) that a session can remain open.
///  Only used if useSession = True. Default = 24 hours.</param>
/// <param name="useSsl">True to use secure communications.</param>
/// <param name="serverCert">Server Certificate. Pass null to use the default or supply your own
/// (private key not required).</param>
/// <param name="clientCertificate">Client certificate. Pass null to disable client authentication.</param>
/// <param name="validationCallBack">Callback method to validate the remote server certificate. Pass
///  null to ignore server validation.</param>
public NetworkClient(   string host, 
                        bool useSession = true, 
                        int port = 1111, 
                        int connnectTimeout = 1000, 
                        int sendTimeout = 5000, 
                        int chunkSize = 65536, 
                        int maxSessionDuration = 86400000, 
                        bool useSsl = false, 
                        X509Certificate serverCert = null, 
                        X509Certificate2 clientCertificate = null, 
                        RemoteCertificateValidationCallback validationCallBack = null
)

So to create a minimal SSL client you can initialize it as in:

C#
NetworkClient client = new NetworkClient("localhost", useSsl: true);

Connect Method

The Connect method returns true if the connection was successful. The Connect method will be called automatically if you try to Send without connecting first but in general you should try to stick to the following pattern to keep your code clear:

C#
if (client.Connect())
{ 
      SendSomething... 
      SendSomethingElse... 
}

Most of the method body is self-explanatory and the only interesting bit is the part selecting the stream which either uses a normal NetworkStream or an SslStream

C#
private Stream _ns;
...
if (UseSsl) // If we're using encryption
{
      if (_clientCert == null) // If we haven't set a client certificate then we're not doing client authentication.
      {
             SslStream ssl = new SslStream(_tcpclient.GetStream(), false, _remoteValidationCallback);
             ssl.AuthenticateAsClient(_serverCert.Subject);
             _ns = ssl; //Set the stream to the SslStream
      }
      else //Otherwize we have to do client authentication
      {
             SslStream ssl = new SslStream(
                                           _tcpclient.GetStream(), 
                                           false, 
                                           _remoteValidationCallback, 
                                           SelectLocalCertificate, 
                                           EncryptionPolicy.RequireEncryption);
             ssl.AuthenticateAsClient(
                                           _serverCert.Subject, 
                                           new X509CertificateCollection(
                                                new X509Certificate2[1] { _clientCert }
                                                                        ), 
                                           System.Security.Authentication.SslProtocols.Default, 
                                           false
                                      );
             _ns = ssl; //Set the stream to the SslStream
      }
}
else // If we're not using encryption
{
      _ns = _tcpclient.GetStream(); //Set the stream to the NetworkStream
}
...

Send Method

The public Send method is quite simple (most of the work happens in the private SendPacket and ReceivePacket methods). The only interesting bit is where we set the flags to tell the server how to handle the message:

C#
...
MessageMode mode = MessageMode.None;
if (_useSession) mode = mode | MessageMode.Session;
if (isOneWay) mode = mode | MessageMode.OneWay;
...

SendPacket Method

This method is the 'meat' of sending a message to the server.

C#
/// <summary>
/// Sends a message to the server and optionally waits for a reply.
/// </summary>
///<param name="mode">The MessageMode to send to the server.</param>
///<param name="data">The object to send.</param>
///<param name="s">The NetworkStream or SslStream to use for communications.</param>
///<param name="tcp">The TcpClient to use for communications.</param>
///<param name="operationId">The operation id to send to the server.</param>
///<param name="responseOperationId">If a response is received this will contain the response's
/// operation id.</param>
///<returns>The response object returned by the server or null.</returns>
private object SendPacket(MessageMode mode, object data, Stream s, TcpClient tcp, int operationId, out int? responseOperationId)
{
    lock (_lockObject) // Only 1 sending thread at a time.
    {
        responseOperationId = null;
        // serialize data into buffer
        byte[] databytes = _parameters != null ? BJSON.ToBJSON(data, _parameters) 
                                               : BJSON.ToBJSON(data);
        int len = databytes.Length;
                
        BinaryWriter bw = new BinaryWriter(s);
        bw.Write((byte)mode); // send mode
        bw.Write(len); // send packet header size
        bw.Write(operationId); // send the operation id.
        bw.Flush(); // Write the data to the stream.

        // send data bytes:
        int offset = 0;
        int size = _chunkSize;
        while (len > 0)
        {
            if (len < size)
                size = len;
            s.Write(databytes, offset, size);
            len -= size;
            offset += size;
        }
        s.Flush();
    }
    // Wait for response if we expect one:
    if (!mode.HasFlag(MessageMode.OneWay))
    {
        return ReceivePacket(s, tcp, out responseOperationId);
    }
    return null;
}
  1. We take out a lock to ensure that only 1 thread tries to send using the same client at a time. This is important even in a single-treaded application because if we're using sessions a background timer will try to send keep-alive messages to the server priodically.
  2. We serialize the object being sent and determine the length of the serialized data.
  3. Then, one-by-one, we use a BinaryWriter to send the mode, data-length and operation id to the server.
  4. Once all the header information has been sent we send the serialized data in chucks to maximize throughput.
  5. Lastly, if we're expecting a response we branch to the ReceivePacket method to get the response from the server.

Note that I release the lock before calling ReceivePacket. This is to ensure that if the server takes a long time to process the request the client will continue sending keepalive messages to the server to keep the connection open.

ReceivePacket Method

This method waits for a response from the server and returns the result to the SendPacket method.

C#
/// <summary>
/// Receives a message from the server.
/// </summary>
/// <param name="s">The NetworkStream or SslStream to use for communications.</param>
/// <param name="tcp">The TcpClient to use for communications.</param>
/// <param name="responseOperationId">If a response is received this will contain the response's operation id.</param>
/// <returns>The response object returned by the server or null.</returns>
private object ReceivePacket(Stream s, TcpClient tcp, out int? responseOperationId)
{
    responseOperationId = null;
    BinaryReader br = new BinaryReader(s);
    DateTime dt = DateTime.Now;

    while (tcp.Available < 8) // Wait until we've received a full header.
    {
        Thread.Sleep(1);
        if (DateTime.Now.Subtract(dt).TotalMilliseconds > _sendTimeout)
            return null; // If we've waited longer than the timeout setting give up.
    }
    int len = br.ReadInt32(); // Read the length of the serialized data.
    responseOperationId = br.ReadInt32(); // Read the operation id of the response sent by the server

    if (len > 0)
    {
        // read packet.datalen bytes into buffer
        byte[] data = new byte[_chunkSize];
        using (MemoryStream ms = new MemoryStream())
        {
            int size = _chunkSize;
            while (len > 0)
            {
                int read = s.Read(data, 0, size);
                ms.Write(data, 0, read);
                len -= read;
            }

            ms.Seek(0, SeekOrigin.Begin); //Go to the beginning of the stream
            // deserialize buffer into object
            return _parameters != null ? BJSON.ToObject(ms.ToArray(), _parameters) 
                                       : BJSON.ToObject(ms.ToArray());
        }
    }
    else
    {
        return null;
    }
}
  1. First we wait for a response, if we don't get one in time we give up and return null.
  2. We use a BinaryReader to read the message length and operation id from the header.
  3. We read the serialized data in chunks.
  4. We deserialize the received object and return it.

Other Methods:

The client also has an overloaded Send<TResult, TRequest> method which allows you to send/receive objects that implement the ITcpMessage interface and casts the return value to the correct type for you. This interface simply embeds the operation id into the object so you don't have to pass it seperately. The Send<TResult, TRequest> method works exactly the same as the Send method so further explanation is not really needed.

The only other method is the SetJsonParameters method which (as mentioned previously) allows you to provide BJSONParameters for the internal serializer to use.

 

The Server

The server is implemented in the NetworkServer class and all configuration properties are provided in the construtor.

Callback delegates

The server provides three delegates for you to implement to perform processing:

ProcessPayload

You must implement the ProcessPayload delegate. The server will call this delegate when it receives a message for you to do the processing on the message and optionally return a result. Note that if the client sent the message with the isOneWay flag the server will ignore the result you return to it and not pass it back to the client.

C#
public delegate OperationResult ProcessPayload(Operation data);

OperationResult and Operation are two very simple classes that shouldn't need further explanation, just have a look at the definitions in the source code download.

LogEvent

The second delegate (LogEvent) is optional and if supplied enables logging of events. If you supply a method to this delegate in the constructor's eventLoggingCallback parameter and set the logLevel parameter to anything but EventLoggingLevel.None the server will call your method whenever there is an event that may need logging.

C#
public delegate void LogEvent(EventLogEntryType entryType, string Message, int EventId);
SessionEnded

The third delegate (SessionEnded) is optional and if supplied is used to notify the host application that a session has been closed. This could be useful in scenarios where you're implementing some form of authentication. If you supply a method to this delegate in the constructor's sessionEnded parameter the server will call your method whenever a connection is closed.

C#
public delegate void SessionEnded(long sessionId);

Note that a 'SessionStarted' notification is redundant because you will get a message to process with a session id when the client sends its first request. To check the current connection count you can use the ConnectionCount property.

Constructor

As with the client, the server's constructor is a rather hefty beast but again most of the parameters are optional and if you use named parameters your code can still look clean:

C#
/// <summary>
/// Server Constructor
/// </summary>
/// <param name="processingCallback">Callback method to execute to perform processing on received data.
/// </param>
/// <param name="useSession">True to maintain sessions, False to close a connection after processing a request.</param>
/// <param name="port">Port to listen for connections on.</param>
/// <param name="connnectTimeout">Connection Timeout</param>
/// <param name="sendTimeout">Send/Receive Timeout</param>
/// <param name="chunkSize">Data packet size.</param>
/// <param name="maxProcessingThreads">Maximum number of threads to execute the processingCallback on.
/// </param>
/// <param name="maxConnections">Maximum number of simultaneous connections to the server.</param>
/// <param name="maxSessionDuration">Maximum time (in milliseconds) that a session can remain open.
/// Only used if useSession = True. Default = 24 hours.</param>
/// <param name="useSsl">True to use secure communications.</param>
/// <param name="serverCert">Server certificate for secure communications. Only used if useSsl = True.
/// Omit or pass null to use the default.</param>
/// <param name="validationCallBack">Callback to validate client certificates. Only used if
/// useSsl = True. Omit or pass null to accept any client certificates.</param>
/// <param name="logLevel">Level of events to log.</param>
/// <param name="eventLoggingCallback">Callback to log events. Pass null to disable logging.</param>
/// <param name="sessionEnded">Callback to notify host application that a session has ended.
/// Pass null if you don't need notification.</param>
public NetworkServer(ProcessPayload processingCallback, 
                    bool useSession = true, 
                    int port = 1111, 
                    int connnectTimeout = 1000, 
                    int sendTimeout = 5000, 
                    int chunkSize = 65536, 
                    int maxProcessingThreads = 10, 
                    int maxConnections = 20, 
                    int maxSessionDuration = 86400000, 
                    bool useSsl = false, 
                    X509Certificate2 serverCert = null, 
                    RemoteCertificateValidationCallback validationCallBack = null, 
                    EventLoggingLevel logLevel = EventLoggingLevel.None, 
                    LogEvent eventLoggingCallback = null,
                    SessionEnded sessionEnded = null
)

As with the client to create a minimal SSL server you can initialize it as in:

C#
NetworkServer server = new NetworkServer(myProcessingCallback, useSsl: true);

Start Method

The Start method is pretty straightforward and well-documented so I won't go into it except to note that I marked the method as async and used await when listening for connections rather than using BeginAcceptTcpClient as the resulting code is a bit cleaner. This is the only part of the code that really requires .Net 4.5 or later so if you need to run this under an older version of .Net you'll have to re-work the Start method to use BeginAcceptTcpClient.

Once a connection is received it is passed to the HandleClientConnection method (see the code download for details) which creates a ClientHandler to handle communications with the client. The method tries to add the connection to the connection/session list and if successfull starts communicating with the client. If the maximum number of permitted connections has been reached we just stop here and disconnect the new client.

Other methods

The other methods in the NetworkServer class are ReceivePacket, SendPacket, OnLogEvent, OnSessionEnded and SetJsonParameters. ReceivePacket and SendPacket are essentially the same as the corresponding methods in the client, I've already described them and the code in the download is well documented so I won't go through them again here. Just remember that the header sent by the server to the client is slightly different to the one sent by the client. The OnLogEvent method simply calls your LogEvent method and is also pretty straightforward. SetJsonParameters enables you to provide BJSONParameters for the internal serializer to use if needed. OnSessionEnded simply calls the method you provided to get notified when a session is closed.

 

The ClientHandler

The ClientHandler class handles all communications with the client. One instance is created for each connection and maintained for the life of the connection. The class is nested within the NetworkServer class so has access to its private properties and methods. It uses the server's ReceivePacket, SendPacket, OnLogEvent and OnSessionEnded methods and adds processing tasks to the server's task queue.

The contructor for the ClientHandler has logic similar that of the the Connect method of the client to select between a NetworkStream and SslStream and perform the neccesary certificate validation in the case of an SslStream. I won't go into the details here as I've already described most of it earlier.

Start Method

The ClientHandler's Start method is where most of the work is done:

C#
/// <summary>
/// Starts communications with the client.
/// </summary>
internal void Start()
{
    while (Alive)
    {
        int? requestOperationId;
        MessageMode mode;
        try
        {
            // Try to receive a message from the client:
            object o = _server.ReceivePacket(_ns, _tcpClient, out requestOperationId, out mode);
            // If we got a keepalive message adjust the timeout setting and start receiving again:
            if (mode == MessageMode.KeepAlive)
            {
                _lastKeepAlive = DateTime.Now;
                continue;
            }
            // If we got a message from the client add the processing job to the server's queue,
            // adjust the timeout and continue. If we're not using sessions and the message was
            // marked as one-way we'll just close the connection after queueing the job:
            else if (o != null)
            {
                _server._processingTasks.Enqueue(new ProcessingTask() 
                                                 { 
                                                   Data = o, 
                                                   Handler = this, 
                                                   Mode = mode, 
                                                   OperationId = requestOperationId.Value 
                                                  });
                _lastKeepAlive = DateTime.Now;
                if (!_server._useSession && mode.HasFlag(MessageMode.OneWay))
                {
                    Close();
                    return;
                }
            }
            // If we've exceeded the timeout limit we close the connection:
            if (_lastKeepAlive.AddMilliseconds(
                                                _server._sendTimeout * 2) < DateTime.Now 
                                                & !_hasPendingResponse
                                              )
            {
                Close();
                return;
            }
        }
        catch (SocketException sox)
        {
            Close();
            _server.OnLogEvent(EventLogEntryType.Error, 
                               "Client communications error. Connection closed.\r\n{0}\r\n{1}", 
                               1006, 
                               EventLoggingLevel.Normal, 
                               sox.Message, 
                               sox.StackTrace
                              );
        }
        catch (Exception ex)
        {
            _server.OnLogEvent(EventLogEntryType.Error,
                               "Client communications error.\r\n{0}\r\n{1}", 
                               1006,
                               EventLoggingLevel.Normal,
                               ex.Message,
                               ex.StackTrace
                              );
        }
    }
}

 What we're doing here is:

  1. Wait for a message from the client (we wait as long as the sendTimeout setting),
  2. If we received nothing and the timeout has expired we close the connection,
  3. If we received a keep-alive message we adjust the timeout setting and carry on waiting,
  4. If we got a message we submit it to the server's procesing queue and either continue listening (if we're using sessions) or close the connection (if we're not using sessions and it was a one-way message).

 Other Methods

SendResponse and Close are very simple methods that shouldn't need explaining. The SendResponse method is called from the ClientService class when the server needs to send a response to the client and the Close method is called to terminate communications with the client and clean up the session.

 

The ClientService

The ClientService class manages the processing of client tasks and periodically checks the connection list for stale connections and purges them. Like the ClientHandler class it is also nested withing the NetworkServer class so that it can access it's private members. It creates a number of threads that will each continiously try to get a job from the processing queue, submit it for processing and return a result to the client by calling the SendResponse method on the appropriate ClientHandler if required. It also creates a seperate thread to periodically monitor and clean up the connection list. The methods in the class are pretty straight-forward and well documented and not woth exploring here.

 

The SessionCollection

The SessionCollection is a simple class to hold a thread-safe list of active connections. No direct external access to the list is possible. Thee methods are provided to manipulate the list:

  1. TryAdd will attempt to add a connection to the list,
  2. TryRemove will attempt to remove a connection from the list, and
  3. Scavenge is used by the ClientService to remove stale connections.

Each of these methods have a timeout parameter which tells the method for how long it should try to perform its function. If the method can't perform it's intended action within that time it will give up and in the case of TryAdd and TryRemove will return false. TryAdd will not add a connection if the _maxConnections limit has been reached.

 

Points of Interest

Configuration and Optimization

The client and server should both use the same values for useSession, port, connnectTimeout, sendTimeoutuseSsl and chunkSize. You can play with the chunkSize property to optimize throughput by matching it to the actual packet size on your network. The sendTimeout affects how long the server & client will wait for a message from the remote side. You should make it as small as possible while making sure that you don't end up in a situation where the server takes a long time to process a request and the client gives up waiting prematurely. The higher you set this the longer the server will both wait for a message and wait before closing an apparrent inactive connection. Setting the sendTimeout too low will cause the cpu load on the server to increase. Setting it too high will mean there will be an excessive delay in detecting closed connections and you may end up hitting the maxConnections limit because you have a bunch of dead connections that haven't been detected/closed yet.

On the server the maxProcessingThreads parameter limits the number of threads the server creates to call your ProcessPayload callback. In an ideal world this would be set to the same value as maxConnections but you'll have to tailor it to the capabilities of your hardware and the number threads you are creating in your own application to optimize performance. It won't help performance if the total number of active threads start swamping your Cpu.

Keep-Alives

Initially I tried to use built-in TCP keepalive messages to manage the detection of session-termination but this proved unreliable so I ended up adding a keepalive message type to the protocol. The client will send a 1-byte keepalive message to the server at an interval that is half the configured send timeout. If sessions are used the server will keep the connection/session open as long as it is still receiving these periodic keep-alive messages from the client. So far in testing this approach has proved reliable.

Custom Authentication

Every processing job passed to your ProcessPayload callback includes a unique session id. To implement authentication just create a 'Login' type and make that the first message you send to the server. You can then check each incoming task's session id and compare it to id's for which a login was performed successfully. You can use the optional SessionEnded delegate to get a notification when the client's connection is closed.

Session Ids

I chose to use a long rather than Guid for the session id as operations on a long are faster than on a Guid. This however creates the possibility (albeit highly unlikely) of an overflow occurring if the server runs for an extremely long time and handles a physically-unattainable number of new sessions per second. With 18 446 744 073 709 551 616 possible values it is almost impossible for anyone to run into this potential overflow (it would take over 584 942 417 years at a 1000 new sessions per second before reaching the limit).

To Do

There is no real reason why the server can't support both clients that want to use sessions and clients that don't at the same time, I just haven't gotten around to changing that. The server's SendPacket and ReceivePacket methods should probably logically be moved to the ClientHandler class.

 

History

  1. 2015-07-27: First version.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
South Africa South Africa
I started programming when my father got me a quirky thing with both a Z80A and 6502 processor, a basic interpreter, an Assembly compiler and not much else. Through the years I went through everything from Basic, Pascal, Delphi, C, C++, VB and everything .Net.

Comments and Discussions

 
Generalvisual basic Pin
Olivia Nasekeito27-Feb-17 21:49
Olivia Nasekeito27-Feb-17 21:49 
QuestionDownload demo link is not working :( Pin
Tridip Bhattacharjee6-Aug-15 21:45
professionalTridip Bhattacharjee6-Aug-15 21:45 
AnswerRe: Download demo link is not working :( Pin
dgb776-Aug-15 21:50
dgb776-Aug-15 21:50 
GeneralMy vote is Excellent Pin
MrVitaly28-Jul-15 7:10
MrVitaly28-Jul-15 7:10 
GeneralMy vote of 5 Pin
Fernando Bravo27-Jul-15 6:36
Fernando Bravo27-Jul-15 6:36 
GeneralMy vote of 5 Pin
Mike (Prof. Chuck)27-Jul-15 6:09
professionalMike (Prof. Chuck)27-Jul-15 6:09 
GeneralRe: My vote of 5 Pin
dgb7727-Jul-15 22:41
dgb7727-Jul-15 22:41 
GeneralRe: My vote of 5 Pin
Mike (Prof. Chuck)28-Jul-15 6:21
professionalMike (Prof. Chuck)28-Jul-15 6:21 
My Team is currently load testing our system for a larger platform we write and at the moment we are at 10k clients per server, communicating bi-directional and broadcasting as hell and the server is still quite bored...
we are testing the limits how far we can go, but 20k doesn't seem to be unreachable.

The system is closed source, I can't post it here, but as a rough number and as a target for your testing, if you can spare 1GB of RAM, your server can handle around 1-2k clients with ease if coded right. CPU is not the point here, it's the memory allocation.

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.