Click here to Skip to main content
Click here to Skip to main content
Go to top

Custom socket communication

, 13 Dec 2012
Rate this:
Please Sign up or sign in to vote.
Custom socket communication between two .NET projects

Introduction  

This article describes a simple custom socket communication useful in inter-process communication. The attached classes can be used for any kind of data exchange. The communication is client – server, server accepts one or more client connections and sends custom data to all connected clients. The clients can also send some data to the server. 

Using the code   

Server   

The socket server is implemented in a class SocketSender. We start the server using this code:

SocketSender<SocketData,ResponseData> socketSender = new SocketSender<SocketData,ResponseData>(8000, 10000, true);
socketSender.OnClientConnect += new SocketSender<SocketData,ResponseData>.ClientConnect(clientConnected);
socketSender.OnResponseRecieved += new SocketSender<SocketData, ResponseData>.ResponseRecieved(responseRecieved);
socketSender.OnClientRemove += new SocketSender<SocketData, ResponseData>.ClientRemove(socketSender_OnClientRemove);
socketSender.OnCanSend += new SocketSender<SocketData, ResponseData>.CanSend(socketSender_OnCanSend); 

socketSender.Start();

In the constructor, we set the server listening port to 8000 and LifeTickTimeout in ms. LifeTickTimeout is the time between two lifetick packets the server sends to keep connections with clients active.  The third parameter is a simple boolean if SocketSender starts the lifetick timer. We also define the data that the server sends to clients (SocketData class) and the data the clients sends to the server (ResponseData class). We attach the server events: 

  • OnClientConnect: this event is fired when a new client is connected. 
  • OnClientRemove: this event is fired when a client is removed from the list of server clients  
  • OnResponseRecieved: this event is fried when a client sends some response to the server. 
  • OnCanSend: this event is fired before sending data to the client.  

The server sends the data to all clients with:

socketSender.SendData(new SocketData(num, DateTime.Now));

Data is send using a new thread so this does not stop the execution of the main thread. Before send to each client the OnCanSend event is checked.

// after that we send data to all clients
foreach (ComClass<D> com in clients)
{
    Boolean sendData = true;
    // if we have a handler we check with the handler before sending
    if (this.onCanSend != null)
        sendData = this.onCanSend.Invoke(com.RemoteAddress, data);
    // send
    if (sendData)
        com.SendData(data);
} 

In this example the server is sending a simple class with an integer and DateTime data, but the SocketData can be any kind of .NET class that can be serialized to XML.  

Client  

The socket clients are implemented in the SocketReciever class. We initialize the client using this code: 

SocketReciever<SocketData,ResponseData> socketReciver = 
   new SocketReciever<SocketData,ResponseData>("localhost", 8000);
socketReciver.OnConnectionChanged += 
   new SocketReciever<SocketData,ResponseData>.ConnectionChanged(socketReciver_OnConnectionChanged);
socketReciver.OnNewData += new SocketReciever<SocketData,ResponseData>.NewData(socketReciver_OnNewData);
socketReciver.Start(5000);

In the constructor we set the server hostname ("localhost") and the listening port of the server. We attach two event handlers: 

  • OnConnectionChanged: this event is fired when the connection status is changed from connected to disconnected, or back 
  • OnNewData: this event is fired when the clients receive some new data from the server 

The start method accepts the reconnect timeout parameter in ms. This is the time clients wait after disconnect from server to retry connect. If this parameter is 0 - client does not do reconnection. 

As mentioned before, the client can also send some responses to the server using this code: 

socketReciver.SendResponse(new ResponseData(readLine, DateTime.Now));

The response class in this example is a simple class with a String and DateTime data.   

How it works

Client connections  

The server class uses System.Net.Sockets.Socket for accepting client connections. When a new connection occurs the server creates a new class ComClass, sends the TimeOut message to the client, and adds this class on a list of active clients. 

Socket newConnection = sock.Accept();
// new socket is stored in a list of clients
lock (clients)
{
   ComClass<D> newClient = new ComClass<D>(newConnection, clientResponse);
   newClient.SendTimeOut(lifeTickTimeout + TIMEOUT_INC);
   clients.Add(newClient);
   newClient.StartRead();
   log.Debug("Clients: " + clients.Count.ToString());
}

The ComClass starts reading client responses using the method StartRead().

When the server sends data using SendData() - it loops the clients list and calls SendData on all active ComClasses.   

// after that we send data to all clients
foreach (ComClass<D> com in clients)
{
     com.Send(data);
} 

Before sending the list is also checked for dead clients and this clients are removed from the list.

// first we remove dead clients
int i = 0;
while (i < clients.Count)
{
  if (clients[i].IsDead)
  {
      clients.RemoveAt(i);
  }
  else
  {
      i++;
  }
}

Data sending 

The ComClass is also responsible for data sending to a connected client. The data is sent using a StreamWriter. We serialize the data object into XML, and then we calculate the XML length. Data is sent with by writing two lines: 

  • first line: size of data formatted: size=xxxxxx  
  • second line: serialized object in XML  
lock (m_writer)
{
    try
    {
        StringBuilder sBuilder = new StringBuilder();
        StringWriter sWriter = new StringWriter(sBuilder);
        dataSerializer.Serialize(sWriter, (D)data);
        // size tag on begining
        m_writer.WriteLine(CreateSizeString(sBuilder.Length));
        m_writer.WriteLine(sBuilder.ToString());
        m_writer.Flush();
    }
    catch (Exception ex)
    {
        log.Debug(ex.Message);
        this.isDead = true;
        this.Dispose();
    }
} 

Here is the example row data that is written to the socket: 

size=000240
<?xml version="1.0" encoding="utf-16"?>
<SocketData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Number>10</Number>
  <Time>2012-12-08T14:29:56.2034557+01:00</Time>
</SocketData>  

Server also sends two special messages to clients: timeout message and lifetick messages. 

TimeOut message

A timeout message is sent to the client immediately after connecting. Time is specified in ms and on the client side this timeout is used for socket RecieveTimeout. The message is formatted:

Timeout=0012000 

On the server side this timeout interval is the time between two lifetick messages incremented by a second or two (the time needed for sending a lifetick).

LifeTick message

The lifetick message is important to keep the clients connected. The server sends a lifetick in a user defined interval specified in the constructor. The lifetick message is a message with no data so clients receive only the size line with size =0. 

Receiving data

The data is received in an infinite loop in the class SocketReciever:

try
{
    if ((socket == null) || !socket.Connected)
    {
        log.Debug("Socket not connected - connect....");
        // open socket
        socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        // set timeout
        socket.ReceiveTimeout = RECIEVE_TIMEOUT;
        socket.Connect(hostname, port);
        m_reader = new StreamReader(new NetworkStream(socket), Encoding.Unicode);
        m_writer = new StreamWriter(new NetworkStream(socket), Encoding.Unicode);
        // we have connection status change
        if (onConnectionChanged != null)
        {
            onConnectionChanged.Invoke(socket.Connected);
        }
        connected = true;
        // read timeout from server and set it
        socket.ReceiveTimeout = readTimeOut();
    }
    // wait for read new data
    readData();
}
catch (System.Threading.ThreadAbortException)
{
    log.Debug("ThreadAbort exception");
    // end the loop
    break;
}
catch (Exception ex)
{
    // errors on comunication are only logged
    log.Debug("Exception: " + ex.Message);
    socket.Close();
    // we have connection status change
    if ((connected != socket.Connected) && (onConnectionChanged != null))
    {
        onConnectionChanged.Invoke(socket.Connected);
    }
    connected = false;
    // after disconnect we sleep for some time - before we try reconnect
    if (running)
        if (reconnectSleep > 0)
        {
            log.Debug("Reconnect sleep: " + reconnectSleep.ToString() + " ms");
            Thread.Sleep(reconnectSleep);
        }
        else
        {
            log.Debug("Reconnect disabled");
            running = false;
        }
} 

The method readData parses the data received and deserializes the data object: 

private void readData()
{ 
    // read the data - sync with size message
    int size = -1;
    do
    {
        String message = m_reader.ReadLine();
        size = getMessageSize(message);
    }
    while (size < 0);
    char[] buf = new char[BUF_SIZE];
    StringBuilder sb = new StringBuilder();
    while (sb.Length < size)
    {
        int len = m_reader.Read(buf, 0, size - sb.Length);
        sb.Append(new string(buf, 0, len));
    }
    log.Debug("Deserialization");
    StringReader sReader = new StringReader(sb.ToString());
    D sd = (D)dataSerializer.Deserialize(sReader);
    if ((sd != null) && (onNewData != null))
    {
        // send the new data
        onNewData.Invoke(sd);
    }
}

The SocketReciever waits for data for RECIEVE_TIMEOUT and after that closes the connection and tries to reconnect to the server.  

Sending responses 

SocketReciever can also send responses to the server. The communication protocol is the same - two lines with data size and XML data. The sending of responses is done in a new thread:

private void sendResponseInThread(Object data)
{
  if (m_writer != null)
    lock (this.m_writer)
        try
        {
            StringBuilder sBuilder = new StringBuilder();
            StringWriter sWriter = new StringWriter(sBuilder);
            responseSerializer.Serialize(sWriter, (R)data);
            // size tag on message begin
            m_writer.WriteLine(CreateSizeString(sBuilder.Length));
            m_writer.WriteLine(sBuilder.ToString());
            m_writer.Flush();
        }
        catch (Exception ex)
        {
            log.Debug(ex.Message);
        }
}

Conclusion

We wrote this communication object for a digital signage project. This code has worked fine a few years now.

We use some very useful .NET concepts: 

  • .NET Socket
  • XML serialization and deserialization
  • Multi-threading 

The original code was enhanced with a lifetick packet sending an automatic receive timeout setting on the client side. The client also supports reconnection to server after connection loss.

 History 

  • 8/12/2012 - Initial release.
  • 11/12/2012 - Lifetick sending / Timeout sending / Client reconnect.
  • 13/12/2012 - Added OnClientRemove and OnCanSend events 

License

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

Share

About the Author

Damijan Vodopivec
Software Developer
Slovenia Slovenia
Senior C# .NET developer in gaming industry
 
Specialties
C#, XML, WebServices, WCF

Comments and Discussions

 
Questionabout alterations Pinmembermifodii201219-Jun-13 10:18 
QuestionHELP Pinmembermifodii201222-Apr-13 9:34 
AnswerRe: HELP Pinmemberdamijanv22-Apr-13 20:13 

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
Web03 | 2.8.140916.1 | Last Updated 13 Dec 2012
Article Copyright 2012 by Damijan Vodopivec
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid