Introduction

In this article, I will explain the implementation of an Open Source lightweight framework (named Simple Client Server Library (SCS)) that is developed to create client/server applications using a simple Remote Method Invocation mechanism over TCP/IP. It is entirely developed in C# and .NET Framework 4.0.

This is the second article about SCS and explains how I did it. To learn what SCS is and how to use it, please see the first article at the link below. There are two running sample applications upon SCS in the first article. I suggest you to take a look at the first article before reading this one:

Article Outline

What is TCP?

TCP (Transmission Control Protocol) is a transport layer protocol in the TCP/IP protocol suite (another common transport protocol in TCP/IP is UDP (User Datagram Protocol)). It is a layered protocol suite that uses a strong DoD model. DoD is a four layered model that consists of Link Layer, Internet Layer, Transport Layer, and Application Layer. TCP and IP protocols are the core protocols of TCP/IP (therefore its name TCP/IP). Here is a list of some common protocols in TCP/IP layers from top to down:

  • Application Layer: HTTP, FTP, SMTP...
  • Transport Layer: TCP, UDP...
  • Internet Layer: IP, ARP, ICMP...
  • Link Layer: Ethernet, Token Ring...

Also, all your TCP based applications (as SCS framework) stands on Application Layer. There are many other protocols on each layer.

TCP provides reliable, ordered delivery of a stream of bytes from a program on one computer to another program on another computer. TCP is a connection-oriented, asynchronous, and double-way communication protocol (see figure below). Let's explain these concepts:

  • Ordered delivery of a stream of bytes: In TCP, applications communicate over byte streams. Minimum data transfer size is one byte. The first sent byte first reaches the destination (FIFO queue, ordered delivery). There are two independent communication streams (double-way communication). Applications can send data anytime, independent from each other (asynchronous communication).
  • Reliability: TCP uses a sequence number to identify each byte of data. The sequence number identifies the order of the bytes sent from each computer so that the data can be reconstructed in order, regardless of any fragmentation, disordering, or packet loss that may occur during transmission. So, if the physical connection with the remote application does not fail and you don't get an exception, your data is almost guaranteed to be delivered. Event if a single byte of your data is not delivered (because of an error), subsequent bytes are not delivered before it. If your data reaches destination, you can be sure that it is not corrupted and not deficient.

Stream based TCP communication

Stream based double-way TCP communication

Like all communication protocols, in TCP, each application has a unique address. This address consists of an IP address and a TCP port.

IP address (for IPv4) is a four octet number separated by dots, like 85.112.23.219. All computers that are connected to the internet have an IP addresses. (If you are connected to the internet behind a router etc., you may not have a public IP address, but your router has one and your computer has a local network IP, thus the router dynamically uses NAT to allow you to use TCP/IP. Anyway, a remote application can send IP datagrams to your computer.)

TCP port is just a number (2 byte unsigned integer, therefore a TCP port can be 0 to 65536) that allows TCP to distinguish your application from other applications in the same computer.

The last TCP concept that I will mention about is the socket. A socket is an interface to send data to and receive data from the TCP/IP stack that is provided by the Operating System. In .NET, a socket is represented by a Socket object. A Socket object is used to send/receive data between two TCP endpoints. A special kind of socket is the Listener Socket. In server/client architecture, the server first opens a TCP listener socket to allow clients to connect to the server. After communication is established with the client, a dedicated socket object is created with the client and all communication with that client is made on this new socket.

Why TCP?

Communication between two applications is generally called as IPC (Inter-Process Communication). There are many ways of IPC. TCP/IP is one of them. The advantage of TCP/IP is that the communicating applications can be running on different computers and different locations. Since Internet also works on TCP/IP, remote applications can communicate over the internet. TCP/IP is completely platform independent, standard, and implemented by all Operationg Systems (and many other devices). Also, it can be used for communication of applications that is on the same computer. Surely, other techniques such as Named Pipes, Shared Memory can be used for communication of two applications, and they are faster than TCP/IP. But they have an important restriction that the two applications must run on the same computer and can not communicate over the internet. By the way, the SCS framework is not directly dependent on TCP, it can be extended to support other IPC techniques.

What are the stages of building a communication framework upon TCP?

In this section, I will discuss how to make a plan to build a communication framework over TCP and the problems to be considered.

Stream based data transfer

From the perspective of TCP, everything that is being sent from an application to another are bytes of a stream. TCP does not know if it is bytes of a picture, a file, a string or a serialized object. Also TCP does not know if a data (say a serialized object) has been sent and we are now sending another data.

Messaging over streams

From the application perspective, the data transferred over a TCP stream are separated packets. I call it messages. An application wants to send messages to another application when needed. Also, an application wants to be informed when a new message is received from a remote application.

So, we must build a mechanism over TCP streams to write a message to a stream and read a message from a stream. We must provide a way of separating messages from each other.

Wire protocol (message to stream protocol)

The way of writing a message to a stream and reading a message from a stream is called wire protocol. A wire protocol has some knowledge about the message that is being sent and received. In .NET, if we represent a message by an object, we can use serialization to build a byte array from the object. We can use binary serialization, XML serialization (and then string to byte array encoding), or a custom serialization. Anyway, after creating a byte array from a message, we can write it to the TCP stream. Also, we can use a kind of deserialization to create an object from the TCP stream.

If we create a custom wire protocol that can be implemented in any other programming language or platform, we can build a platform independent system. But if we use .NET binary serialization, our application can only be used in .NET world.

Request/Reply style messaging

In most scenarios, an application sends a message to a remote application and waits for a response message. Because of the asynchronous nature of TCP, this is an important implementation detail. In this case, we must use multithreading tools (such as ManualResetEvent class in .NET) to block/wait a sender thread until a response is received and to notify when a response is received. We also must provide a way of matching request and reply messages. Last but not least, we must provide a timeout mechanism if the remote application does not send a response message within a specified time.

Mapping messages with classes and methods

This is the most important and distinctive feature of a TCP based communication library from a user (that uses the framework to build applications) perspective. Many different approaches can be preferred according to the needs of the user, capabilities of the programming language or platform etc... Let's discuss a few approaches here.

The first approach is message-oriented communication. In this way, the sender application sends an object from a special type of class using serialization (as mentioned before), and the receiver application deserializes the object, and enters a switch or if statement to call a method according to the properties of the object. Message classes can be derived from a base class and can define common methods. Even, message objects can be a simple string. Applications parse this string and performs the needed operations. The disadvantage of this approach is that the user of the library must define his own messaging logic and write switch statements. If a new message type is added to the system, many classes, methods, or code blocks must be changed. Also, it is not type safe, and messages are resolved at runtime. So an application can send unknown message types etc...

An extending to messaging can be accomplished using Reflection. Thanks to .NET which allows Reflection, we can send a message that will cause a method call on the remote application using Reflection. Assume we have a message class like (really, it is a class in the SCS framework):

/// <summary>
/// This message is sent to invoke a method of a remote application.
/// </summary>
[Serializable]
internal class ScsRemoteInvokeMessage : ScsMessage
{
    /// <summary>
    /// Name of the remove service class.
    /// </summary>
    public string ServiceClassName { get; set; }

    /// <summary>
    /// Method of remote application to invoke.
    /// </summary>
    public string MethodName { get; set; }

    /// <summary>
    /// Parameters of method.
    /// </summary>
    public object[] Parameters { get; set; }
}

In the sender application, you can create an object from this class, set the appropriate class, method name, and parameter list, serialize it, and send it over the TCP socket. Then, in the receiver application, you can find the right class, create an instance of this class (or you can use a pre-created object from this class), and call the right method by the received parameters using Reflection techniques. With this approach, there is no need to modify our communication layer to add new methods. Simply add a new method to your business class as you usually do, that's all. If the sender application sends a message with a new method name, your method is called automatically without changing anything. You can also return another message to the sender that contains the return value of the method call.

All right, this way, we have created a good communication layer in the receiver side. But, the sender application still must know the name of the class, method, parameters.. And also, it must create the message object and send it for all method calls. For sure, you can write a proxy class that has the same method signature as the target class. Your application calls this proxy class methods, it internally sends the right messages to the remote application, waits for response messages, and returns the values that are received from the remote application. This is really nice, but you must do that for all new methods or method changes on the target application. It will be a tedious work. So, maybe you can write an application that does this proxy class creation job for you using Reflection. In this case, you must re-create your proxy class for all changes on the target application service signature (Web Services and WCF services do exactly that).

The SCS framework provides a better approach to call methods on remote applications. It uses a dynamic proxy mechanism using the RealProxy class and Reflection. This is one of the key points of SCS. This will be explained in the implementation of SCS.

Server/client architecture and multithreading

Server Client Artitecture

Simple TCP server/client architecture

Writing bytes to a socket is a blocking operation, so while sending a message to a remote application, the thread is blocked. Also, receiving a message (even receiving a single byte) from a socket is a blocking operation. The thread is blocked until the remote application sends bytes. If the remote application does not send data, the thread is blocked until the connection is closed. So, if we want to create a server application that serves multiple clients simultaneously (certainly we want), we must make use of multithreading. A dedicated thread is needed to receive messages from clients and a thread is needed to accept new connections on the listener socket. So, we must have (client count + 1) threads at least (and additional threads to do the business of our application if needed). As you can see in the figure above, the server application has a socket object for each client; additionally, it has a single listener socket object that is used to handle new connection requests from clients.

Implementation of the SCS framework

After brief discussion on developing a TCP based Server/Client communication framework, now i will explain how i implemented SCS framework. SCS framework is consist of two layers as shown below.

SCS Layers

Layers of the SCS framework

Messaging Layer

The Messaging Layer is responsible for sending messages (objects) to and receiving messages from a remote application. It is independent from the Remote Method Invocation Layer, and can be used separately for building message oriented communicating applications. It also allows you to define your own wire protocol (so your application can communicate with non-.NET applications). All classes in the Messaging Layer are defined in the Hik.Communication.Scs namespace (and it's sub namespaces).

Messages

All messages that are sent or received implements the IScsMessage interface. The ScsMessage class implements it and all message classes are derived from ScsMessage.

Message Classes

Message classes in the SCS Messaging Layer

The ScsMessage class has two string properties that are key properties for the SCS framework: MessageId and RepliedMessageId. MessageId is a unique string (it is an auto generated GUID) that is used to distinguish message objects from each other. RepliedMessageId is null by default. If this message is a reply to another message, it contains the MessageId of the original message. This property is used to match request messages with reply messages. ScsMessage does not contain any data.

ScsTextMessage can be used to send/receive simple string messages. ScsRawDataMessage can be used to send/receive byte arrays. Using ScsRawDataMessage, you can send/receive a picture, file, a serialized .NET object... etc. PingMessage has no additional data, and is used by the SCS framework to send Ping messages from the client to the server to keep the TCP connection alive and to check whether the connection is alive or not. It is an internal class and can not be used by user code.

You can also write your own class that is derived from ScsMessage (or its subclasses) to send/receive your own objects. Surely you can directly implement the IScsMessage interface instead of deriving from the ScsMessage class. If you decide to send/receive objects from your own classes, you must mark the class as Serializable. Also, it is a good method to define your message class in a separate class library and to add a reference of this library from both the server and client applications.

Wire protocols

A wire protocol is used to serialize and deserialize messages to send/receive over TCP streams. All wire protocol classes in the SCS framework must implement the IScsWireProtocol interface.

Wire Protocols

Wire protocol classes in the SCS Messaging Layer

The ReadMessage(...) method creates a message object from a given stream, and the WriteMessage(...) method writes a message to a stream. As you see in the figure above, currently, the only wire protocol defined in the SCS framework is BinarySerializationProtocol. It uses the .NET BinaryFormatter class to serialize/deserialize objects. You can implement your own wire protocol and set it to your client and server objects (will be explained later).

EndPoints

As I mentioned before (in the What is TCP section), an end point is used to define an address for your application. In SCS, all end point classes must be derived from the ScsEndPoint abstract class.

End Points

End point classes in the SCS framework

The ScsEndPoint class defines two important internal abstract methods to create a server (that listens to this end point for incoming client connections) and to create a client (that connects to a server that listens to this end point). I will return to these methods later. ScsEndPoint also defines a static CreateEndPoint(...) method to create an end point object from a string address. So, to create a ScsTcpEndPoint object for the 127.0.0.1 IP address and 10048 TCP port, you can pass the "tcp://127.0.0.1:10048" string to the CreateEndPoint(...) method. Surely, you can directly create a ScsTcpEndPoint object. As you see, the only class that is derived from ScsEndPoint is ScsTcpEndPoint. It has two properties: IpAddress and TcpPort.

Connection listeners

Connection listeners are used to listen and accept incoming client connections by the server. They create a communication channel (will be explained in the next section) with the new client.

Connection Listeners Diagram

Connection Listener classes in the SCS framework

As you can see in the class diagram, the IConnectionlistener interface defines two methods to Start/Stop the listener, and an event named CommunicationChannelConnected that is raised when a new client connected to the server and communication channel is successfully created. The only concrete connection listener class is TcpConnectionListener. Some important parts of the TcpConnectionListener class are shown below:

/// <summary>
/// This class is used to listen and accept
/// incoming TCP connection requests on a TCP port.
/// </summary>
internal class TcpConnectionListener : ConnectionListenerBase
{
    /// <summary>
    /// Listening TCP port number.
    /// </summary>
    public int Port { get; private set; }

    /// <summary>
    /// Server socket to listen incoming connection requests.
    /// </summary>
    private TcpListener _listenerSocket;
    
    /// <summary>
    /// Creates a new TcpConnectionListener with given tcp port.
    /// </summary>
    /// <param name="tcpPort">Listening TCP port</param>
    public TcpConnectionListener(int tcpPort)
    {
        Port = tcpPort;
    }

    /// <summary>
    /// Starts listening incoming connections.
    /// </summary>
    public override void Start()
    {
        StartSocket();
        _running = true;
        _thread = new Thread(DoListenAsThread);
        _thread.Start();
    }

    /// <summary>
    /// Starts listening socket.
    /// </summary>
    private void StartSocket()
    {
        _listenerSocket = new TcpListener(System.Net.IPAddress.Any, Port);
        _listenerSocket.Start();
    }

The TcpConnectionListener class defines a constructor that takes a port number to listen. In the Start() method, it creates and starts a TcpListener (System.Net.Sockets.TcpListener) object with the specified port number and then starts a separate thread to listen and accept new clients. The DoListenAsThread method is defined as below:

/// <summary>
/// Entrance point of the thread.
/// This method is used by the thread to listen incoming requests.
/// </summary>
private void DoListenAsThread()
{
    while (_running)
    {
        try
        {
            var clientSocket = _listenerSocket.AcceptSocket();
            if (clientSocket.Connected)
            {
                OnCommunicationChannelConnected(
                   new TcpCommunicationChannel(clientSocket));
            }
        }
        catch
        {
            //Disconnect, wait for a while and connect again.
            StopSocket();
            Thread.Sleep(1000);
            if (!_running)
            {
                return;
            }

            try
            {
                StartSocket();
            }
            catch
            {

            }
        }
    }
}

The DoListenAsThread method calls the AcceptSocket() method of the TcpListener object (_listenerSocket). This is a blocking method, so the thread is blocked until a new client is connected to the server. It continues when a client is connected, checks if the socket is properly connected, then raises the CommunicationChannelConnected event using the OnCommunicationChannelConnected method. It creates a new TcpCommunicationChannel (will be examined in the next section) object and passes it as an argument to the OnCommunicationChannelConnected method (it is defined in the ConnectionListenerBase class as protected). You can take a look at CommunicationChannelEventArgs (that is used as an argument in the event) in the SCS framework source code.

Communication channels

You have seen how a communication channel is created in the server side. Also, channels are created in the client side to connect to a server (we will see that later). In SCS, all communication between applications (server-to-client and client-to-server) are made on communication channels. Channel objects are responsible for sending message objects to a remote application over the network. They are also responsible to listen and receive incoming messages from a remote application. The communication channel hierarchy in SCS is shown in the figure below.

Communication channels in the SCS framework

All communication channels must implement the ICommunicationChannel interface or inherit from the CommunicationChannelBase abstract class. CommunicationChannelBase implements ICommunicationChannel and provides a good template for concrete communication channel classes.

As you can see, the ICommunicationChannel interface extends the IMessenger interface. The IMessenger interface represents an object that can send and receive messages. The SendMessage(...) method defines the method to send a message to a remote application. MessageReceived defines the event that is raised when a new message is received from a remote application (messages are received by events). IMessenger also defines the WireProtocol property that can be used to change the wire protocol (as mentioned before). Other properties explain themselves.

ICommunicationChannel inherits IMessenger and adds some functionality. It defines a property named CommunicationState to learn whether a channel is connected to a remote application or not. It also defines the Start() and Disconnect() methods to start/stop communication. Lastly, if defines the Disconnected event that is raised when a channel is closed/fails.

The only concrete communication channel class is TcpCommunicationChannel in SCS. It is derived from CommunicationChannelBase. I'll examine this class to see how the communication channel is implemented using TCP. It defines just one constructor and gets a Socket (System.Net.Sockects.Socket) object.

/// <summary>
/// Creates a new TcpCommunicationChannel object.
/// </summary>
/// <param name="clientSocket">A connected Socket
///     object that is used to communicate over</param>
public TcpCommunicationChannel(Socket clientSocket)
{
    _clientSocket = clientSocket;
    _clientSocket.NoDelay = true;
    _networkStream = new NetworkStream(_clientSocket);
    _syncLock = new object();
}

As you saw in the Communication listeners section, this constructor is used by the TcpConnectionListener class when a new client is connected to the server. It creates a NetworkStream (System.Net.Sockets.NetworkStream) object to read to and write from a socket. Communication is started by the Start method (it is called by the server and client objects as we will see later). In the Start method, a separate thread is started that runs the ListenMessages method.

/// <summary>
/// This is the thread method to listen and receive incoming messages.
/// </summary>
private void ListenMessages()
{
    try
    {
        while (_running)
        {
            OnMessageReceived(WireProtocol.ReadMessage(_networkStream));
        }
    }
    catch
    {

    }

    OnDisconnected();
}

The ListenMessages method almost consists of a single line of code. It reads a message from the Socket using the current wire protocol object and raises the MessageReceived event (_running is a flag to control the thread's running and is set in the Start/Disconnect methods). The MessageReceived event supplies the received message object (see source code). The WireProtocol property is set by the server and client objects as we will see later. Also, if an exception occurs or a channel is closed, the Disconnected event is raised. Now, let's see how a message is being sent. The ICommunicationChannel.SendMessage(IScsMessage message) method is implemented by the CommunicationChannelBase class as shown below:

/// <summary>
/// Sends a message to the remote application.
/// </summary>
/// <param name="message">Message to be sent</param>
public void SendMessage(IScsMessage message)
{
    SendMessageInternal(message);
    LastSentMessageTime = DateTime.Now;
}

It sends a message using the SendMessageInternal(...) method, then sets LastSentMessageTime as Now. SendMessageInternal(...) is an abstract method and is implemented by the TcpCommunicationChannel class as shown below:

/// <summary>
/// Sends a message to the remote application.
/// </summary>
/// <param name="message">Message to be sent</param>
protected override void SendMessageInternal(IScsMessage message)
{
    //Create a byte array from message
    var memoryStream = new MemoryStream();
    WireProtocol.WriteMessage(message, memoryStream);
    var sendBuffer = memoryStream.ToArray();

    //Send message
    var totalSent = 0;
    lock (_syncLock)
    {
        while (totalSent < sendBuffer.Length)
        {
            var sent = _clientSocket.Send(sendBuffer, totalSent, 
                                          sendBuffer.Length - totalSent, 
                                          SocketFlags.None);
            if (sent <= 0)
            {
                throw new Exception("Message can not be sent via TCP socket. Only " + 
                      totalSent + " bytes of " + sendBuffer.Length + 
                      " bytes are sent.");
            }

            totalSent += sent;
        }
    }
}

It first serializes the message (IScsMessage) object to a byte array using the WireProtocol object and a MemoryStream (System.IO.MemorySteam), then sends it to a remote application using the socket object (_networkStream is not used, the byte array is directly sent by the Socket object). Sending a message is done in a lock statement. Thus, if more than one thread wants to send messages, only one of them can send at a time; the others wait until the first message is successfully sent.

Client side

We have seen how a communication channel is created (by a connection listener in the server side) and how a message is sent and received by a communication channel. Now I will examine the SCS client objects that are used to connect to and communicate with the server. The main interface to use in the client side is IScsClient.

Client classes in the SCS framework

The IScsClient interface inherits IMessenger and IConnectableClient, and does not define any additional members. We have examined IMesenger before (in the Communication channels section). IConnectableClient is used to control the connection of the client to the server. It looks like the ICommunicationChannel interface but the implementation and purpose are a little different. It defines the Connect() method to connect to a server (instead of the Start() method of channels), and the Disconnect() method to close a connection. It defines two important events: Connected and Disconnected. They can be used to monitor the connection state of the client. Finally, it defines the CommunicationState property (same as channels).

The ScsClientBase abstract class provides a good template to implement IScsClient. Almost all implementations are made in this class. So, I will first examine the ScsClientBase class. Let's look at two properties here:

/// <summary>
/// Gets/sets wire protocol that is used while reading and writing messages.
/// </summary>
public IScsWireProtocol WireProtocol
{
    get { return _wireProtocol; }
    set
    {
        if (CommunicationState == CommunicationStates.Connected)
        {
            throw new ApplicationException("Wire protocol can not " + 
                      "be changed while connected to server.");
        }

        _wireProtocol = value;
    }
}
private IScsWireProtocol _wireProtocol;

/// <summary>
/// Gets the communication state of the Client.
/// </summary>
public CommunicationStates CommunicationState
{
    get
    {
        return _communicationChannel != null
                   ? _communicationChannel.CommunicationState
                   : CommunicationStates.Disconnected;
    }
}

As you can see, WireProtocol can only be changed while not connected to the server. CommunicationState directly gets the CommunicationState of the underlying channel object. The LastReceivedMessageTime, LastSentMessageTime properties, and the SendMessage(...) and Disconnect() methods are also directly mapped to the underlying channel object. The constructor of ScsClientBase is shown below:

/// <summary>
/// The communication channel that is used by client to send and receive messages.
/// </summary>
protected ICommunicationChannel _communicationChannel;

/// <summary>
/// This timer is used to send PingMessage messages to server periodically.
/// </summary>
private readonly Timer _pingTimer;

/// <summary>
/// Constructor.
/// </summary>
protected ScsClientBase()
{
    _pingTimer = new Timer(30000);
    _pingTimer.Elapsed += PingTimer_Elapsed;
    _wireProtocol = WireProtocolManager.GetDefaultWireProtocol();
}

In the constructor, we create a Timer (this timer is defined by the SCS framework) that is used to send periodic PingMessage objects to the server (if the communication line is idle the previous minute). And we also get a reference to the default wire protocol (BinarySerializationProtocol). If the user does not set WireProtocol, then the default protocol is used. Now, let's see how the client connects to the server (how the Connect() method is implemented):

/// <summary>
/// Connects to server.
/// </summary>
public void Connect()
{
    _communicationChannel = CreateCommunicationChannel();
    _communicationChannel.WireProtocol = _wireProtocol;
    _communicationChannel.Disconnected += CommunicationChannel_Disconnected;
    _communicationChannel.MessageReceived += CommunicationChannel_MessageReceived;
    _communicationChannel.Start();
    _pingTimer.Start();
    OnConnected();
}

We first create a communication channel. This is critical because we must create the appropriate communication channel according to the current transfer layer protocol (TCP is the default). We achieve this by defining the CreateCommunicationChannel() method as abstract. So it will be implemented by the derived classes (by the ScsTcpClient class). Then we are set the WireProtocol of the channel, and register to the Disconnected and MessageReceived events and start the channel. Finally, we start the ping timer and raise the Connected event. The last method of ScsClientBase that I will examine here is the Elapsed event handler of the _pingTimer object. We are sending a Ping message just in case no communication was made in the last minute, as shown below:

/// <summary>
/// Handles Elapsed event of _pingTimer to send PingMessage messages to server.
/// </summary>
/// <param name="sender">Source of event</param>
/// <param name="e">Event arguments</param>
private void PingTimer_Elapsed(object sender, EventArgs e)
{
    if (CommunicationState != CommunicationStates.Connected)
    {
        return;
    }

    try
    {
        var lastMinute = DateTime.Now.AddMinutes(-1);
        if (_communicationChannel.LastReceivedMessageTime > lastMinute || 
            _communicationChannel.LastSentMessageTime > lastMinute)
        {
            return;
        }

        _communicationChannel.SendMessage(new PingMessage());
    }
    catch
    {

    }
}

As shown in the diagram at the beginning of this section, the only concrete client class that implements IScsClient is ScsTcpClient. It inherits ScsClientBase and overrides the CreateCommunicationChannel() method as defined below.

/// <summary>
/// Creates a communication channel using ServerIpAddress and ServerPort.
/// </summary>
/// <returns>Ready communication channel to communicate</returns>
protected override ICommunicationChannel CreateCommunicationChannel()
{
    var socket = TcpHelper.ConnectToServer(new IPEndPoint(
                     IPAddress.Parse(ServerIpAddress), ServerPort), 
                     ConnectionAttemptTimeout);
    socket.NoDelay = true;
    return new TcpCommunicationChannel(socket);
}

It creates a .NET Socket (System.Net.Sockets.Socket) object, and sets NoDelay to true (this disables the Nagle algorithm, because it limits the speed of the SCS message sending and is not needed because SCS writes whole message to the socket at once). Finally, it creates and returns a TcpCommunicationChannel object using the socket. You can see the TcpHelper.ConnectToServer(...) method in the source code. It simply creates a Socket object and connects to a server. If can not connect to a server with a specified time, it throws an exception.

We have seen how a TCP client object (ScsTcpClient) works. Now, let's see how it is created by the user of the SCS framework.

/// <summary>
/// This class is used to create Clients to connect to server.
/// </summary>
public static class ScsClientFactory
{
    /// <summary>
    /// Creates a new client to connect to a server using an end point.
    /// </summary>
    /// <param name="endpoint">End point of the server to connect it</param>
    /// <returns>Created TCP client</returns>
    public static IScsClient CreateClient(ScsEndPoint endpoint)
    {
        return endpoint.CreateClient();
    }
}

The ScsClientFactory.CreateClient(...) method is used to create a client. It gets an end point (ScsEndPoint) object and returns a client (IScsClient) object. As you can see, it just calls the CreateClient() method of the end point. Thus, we don't enter a switch or if statement, since each end point object knows what type of client will be created using the end point. So, if new protocols (like Named pipes) are added, there is no need to change the ScsClientFactory. If the endPoint arguments is a ScsTcpEndPoint, then a ScsTcpClient object is created in the ScsTcpEndPoint.CreateClient() method as shown below.

/// <summary>
/// Creates a Scs Client that uses this end point to connect to server.
/// </summary>
/// <returns>Scs Client</returns>
internal override IScsClient CreateClient()
{
    return new ScsTcpClient(IpAddress, TcpPort);
}

After all, user can easily create a TCP based SCS client object, connect to a server, send/receive messages, and close the connection, as shown below:

using System;
using Hik.Communication.Scs.Client;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.Scs.Communication.Messages;

namespace ClientApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Create a client object to connect a server
            //on 127.0.0.1 (local) IP and listens 10085 TCP port
            var client = ScsClientFactory.CreateClient(
                             new ScsTcpEndPoint("127.0.0.1", 10085));

            //Register to MessageReceived event to receive messages from server.
            client.MessageReceived += Client_MessageReceived;

            //Wait user to press enter
            Console.WriteLine("Press enter to connect to the server...");
            Console.ReadLine();

            //Connect to the server
            client.Connect();

            //Get a message from user
            Console.Write("Write some message to be sent to server: ");
            var messageText = Console.ReadLine();

            //Send user message to the server
            client.SendMessage(new ScsTextMessage(messageText));

            //Wait user to press enter
            Console.WriteLine("Press enter to disconnect from server...");
            Console.ReadLine();

            //Close connection to server
            client.Disconnect();
        }

        static void Client_MessageReceived(object sender, MessageEventArgs e)
        {
            //Client only accepts text messages
            var message = e.Message as ScsTextMessage;
            if (message == null)
            {
                return;
            }

            //Write received message to console screen
            Console.WriteLine("Server sent a message: " + message.Text);
        }
    }
}

The server side of this application will be shown at the end of the server side section. You can find the source code in the Samples\SimpleMessaging folder in the download file.

Server side

We have seen almost everything about the client side. In this section, I will examine that how the server side of the SCS Messaging Layer is implemented. The main interface on the server side is IScsServer. Like other inheritance models in the SCS framework, there is a template abstract implementation of it named ScsServerBase. ScsServerBase implements common members in IScsServer and provides a good base for concrete server classes. The only concrete server class is the ScsTcpServer class.

SCS Server Diagram

Server classes in the SCS framework

IScsServer defines WireProtocol (as in the client side) to allow the user to change the messaging protocol. It defines a kind of sorted list named Clients that is used to store the clients that are currently connected to the server (ThreadSafeSortedList is used to store clients to be thread safe, because it can be changed by more than one thread). The key of this collection is ClientId (a unique long number) and the value is a reference to the client object (IScsServerClient) to communicate with the client (will be examined later). The only event in the server object is ClientConnected. Using this event, we can be informed when a client has successfully connected to the server. Also, we can get a reference to a newly connected client in the ClientConnected event handler using the ServerClientEventArgs event argument.

As a beginning to examining the server side, let's see some significant methods of ScsServerBase.

/// <summary>
/// Starts server.
/// </summary>
public virtual void Start()
{
    _connectionListener = CreateConnectionListener();
    _connectionListener.CommunicationChannelConnected += 
              ConnectionListener_CommunicationChannelConnected;
    _connectionListener.Start();
}

/// <summary>
/// Stops server.
/// </summary>
public virtual void Stop()
{
    if (_connectionListener != null)
    {
        _connectionListener.Stop();
    }

    foreach (var client in Clients.GetAllItems())
    {
        client.Disconnect();
    }
}

/// <summary>
/// This method is implemented by derived classes to create
/// appropriate connection listener to listen incoming connection requets.
/// </summary>
/// <returns></returns>
protected abstract IConnectionListener CreateConnectionListener();

/// <summary>
/// Handles CommunicationChannelConnected event of _connectionListener object.
/// </summary>
/// <param name="sender">Source of event</param>
/// <param name="e">Event arguments</param>
private void ConnectionListener_CommunicationChannelConnected(object sender, 
                                CommunicationChannelEventArgs e)
{
    var client = new ScsServerClient(e.Channel)
                 {
                     ClientId = ScsServerManager.GetClientId(),
                     WireProtocol = WireProtocol
                 };
    client.Disconnected += Client_Disconnected;
    Clients[client.ClientId] = client;
    OnClientConnected(client);
    e.Channel.Start();
}

/// <summary>
/// Handles Disconnected events of all connected clients.
/// </summary>
/// <param name="sender">Source of event</param>
/// <param name="e">Event arguments</param>
private void Client_Disconnected(object sender, EventArgs e)
{
    Clients.Remove(((IScsServerClient)sender).ClientId);
}

The main responsibility of the ScsServerBase class is to manage clients. In the Start() method, it creates a connection listener (IConnectionListener) using the abstract CreateConnectionListener() method. This method is implemented by ScsTcpServer to create a ScsTcpConnectionListener object (please see the ScsTcpServer class in the source code). It then registers to the CommunicationChannelConnected event and starts the connection listener. The Stop() method just stops the connection listener and disconnects all active clients.

The CommunicationChannelConnected event handler (ConnectionListener_CommunicationChannelConnected method) creates a new ScsServerClient object with the new channel, registers its Disconnect event to be informed when the client connection fails, adds it to the Clients collection, and starts communication with the new client. Finally, the Client_Disconnected event handler method is used to remove the disconnected client from the Clients collection. Note that the ClientId property of the new client is set while creating the object using ScsServerManager.GetClientId(). This method is defined below. It uses the Interlocked (System.Threading.Interlocked) class to increment _lastClientId in a thread safe manner.

internal static class ScsServerManager
{
    /// <summary>
    /// Used to set an auto incremential unique identifier to clients.
    /// </summary>
    private static long _lastClientId;

    /// <summary>
    /// Gets an unique number to be used as idenfitier of a client.
    /// </summary>
    /// <returns></returns>
    public static long GetClientId()
    {
        return Interlocked.Increment(ref _lastClientId);
    }
}

As you have seen, a client is represented by the IScsServerClient interface in server side. This interface is used to communicate with the client from the server.

Server side client classes in the SCS framework

The IScsServerClient interface also extends IMessenger. It is implemented by the ScsServerClient class. There is no transport-layer based implementation of IScsServerClient (like ScsTcpServerClient), since ScsServerClient uses ICommunicationChannel to communicate with the client (thus, it is protocol independent). The constructor and some methods of ScsServerClient are shown below:

/// <summary>
/// Unique identifier for this client in server.
/// </summary>
public long ClientId { get; set; }

/// <summary>
/// The communication channel that is used by client to send and receive messages.
/// </summary>
protected ICommunicationChannel _communicationChannel;

/// <summary>
/// Creates a new ScsClient object.
/// </summary>
/// <param name="communicationChannel">The communication channel
///    that is used by client to send and receive messages</param>
public ScsServerClient(ICommunicationChannel communicationChannel)
{
    _communicationChannel = communicationChannel;
    _communicationChannel.MessageReceived += CommunicationChannel_MessageReceived;
    _communicationChannel.Disconnected += CommunicationChannel_Disconnected;
}

/// <summary>
/// Disconnects from client and closes underlying communication channel.
/// </summary>
public void Disconnect()
{
    _communicationChannel.Disconnect();
}

/// <summary>
/// Sends a message to the client.
/// </summary>
/// <param name="message">Message to be sent</param>
public void SendMessage(IScsMessage message)
{
    _communicationChannel.SendMessage(message);
}

As I mentioned above, ScsServerClient uses a communication channel to communicate with the client, and gets it as an argument to the constructor. It registers the events of the communication channel to be able to raise these events since it implements IMessenger and IScsServerClient and they define the MessageReceived and Disconnected events. ClientId is a unique number that is set by the server, while ScsServerClient is being created (as explained before).

The user of the SCS framework uses the ScsServerFactory class to create a server object.

/// <summary>
/// This class is used to create SCS servers.
/// </summary>
public static class ScsServerFactory
{
    /// <summary>
    /// Creates a new SCS Server using an EndPoint.
    /// </summary>
    /// <param name="endPoint">Endpoint that represents address of the server</param>
    /// <returns>Created TCP server</returns>
    public static IScsServer CreateServer(ScsEndPoint endPoint)
    {
        return endPoint.CreateServer();
    }
}

Very similar to creating a client object, we get an end point object (that is used to listen to incoming client connection requests) and use the end point to create a server. Thus, the server object is created according to the end point (note that this method is internal and can not be used by the user directly). Since all end points (in fact, there is only one) know what type of server to create, the server object is successfully created. Let's see the TCP implementation of the CreateServer() method in the ScsTcpEndPoint class.

/// <summary>
/// Creates a Scs Server that uses this
/// end point to listen incoming connections.
/// </summary>
/// <returns>Scs Server</returns>
internal override IScsServer CreateServer()
{
    return new ScsTcpServer(TcpPort);
}

Lastly, let's see a simple server application that listens to client connections, gets a text message from clients, and sends a response for the message. It completes the sample application that we built in the client side.

using System;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.Scs.Communication.Messages;
using Hik.Communication.Scs.Server;

namespace ServerApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //Create a server that listens 10085 TCP port for incoming connections
            var server = ScsServerFactory.CreateServer(new ScsTcpEndPoint(10085));

            //Register ClientConnected event
            server.ClientConnected += Server_ClientConnected;

            //Start the server
            server.Start();

            //Wait user to press enter
            Console.WriteLine("Server is started successfully. " + 
                              "Press enter to stop...");
            Console.ReadLine();

            //Stop the server
            server.Stop();
        }

        static void Server_ClientConnected(object sender, 
                    ServerClientEventArgs e)
        {
            //Write a inform message to console screen
            Console.WriteLine("A new client is connected. Client Id = " + 
                              e.Client.ClientId);

            //Register to MessageReceived event
            //to receive messages from new client
            e.Client.MessageReceived += Client_MessageReceived;

            //Register to Disconnect event of new client to 
            //be informed when the client connection is closed
            e.Client.Disconnected += Client_Disconnected;
        }

        static void Client_MessageReceived(object sender, MessageEventArgs e)
        {
            //Server only accepts text messages
            var message = e.Message as ScsTextMessage;
            if (message == null)
            {
                return;
            }

            //Get a reference to the client
            var client = (IScsServerClient) sender; 
            
            //Write received message to console screen
            Console.WriteLine("Client sent a message: " + message.Text + 
                              ". (Cliend Id = " + client.ClientId + ")");

            //Send response message
            client.SendMessage(new ScsTextMessage("Hello client. " + 
                   "I got your message (" + message.Text + ")"));
        }

        static void Client_Disconnected(object sender, EventArgs e)
        {
            //Get a reference to the client
            var client = (IScsServerClient)sender;

            //Write a inform message to console screen
            Console.WriteLine("A client is disconnected! " + 
                    "Client Id = " + client.ClientId);
        }
    }
}

If you run the server and client applications and send a message from the client to the server, you can see a screen as shown below:

Server Client Test Apps

A screenshot of the running test applications

You can find the source code in the Samples\SimpleMessaging folder in the download file.

Remote Method Invocation Layer

As I mentioned before, the SCS framework allows the user to call remote methods. I have explained the Messaging Layer in detail. The Remote Method Invocation Layer is responsible for translating method calls to messages and sending them over the Messaging Layer. It also receives messages from the Messaging Layer and calls the service methods of the application. All classes in the Messaging Layer are defined in the Hik.Communication.ScsServices namespace (and its sub namespaces). I suggest looking at the chat system in the first article to understand how the RMI Layer is used.

Request/Reply messaging

A method call is a type of request/reply operation. The request consists of the name of the method and the arguments, and the reply is the result value of the method (even if the method return type is void, the method can throw an exception that can be considered as a response/output). The Messaging Layer of SCS is asynchronous (because of the nature of TCP). It means the server and the client can send/receive messages independent from each other. So, we must build a mechanism to send a method call request and wait for the result of the method call.

Request/Reply style messaging is accomplished in SCS with the RequestReplyMessenger class. It is actually a decorator for any IMessenger. The class diagram of RequestReplyMessenger is shown below:

RequestReplyMessenger class diagram; just some of the members of RequestReplyMessenger is shown in diagram

RequestReplyMessenger is a generic class, and gets a class (that implements IMessenger) as its generic parameter. RequestReplyMessenger also implements IMessenger (as shown in the class diagram above). It decorates an IMessenger object with the following features:

  • SendMessageAndWaitForReply(...) method: Using this method, we can send a message to a remote application and get a reply message. It blocks calling a thread until a response is received or a timeout occurs. If a response is not received in a specified time, an Exception is thrown.
  • Queued processing of incoming messages: RequestReplyMessenger processes message over a FIFO (first in first out) queue. If the sender application generates messages faster than processing in the receiver side, messages are queued.

First we must look at how a RequestReplyMessenger is being created and used. Assume that you have created an SCS client and wants to use RequestReplyMessenger to send request/reply style messages over this client. I will re-write the application that we built in the client side section.

using System;
using Hik.Communication.Scs.Client;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.Scs.Communication.Messages;
using Hik.Communication.ScsServices.Communication;

namespace RequestReplyStyleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            //Create a client object to connect a server
            //on 127.0.0.1 (local) IP and listens 10085 TCP port
            var client = ScsClientFactory.CreateClient(
                            new ScsTcpEndPoint("127.0.0.1", 10085));

            //Create a RequestReplyMessenger that uses this client as messenger.
            var requestReplyMessenger = 
                   new RequestReplyMessenger<IScsClient>(client);
            requestReplyMessenger.Start();

            //Wait user to press enter
            Console.WriteLine("Press enter to connect to the server...");
            Console.ReadLine();

            //Connect to the server
            client.Connect();

            //Get a message from user
            Console.Write("Write some message to be sent to server: ");
            var messageText = Console.ReadLine();

            //Send user message to the server and get response
            var response = requestReplyMessenger.SendMessageAndWaitForResponse(
                                                 new ScsTextMessage(messageText));
            Console.WriteLine("Response to message: " + ((ScsTextMessage) response).Text);

            //Wait user to press enter
            Console.WriteLine("Press enter to disconnect from server...");
            Console.ReadLine();

            //Close connection to server
            requestReplyMessenger.Stop();
            client.Disconnect();
        }
    }
}

As you can see, we create a RequestReplyMessenger<IScsClient> object. The generic parameter is IScsClient since we are using this type of object to communicate with the server (remember that IScsClient extends IMessenger). Then we call the Start() method (it internally starts the message process queue as we will see soon). The critical line is where we call the SendMessageAndWaitForResponse(...) method. It returns a IScsMessage object. This object is the response message that is sent from the server for our outgoing message. Finally, we must Stop RequestReplyMessenger (to stop the internal messaging queue). Now, let's examine how the RequestReplyMessenger class is implemented.

Since RequestReplyMessenger may be used by more than one thread, it must block each thread, store the messaging context (as the sending message ID) and match with received messages. So, it must store some information about the request, until the response is received (or a timeout occurs). It uses WaitingMessage class objects to accomplish this task.

/// <summary>
/// This class is used to store messaging context for a request message
/// until response is received.
/// </summary>
private sealed class WaitingMessage
{
    /// <summary>
    /// Response message for request message (null if response
    /// is not received yet).
    /// </summary>
    public IScsMessage ResponseMessage { get; set; }

    /// <summary>
    /// ManualResetEvent to block thread until response is received.
    /// </summary>
    public ManualResetEvent WaitEvent { get; private set; }

    /// <summary>
    /// State of the request message.
    /// </summary>
    public WaitingMessageStates State { get; set; }

    /// <summary>
    /// Creates a new WaitingMessage object.
    /// </summary>
    public WaitingMessage()
    {
        WaitEvent = new ManualResetEvent(false);
        State = WaitingMessageStates.WaitingForResponse;
    }
}

/// <summary>
/// This enum is used to store the state of a waiting message.
/// </summary>
private enum WaitingMessageStates
{
    /// <summary>
    /// Still waiting for response.
    /// </summary>
    WaitingForResponse,

    /// <summary>
    /// Message sending is cancelled.
    /// </summary>
    Cancelled,

    /// <summary>
    /// Response is properly received.
    /// </summary>
    ResponseReceived
}

WaitingMessage objects are stored in the _waitingMessages collection until a response is received.

/// <summary>
/// This messages are waiting for a response.
/// Key: MessageID of waiting request message.
/// Value: A WaitingMessage instance.
/// </summary>
private readonly SortedList<string, WaitingMessage> _waitingMessages;

Now, let's see how I implemented the SendMessageAndWaitForReply(...) method.

/// <summary>
/// Sends a message and waits a response for that message.
/// </summary>
/// <param name="message">message to send</param>
/// <param name="timeoutMilliseconds">Timeout duration as milliseconds.</param>
/// <returns>Response message</returns>
public IScsMessage SendMessageAndWaitForResponse(IScsMessage message, 
                   int timeoutMilliseconds)
{
    //Create a waiting message record and add to list
    var waitingMessage = new WaitingMessage();
    lock (_waitingMessages)
    {
        _waitingMessages[message.MessageId] = waitingMessage;
    }

    try
    {
        //Send message
        Messenger.SendMessage(message);

        //Wait for response
        waitingMessage.WaitEvent.WaitOne(timeoutMilliseconds);
                
        //Check for exceptions
        switch (waitingMessage.State)
        {
            case WaitingMessageStates.WaitingForResponse:
                throw new Exception("Timeout occured. Can not received response.");
            case WaitingMessageStates.Cancelled:
                throw new Exception("Disconnected before response received.");
        }

        //return response message
        return waitingMessage.ResponseMessage;
    }
    finally
    {
        //Remove message from waiting messages
        lock (_waitingMessages)
        {
            if (_waitingMessages.ContainsKey(message.MessageId))
            {
                _waitingMessages.Remove(message.MessageId);
            }
        }
    }
}

First, we create a WaitingMessage object and store it in the _waitingMessages SortedList collection with the MessageId of the sending message as key (we are performing operations on the collection in lock statements for thread safety). Then we send the message by using the underlying Messenger object (that is passed as an argument into the constructor as you saw before). We must wait until a response message is received. So, we call the WaitOne(...) method of the ManualResetEvent (System.Threading.ManualResetEvent) class. It blocks the calling thread until another thread calls the Set() method of the same ManualResetEvent object or a timeout occurs. Anyway, the calling thread continues its running and checks the current State of the WaitingMessage object. If it is still in the WaitForResponse state, that means a timeout has occurred. If it is in the Canceled state, that means RequestReplyMessenger has stopped before a response was received; else (that is to say the state is in ResponseReceived), the response message is returned as a return value of the SendMessageAndWaitForResponse(...) method. Finally, we must remove the WaitingMessage object from the _waitingMessages collection since it is not being waited for anymore.

Now, let's examine that how the State of a WaitingMessage object is changed. The first case is the response message is received successfully. To do this, we must register to the MessageReceived event of the underlying IMessenger object. The event handler for MessageReceived is shown below:

/// <summary>
/// Handles MessageReceived event of Messenger object.
/// </summary>
/// <param name="sender">Source of event</param>
/// <param name="e">Event arguments</param>
private void Messenger_MessageReceived(object sender, MessageEventArgs e)
{
    //Check if there is a waiting thread for this message
    //(in SendMessageAndWaitForResponse method)
    if (!string.IsNullOrEmpty(e.Message.RepliedMessageId))
    {
        WaitingMessage waitingMessage = null;
        lock (_waitingMessages)
        {
            if (_waitingMessages.ContainsKey(e.Message.RepliedMessageId))
            {
                waitingMessage = _waitingMessages[e.Message.RepliedMessageId];
            }
        }

        //If there is a thread waiting for this response message, pulse it
        if (waitingMessage != null)
        {
            waitingMessage.ResponseMessage = e.Message;
            waitingMessage.State = WaitingMessageStates.ResponseReceived;
            waitingMessage.WaitEvent.Set();
            return;
        }
    }

    _incomingMessageQueue.Add(e);
}

This method first checks if the received message is a response message (if its RepliedMessageId is empty, then it is not a response message). If it is, check if there is a message in the _waitingMessages collection that is waiting for this response. If this condition is also true, then set the state of the message as ResponseReceived, set ResponseMessage as a new incoming message, and lastly call the Set() method of the ManualResetEvent object (WaitEvent) to notify the waiting thread to continue. If all conditions are false and the message is an ordinary incoming message, it is added to the _incomingMessageQueue queue to be processed (please see the source code for processing of messages in this queue).

The second and last method that changes the state of a WaitingMessage is the Stop() method of RequestReplyMessenger. It is defined as below:

/// <summary>
/// Stops the messenger.
/// Cancels all waiting threads in SendMessageAndWaitForResponse
/// method and stops message queue.
/// SendMessageAndWaitForResponse method throws exception
/// if there is a thread that is waiting for response message.
/// </summary>
public void Stop()
{
    _incomingMessageQueue.Stop();

    //Pulse waiting threads for incoming messages,
    //since underlying messenger is disconnected
    //and can not receive messages anymore.
    lock (_waitingMessages)
    {
        foreach (var waitingMessage in _waitingMessages.Values)
        {
            waitingMessage.State = WaitingMessageStates.Cancelled;
            waitingMessage.WaitEvent.Set();
        }

        _waitingMessages.Clear();
    }
}

If RequestReplyMessenger is stopped, it sets the states of all waiting messages to Canceled and informs the waiting threads to continue by calling the Set() method of ManualResetEvent.

With the examining of the Stop method, I have finished explaining Request/Reply style messaging. This was one of the key points of the SCS framework.

Remote Method Call messages

We built a strong messaging infrastructure until this point. It's time to see how method calls are being translated into messages and vice versa. The Remote Method Invocation Layer defines two new message classes. They are ScsRemoteInvokeMessage and ScsRemoteInvokeReturnMessage.

Remote Message Classes

Remote Method Invocation classes

ScsRemoteInvokeMessage stores method call information such as name of the method (MethodName), the class that has the method (ServiceClassName), and all parameters (Parameters) that are used while calling the method on a remote application. ScsRemoteInvokeReturnMessage stores the return value of the method call (ReturnValue) if no exception occurs, or the exception object (RemoteException) if an exception is thrown in the remote method. Since both of these classes are inherited from ScsMessage, they have the MessageId and RepliedMessageId properties that are used to match the request (ScsRemoteInvokeMessage) and reply (ScsRemoteInvokeReturnMessage) messages.

Service contracts and their usage

Service contracts are another key point of the SCS framework. A service contract is an interface that is used to define methods of the server that can be remotely called by clients. You can also create a service contract to define methods of the clients that can be remotely called by the server (you can think that service contracts in the SCS framework correspond to contracts in WCF).

Before going further into the implementation of the SCS Remote Method Invocation Layer, I want to demonstrate the usage of this layer in a very simple application. If you have read the first article, you can skip to the next section. If you want to see the complete usage of this layer (as the used for server-to-client method calls), please see the first article. In this simple application, I will use a calculator service and a client that uses the service to do some calculations.

First, we define a service contract interface as shown below:

/// <summary>
/// This interface defines methods of calculator
/// service that can be called by clients.
/// </summary>
[ScsService]
public interface ICalculatorService
{
    int Add(int number1, int number2);

    double Divide(double number1, double number2);
}

The service contract is very clear. It is an ordinary C# interface except for the ScsService attribute. Let's see a service application that implements this contract. The complete server side code is shown below:

using System;
using CalculatorCommonLib;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.ScsServices.Service;

namespace CalculatorServer
{
    class Program
    {
        static void Main(string[] args)
        {
            //Create a service application that runs on 10083 TCP port
            var serviceApplication = 
              ScsServiceBuilder.CreateService(new ScsTcpEndPoint(10083));

            //Create a CalculatorService and add it to service application
            serviceApplication.AddService<ICalculatorService, 
                          CalculatorService>(new CalculatorService());
            
            //Start service application
            serviceApplication.Start();

            Console.WriteLine("Calculator service is started. Press enter to stop...");
            Console.ReadLine();

            //Stop service application
            serviceApplication.Stop();
        }
    }

    public class CalculatorService : ScsService, ICalculatorService
    {
        public int Add(int number1, int number2)
        {
            return number1 + number2;
        }

        public double Divide(double number1, double number2)
        {
            if(number2 == 0.0)
            {
                throw new DivideByZeroException("number2 can not be zero!");
            }

            return number1 / number2;
        }
    }
}

The CalculatorService class implements ICalculatorService. It must be inherited from the ScsService class. In the Main method of the console application, we are first create a service application that runs on the 10083 TCP port. Then we create a CalculatorService object and add it to the service application using AddService(...). This method is generic, and gets the service contract interface type and the service implementation type. Then we start the service by using the Start() method. As you can see, it is very easy and straightforward to create a SCS service application. Now, let's write a client application that uses this service:

using System;
using CalculatorCommonLib;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.ScsServices.Client;

namespace CalculatorClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press enter to connect " + 
                              "to server and call methods...");
            Console.ReadLine();

            //Create a client that can call methods of Calculator
            //Service that is running on local computer and 10083 TCP port
            //Since IScsServiceClient is IDisposible,
            //it closes connection at the end of the using block
            using (var client = 
                   ScsServiceClientBuilder.CreateClient<ICalculatorService>(
                   new ScsTcpEndPoint("127.0.0.1", 10083)))
            {
                //Connect to the server
                client.Connect();

                //Call a remote method of server
                var division = client.ServiceProxy.Divide(42, 3);
                Console.WriteLine("Result: " + division);
            }
            
            Console.WriteLine("Press enter to stop client application");
            Console.ReadLine();
        }
    }
}

As you can see, we are using the ServiceProxy property of the client object (IScsServiceClient) to call the service methods remotely. It is a dynamic proxy. We did not generate a proxy class. When you add a Web Service or WCF service to your application, a proxy class is generated automatically by Visual Studio (or you can use the console utility wsdl.exe for Web Services, and scvutil.exe for WCF services). When the service contract or Web Service definition changes, you must update/regenerate your proxy code. In the SCS service, it is completely dynamic. You don't need to generate a proxy class.

Dynamic proxy

A proxy, in its most general form, is a class functioning as an interface to something else. A proxy class implements the same interface with a real class. The user can call a method of the proxy object instead of directly calling the same method of a real object. The proxy object can do some actions before/after calling the real object's method.

We discussed how a proxy class can be implemented in the Mapping messages with classes and methods section. When the user calls a service method, we must get the service contract interface name, method name, and parameter list, create a ScsRemoteInvokeMessage object using these arguments, send the message, and get a response (a ScsRemoteInvokeReturnMessage object) using the RequestReplyMessenger class, and return the method result to the user. Let's examine how we implement it dynamically.

The .NET Framework allows to create dynamic transparent proxy classes by inheriting from the RealProxy (System.Runtime.Remoting.Proxies.RealProxy) class. It has a method named GetTransparentProxy() that returns a transparent proxy object. You can cast this object to any interface. Despite of the object not being an instance of a class that implements the casted interface, it does not throw an exception! It is one of the most interesting classes in the .NET Framework. So, what happens when you call a method of the object over the interface? Surely, it has no implementation (because the same TransparentProxy object can be cast to any interface). So, you must override the RealProxy.Invoke method to decide on what to do on a method call. In the Invoke method, you can get the method name that is called and the parameter list that is passed as the arguments to the method by the user. It does not matter which method is called by the user, you must handle all method calls in the same Invoke method.

The SCS framework uses the RemoteInvokeProxy class (that is derived from RealProxy) to translate method calls to messages dynamically. It is defined as below:

/// <summary>
/// This class is used to generate a dynamic proxy to invoke remote methods.
/// It translates method invocations to messaging.
/// </summary>
/// <typeparam name="TProxy">Type of the proxy class/interface</typeparam>
/// <typeparam name="TMessenger">Type of the messenger object
///      that is used to send/receive messages</typeparam>
internal class RemoteInvokeProxy<TProxy, TMessenger> : 
               RealProxy where TMessenger : IMessenger
{
    /// <summary>
    /// Messenger object that is used to send/receive messages.
    /// </summary>
    private readonly RequestReplyMessenger<TMessenger> _clientMessenger;

    /// <summary>
    /// Creates a new RemoteInvokeProxy object.
    /// </summary>
    /// <param name="clientMessenger">Messenger object
    ///     that is used to send/receive messages</param>
    public RemoteInvokeProxy(RequestReplyMessenger<TMessenger> clientMessenger)
        : base(typeof(TProxy))
    {
        _clientMessenger = clientMessenger;
    }

    /// <summary>
    /// Overrides message calls and translates
    /// them to messages to remote application.
    /// </summary>
    /// <param name="msg">Method invoke message (from RealProxy base class)</param>
    /// <returns>Method invoke return message (to RealProxy base class)</returns>
    public override IMessage Invoke(IMessage msg)
    {
        var message = msg as IMethodCallMessage;
        if (message == null)
        {
            return null;
        }

        var requestMessage = new ScsRemoteInvokeMessage
        {
            ServiceClassName = typeof (TProxy).Name,
            MethodName = message.MethodName,
            Parameters = message.InArgs
        };

        var responseMessage = 
          _clientMessenger.SendMessageAndWaitForResponse(requestMessage) 
                           as ScsRemoteInvokeReturnMessage;
        if (responseMessage == null)
        {
            return null;
        }

        return responseMessage.RemoteException != null
                   ? new ReturnMessage(responseMessage.RemoteException, message)
                   : new ReturnMessage(responseMessage.ReturnValue, 
                                       null, 0, message.LogicalCallContext, 
                                       message);
    }
}

RemoteInvokeProxy is a generic class. The first generic parameter (TProxy) is the type of the service contract interface, the second one (TMessenger) is the type of the messenger object (this is needed to use RequestReplyMessenger). It gets a RequestReplyMessenger object that is used to send messages to and get responses from a remote application. The only method is the override of the RealProxy.Invoke() method. Don't be confused because of the IMessage, IMethodCallMessage... interfaces. They are a part of the .NET Framework, and not related to SCS.

In the Invoke method, we create a new ScsRemoteInvokeMessage object. ServiceClassName is the name of the service contract interface type. MethodName and Parameters are set using the input parameter of the Invoke method (this input parameter provides all the information about the method call). Then we send messages and receive responses using the RequestReplyMessenger.SendMessageAndWaitForResponse(...) method. Finally, we supply a return value to the method call using the response message. The implementation of the Invoke method is mostly related to the RealProxy class. We will see how we are creating and using the RemoteInvokeProxy in the following sections.

Calling methods using Reflection

Translating user method calls to messages and sending messages was the first part of remote method invocation. The second part is to call the appropriate method in the remote application according to the received ScsRemoteInvokeMessage object and sending the result of the method call in a ScsRemoteInvokeReturnMessage object. This is simply accomplished by using the Reflection mechanism in the .NET Framework.

There are two different implementations of the method call. One implementation is in the server side, the other one in the client side. They have quite different approaches to determine how to find the appropriate object to invoke its method, but calling the method part is almost the same. I'll examine the client side here (remember that the server can call the client methods remotely; for an example, see the chat system in the first article). So, let's see what happens when a message is received from the server in the client side.

/// <summary>
/// Handles MessageReceived event of messenger.
/// It gets messages from server and invokes appropriate method.
/// </summary>
/// <param name="sender">Source of event</param>
/// <param name="e">Event arguments</param>
private void RequestReplyMessenger_MessageReceived(object sender, 
             MessageEventArgs e)
{
    //Cast message to ScsRemoteInvokeMessage and check it
    var invokeMessage = e.Message as ScsRemoteInvokeMessage;
    if (invokeMessage == null)
    {
        return;
    }

    //Check client object.
    if(_clientObject == null)
    {
        SendInvokeResponse(invokeMessage, null, 
          new ScsRemoteException("Client does not wait for " + 
                                 "method invocations by server."));
        return;
    }

    //Invoke method
    object returnValue;
    try
    {
        var type = _clientObject.GetType();
        var method = type.GetMethod(invokeMessage.MethodName);
        returnValue = 
          method.Invoke(_clientObject, invokeMessage.Parameters);
    }
    catch (TargetInvocationException ex)
    {
        var innerEx = ex.InnerException;
        SendInvokeResponse(invokeMessage, null, 
          new ScsRemoteException(innerEx.Message, innerEx));
        return;
    }
    catch (Exception ex)
    {
        SendInvokeResponse(invokeMessage, null, 
          new ScsRemoteException(ex.Message, ex));
        return;
    }

    //Send return value
    SendInvokeResponse(invokeMessage, returnValue, null);
}

We use the MessageReceived event to receive incoming messages from the server. As the system is running on a remote method call approach, we only accept ScsRemoteInvokeMessage messages (otherwise, we return immediately in the event handler method). We are checking if the user supplied an object to handle the method calls from the server (we did not do this in the sample calculator client in this article, but you can look at the first article for an example of this approach). We are invoking the appropriate method using Reflection, getting the return value, and finally sending a message to the server as a response to the method call. The SendInvokeResponse(...) method creates a ScsRemoteInvokeReturnMessage object using the given parameters and sends it to the server (see source code). We know what's done with this message in the server side (from the Dynamic proxy section above). If the method call throws an exception, we are also sending this exception to the server.

We have finished examining the remote method invocation mechanism over the Messaging Layer in the SCS framework. In the following sections, we will see how the client and server side are implemented using these RMI techniques.

Client side

We have seen most of the client side in the sections above. Also, we created a sample application to see the usage of an SCS service client. Now, we will examine the client side classes in SCS. A service client is created by using the ScsServiceClientBuilder.CreateClient(...) static method as we have seen before. This method is defined as below.

/// <summary>
/// Creates a client to connect to a SCS service.
/// </summary>
/// <typeparam name="T">Type of service interface for remote method call</typeparam>
/// <param name="endpoint">EndPoint address of the server</param>
/// <param name="clientObject">Client-side object that
///      handles remote method calls from server to client.
/// May be null if client has no methods to be invoked by server</param>
/// <returns>Created client object to connect to the server</returns>
public static IScsServiceClient<T> CreateClient<T>(ScsEndPoint endpoint, 
              object clientObject = null) where T : class
{
    return new ScsServiceClient<T>(endpoint.CreateClient(), clientObject);
}

It returns a IScsServiceClient interface that is implemented by the ScsServiceClient class. The class diagram is shown below.

SCS Service Client Diagram

Client side class diagram that is used to connect to a service

The IScsServiceClient interface extends IConnectableClient. The IConnectableClient interface was explained before. IScsServiceClient just adds two properties: the ServiceProxy property is a dynamic transparent proxy that is used to call remote methods of the server. It is a generic member, and the T parameter is the type of the service contract (that is passed to the ScsServiceClientBuilder.CreateClient() generic method). Timeout is used to set a timeout value on remote method calls. All members of the interfaces are implemented by the ScsServiceClient class. Its constructor is shown below:

/// <summary>>
/// Creates a new ScsServiceClient object.
/// </summary>
/// <param name="client">Underlying IScsClient object
///    to communicate with server</param>
/// <param name="clientObject">The client object that
///    is used to call method invokes in client side.
/// May be null if client has no methods to be invoked by server.</param>
public ScsServiceClient(IScsClient client, object clientObject)
{
    _client = client;
    _clientObject = clientObject;

    _client.Connected += Client_Connected;
    _client.Disconnected += Client_Disconnected;

    _requestReplyMessenger = new RequestReplyMessenger<IScsClient>(client);
    _requestReplyMessenger.MessageReceived += 
              RequestReplyMessenger_MessageReceived;
    _requestReplyMessenger.Start();

    _realProxy = new RemoteInvokeProxy<T, IScsClient>(_requestReplyMessenger);
    ServiceProxy = (T)_realProxy.GetTransparentProxy();
}

As you have already seen in the ScsServiceClientBuilder.CreateClient() method, ScsServiceClient gets an IScsClient object (as explained before in the Messaging Layer section, this is the main interface that is used to send messages to and receive messages from the server). In the constructor, we create a RequestReplyMessenger object to send/receive messages in request/reply style. Last but not least, we create a RemoteInvokeProxy object and call its GetTransparentProxy() method to create an object that is used to call remote methods of the server. As you can see, we are casting it to T (type of the service contract). Although the T is not known at compile time of the SCS framework and it can be any interface, there is no problem with casting it by means of the special behavior of RealProxy. As we have examined how a method call is made through the client to the server, examining of the client side is finished here.

Server side

As you have seen before, a service application is created by using the ScsServiceBuilder.CreateService(...) static method that is defined as below.

/// <summary>
/// Creates a new SCS Service application using an EndPoint.
/// </summary>
/// <param name="endPoint">EndPoint that represents address of the service</param>
/// <returns>Created SCS service application</returns>
public static IScsServiceApplication CreateService(ScsEndPoint endPoint)
{
    return new ScsServiceApplication(ScsServerFactory.CreateServer(endPoint));
}

The CreateService(...) method gets an end point and returns an IScsServiceApplication interface. The class diagram is shown below.

SCS Service Application Diagram

Service application class diagram

The IScsServiceApplication interface defines the methods and events that can be used by the user to manage the server side. It is implemented by the ScsServiceApplication class. The constructor of this class is shown below:

/// <summary>
/// Creates a new ScsServiceApplication object.
/// </summary>
/// <param name="scsServer">Underlying IScsServer object
///    to accept and manage client connections</param>
internal ScsServiceApplication(IScsServer scsServer)
{
    if (scsServer == null)
    {
        throw new ArgumentNullException("scsServer");
    }

    _scsServer = scsServer;
    _scsServer.ClientConnected += ScsServer_ClientConnected;
    _serviceObjects = new ThreadSafeSortedList<string, ServiceObject>();
    _serviceClients = new ThreadSafeSortedList<long, IScsServiceClient>();
}

It gets an IScsServer object as the only parameter. This object (as we have seen in the Messaging Layer) is the main object that is used to interact with the clients and manage them. Two collections are created in the constructor. The first one is used to store services that are hosted. In an SCS service application, you can run more than one service on the same end point simultaneously. The second collection is used to store the currently connected clients.

After creating a ScsServiceApplication, the AddService(...) method is called by the user to add a service that is hosted by this service application.

/// <summary>
/// Adds a service object to this service application.
/// Only single service object can be added for a service interface type.
/// </summary>
/// <typeparam name="TServiceInterface">Service interface type</typeparam>
/// <typeparam name="TServiceClass">Service class type.
/// Must be delivered from ScsService and must implement TServiceInterface.</typeparam>
/// <param name="service">An instance of TServiceClass.</param>
public void AddService<TServiceInterface, TServiceClass>(TServiceClass service) 
    where TServiceClass : ScsService, TServiceInterface
    where TServiceInterface : class
{
    if (service == null)
    {
        throw new ArgumentNullException("service");
    }

    var type = typeof(TServiceInterface);
    if(_serviceObjects[type.Name] != null)
    {
        throw new Exception("Service '" + type.Name + "' is already added.");                
    }

    _serviceObjects[type.Name] = new ServiceObject(type, service);
}

The AddService(...) method has two generic parameters. The first one is the service contract interface that is used by the clients. The second one is the implementation class of the interface. As you have seen in the AddService method implementation, you can add only one object for each service contract interface.

A client is represented by the IScsServiceClient interface and implemented by the ScsServiceClient class in service side (this is different from the client side IScsServiceClient interface). The class diagram is shown below:

Service side ServiceClient diagram

Diagram for the class that is used to represent a client in service side

The key method in IScsServiceClient is the GetClientProxy() method. This method is generic, and used to get a reference to a dynamic transparent proxy object that is used to call client methods from the server side.

All services that are built on the SCS framework must be derived from the ScsService class (as you have seen in the sample applications). The ScsService class has only one public property: CurrentClient. This property is used to get a reference to the client which is invoking the service method. This property is thread-safe. So, even if two clients invoke the same method concurrently, you can get the right client object using the CurrentClient property. Let's examine the ScsService class to see how it is achieved.

/// <summary>
/// Base class for all services that is serviced by IScsServiceApplication.
/// A class must be derived from ScsService to serve as a SCS service.
/// </summary>
public abstract class ScsService
{
    /// <summary>
    /// Active clients list.
    /// List of all clients those are currently served by this service.
    /// This list is used to find which client called service's method.
    /// See <see cref="CurrentClient"/> property.
    /// Key: ManagedThreadId of client's thread.
    /// Value: IScsServiceClient object.
    /// </summary>
    private readonly ThreadSafeSortedList<int, IScsServiceClient> _clients;

    /// <summary>
    /// Gets the current client that is called method.
    /// This property is thread-safe, if returns
    /// correct client when called in a method
    /// if the method is called by SCS system, else throws exception.
    /// </summary>
    protected IScsServiceClient CurrentClient
    {
        get { return GetCurrentClient(); }
    }

    /// <summary>
    /// Constructor.
    /// </summary>
    protected ScsService()
    {
        _clients = new ThreadSafeSortedList<int, IScsServiceClient>();
    }

    /// <summary>
    /// Adds a client to client list.
    /// This method is called before call a service method.
    /// So, service method can obtain client which called the method.
    /// </summary>
    /// <param name="client">Client to be added</param>
    internal void AddClient(IScsServiceClient client)
    {
        _clients[Thread.CurrentThread.ManagedThreadId] = client;
    }

    /// <summary>
    /// Removes a client from client list.
    /// This method is called after a service method call.
    /// </summary>
    internal void RemoveClient()
    {
        _clients.Remove(Thread.CurrentThread.ManagedThreadId);
    }

    /// <summary>
    /// This method is called from <see cref="CurrentClient"/> property.
    /// </summary>
    /// <returns>Current client</returns>
    private IScsServiceClient GetCurrentClient()
    {
        var client = _clients[Thread.CurrentThread.ManagedThreadId];
        if(client == null)
        {
            throw new Exception("Client channel can not be obtained.");                
        }

        return client;
    }
}

The CurrentClient getter calls the GetCurrentClient() method. This method gets the client object from the _clients collection by using the current thread ID as the key. This collection is modified by two internal methods: the AddClient(...) method adds a client object to the collection by using the current thread ID as the key. The RemoveClient() method removes a client by using the current thread ID as the key. These methods are used before and after calling the service method. So, during the service method call, the user can get the current client in the same thread by invoking a service method. This operation is performed in the MessageReceived event of the server. The related parts of this method are shown below:

...

1:   //Get service object

var serviceObject = _serviceObjects[invokeMessage.ServiceClassName];
if (serviceObject == null)
{
    SendInvokeResponse(requestReplyMessenger, invokeMessage, null, 
      new ScsRemoteException("There is no service with name '" + 
      invokeMessage.ServiceClassName + "'"));
    return;
}

//Invoke method
try
{
    object returnValue;
    //Add client to service object's client list before method
    //invocation, so user service can get client in service method.
    serviceObject.Service.AddClient(client);
    try
    {
        returnValue = serviceObject.InvokeMethod(invokeMessage.MethodName, 
                                                 invokeMessage.Parameters);
    }
    finally
    {
        //Remove client from service object, since it is not neccessery anymore.
        serviceObject.Service.RemoveClient();
    }

    //Send method invocation return value to the client
    SendInvokeResponse(requestReplyMessenger, invokeMessage, returnValue, null);
    ...

Remember that the incoming message is a ScsRemoteInvokeMessage object (that is sent by the client). First of all, we find the service object that will handle this method call using the ServiceClassName property of the message (different clients can use different services). Then we call the ScsService.AddClient(...) method, invoking the requested method using Reflection with the given parameters in the message. Finally, we call the RemoveClient() method. Thus, the user can get the client object by using the CurrentClientt property during method invocation.

The user can obtain a transparent proxy object that is used to call remote methods of the client by calling the IScsServiceClient.GetClientProxy() method. This method creates a RemoteInvokeProxy and calls the GetTransparentProxy() method to create a dynamic transparent proxy to the client.

/// <summary>
/// Gets the client proxy interface that provides calling client methods remotely.
/// </summary>
/// <typeparam name="T">Type of client interface</typeparam>
/// <returns>Client interface</returns>
public T GetClientProxy<T>() where T : class
{
    _realProxy = new RemoteInvokeProxy<T, IScsServerClient>(_requestReplyMessenger);
    return (T)_realProxy.GetTransparentProxy();
}

For a sample application that uses server-to-client method calls, please see the chat system in the first article.

Performance of the SCS framework

I have written a sample application to compare the performance of the SCS framework and regular Web Services. I ran the application and the services in the same computer. The results are shown here:

********** WEB SERVICE TEST **********
Web Service, first run: 1629,8 ms.
Web Service, 10,000 times run: 14442,1 ms.
********** SCS SERVICE TEST **********
SCS Service, first run: 41,4 ms.
SCS Service, 10,000 times run: 3008,8 ms.

As you can see above, it takes 3 seconds to call a remote method 10,000 times in SCS services, when it takes 14.4 seconds in Web Services. Also, initializing of SCS is much more faster. You can find the source code in the PerformanceComparisionWithWebServices folder in Samples. Although results may vary in different servers and services, SCS is significantly faster than a regular Web Service.

Last words

With this article, I have finished examining the SCS framework. In the first article, I focused on the usage of the framework. I examined two different sample applications: a phone book and an IRC chat system. The chat system especially demonstrates the power of the framework. In this article, I discussed how to build a framework upon TCP/IP that allows the user to invoke remote methods as simple as Web Services while it is connection oriented. Then I explained almost all constructions in the framework. I use the SCS framework in real world applications and have not experienced any bugs. If you find bugs, please inform me.

Changes/Updates

  • 11.05.2011 (v1.0.2.0)
    • Added the RemoteEndPoint property to get the addresses of clients in server side.
    • Download files revised.
  • 10.04.2011 (v1.0.1.0)
    • Added the ConnectTimeout property to IConnectableClient to provide a way of setting the timeout value while connecting to a server.
推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"