Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

A Complete TCP Server/Client Communication and RMI Framework in C# .NET - Implementation

4.91/5 (150 votes)
12 Jun 2011CPOL47 min read 1.1M   32.4K  
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.

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 a highly scalable and robust framework that is entirely developed in C# and .NET Framework 4.0 (You can take a look at the performance of the SCS framework section of this article).

This is the second article about SCS and explains how I implemented it. To learn what SCS is and how to use it, please see the first article at the link below. There are 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 and Shared Memory can be used for communication of two applications but they have an important restriction that the two applications must run on the same computer (or on same network at the best case) and can not communicate over the internet. Anyway, the SCS framework is independent from TCP and can be extended to support other IPC techniques or event other communication frameworks.

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 (I mention about message boundaries).

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 (via some callback mechanism) 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 a TCP stream. Also, we can use a kind of deserialization to re-create an object from a 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 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):

C#
/// <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

TCP sockets can be used in blocking (synchronous) or nonblocking (asynchronous) mode. In the blocking mode, receiving and sending data is a blocking operation and sender/receiver thread is blocked until operation is completed. Especially in blocking receiving, the thread waits until a data is sent from remote application. So, we must make use of multithreading to do another operations when waiting data from remote application. This is not a major problem in client side since only one socket is used and one additional thread is enough to listen incoming data. But in server side, there is a dedicated socket for every clients. So, if there are 100 clients connected concurrently, there are 100 sockets to communicate with clients and 100 threads to wait incoming data from clients in blocking mode.

Blocking mode is easy to implement but not scalable for the reasons mantioned above. So, we must use asynchronous sockets with callback mechanism and a thread pool to process data.

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 custom 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 IScsMessage interface. 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 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.

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 approach 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 communicate with remote application. All wire protocol classes in SCS must implement IScsWireProtocol interface.

Wire Protocols

Wire protocol classes in the SCS Messaging Layer

SCS receives bytes from remote application (via socket streams) and sends received bytes to CreateMessages(...) method of IScsWireProtocol. Wire protocol may create zero (if received bytes are not sufficient to create a message) or more messages using given bytes. Given byte array may not contain whole message, wire protocol must store it to concatanete with next receiving bytes. Wire protocol must not block thread.

SCS calls GetBytes(...) message serialize a message to send to remote applications. Finally, Reset() method is called to reset internal state of the protocol when needed (for instance, in a disconnect/re-connect situation).

A client directly uses IScsWireProtocol but a server uses IScsWireProtocolFactory since server needs to create a wire protocol object for each client. IScsWireProtocolFactory has just one method (CreateWireProtocol) that is used to create a wire protocol object.

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 inherit it to change serialization mechanism or directly implement IScsWireProtocol and IScsWireProtocolFactory interfaces to create a custom wire protocol (Will be explained later). You can communicate with non-.NET applications easily by using custom protocols.

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) for a new connected 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:

C#
/// <summary>
/// This class is used to listen and accept incoming TCP
/// connection requests on a TCP port.
/// </summary>
internal class TcpConnectionListener : ConnectionListenerBase
{
    /// <summary>
    /// The endpoint address of the server to listen incoming connections.
    /// </summary>
    private readonly ScsTcpEndPoint _endPoint;

    /// <summary>
    /// Server socket to listen incoming connection requests.
    /// </summary>
    private TcpListener _listenerSocket;

    /// <summary>
    /// Creates a new TcpConnectionListener for given endpoint.
    /// </summary>
    /// <param name="endPoint">The endpoint address of the server to listen incoming connections</param>
    public TcpConnectionListener(ScsTcpEndPoint endPoint)
    {
        _endPoint = endPoint;
    }

    /// <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, _endPoint.TcpPort);
        _listenerSocket.Start();
    }

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

C#
/// <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 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 remote application and to listen and receive incoming messages from the remote application over network. Communication channel hierarchy in SCS is shown in the figure below.

Channels Diagram

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. SendMessage(...) method defines the method to send a message to a remote application. MessageReceived event is raised when a new message is received from the communicating remote application (so, messages are received by events) and MessageSent event raised when a message successfully sent. 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. Defines RemoteEndPoint to obtain address of the remote application (For TCP, it is a ScsTcpEndPoint object.). It also defines the Start() and Disconnect() methods to start/stop communication. Lastly, if defines the Disconnected event that is raised when the channel disconnected.

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.

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

    var ipEndPoint = (IPEndPoint)_clientSocket.RemoteEndPoint;
    _remoteEndPoint = new ScsTcpEndPoint(ipEndPoint.Address.ToString(), ipEndPoint.Port);

    _buffer = new byte[ReceiveBufferSize];
    _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. NoDelay = true is used to disable Nagle algoritm (Because it limits the speed of the SCS message sending and is not needed because SCS writes whole message to the socket at once). A ScsTcpEndPoint object is created as remote end point. It also creates a buffer to receive messages asynchronously.

C#
/// <summary>
/// Starts the thread to receive messages from socket.
/// </summary>
protected override void StartInternal()
{
    _running = true;
    _clientSocket.BeginReceive(_buffer, 0, _buffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
}

StartInternal method is called by base class to start receiving messages from remote application. Since we use asynchronous socket methods, we call non-blocking BeginReceive method by supplying a buffer (to get received bytes) and a callback method to be called when any bytes are received from the remote application.

C#
/// <summary>
/// This method is used as callback method in _clientSocket's BeginReceive method.
/// It reveives bytes from socker.
/// </summary>
/// <param name="ar">Asyncronous call result</param>
private void ReceiveCallback(IAsyncResult ar)
{
    if(!_running)
    {
        return;
    }

    try
    {
        //Get received bytes count
        var bytesRead = _clientSocket.EndReceive(ar);
        if (bytesRead > 0)
        {
            LastReceivedMessageTime = DateTime.Now;

            //Copy received bytes to a new byte array
            var receivedBytes = new byte[bytesRead];
            Array.Copy(_buffer, 0, receivedBytes, 0, bytesRead);

            //Read messages according to current wire protocol
            var messages = WireProtocol.CreateMessages(receivedBytes);
                    
            //Raise MessageReceived event for all received messages
            foreach (var message in messages)
            {
                OnMessageReceived(message);
            }
        }

        //Read more bytes if still running
        if (_running)
        {
            _clientSocket.BeginReceive(_buffer, 0, _buffer.Length, 0, new AsyncCallback(ReceiveCallback), null);
        }
    }
    catch
    {
        Disconnect();
    }
}

In the ReceiveCallBack method, receiving operation is finished using EndReceive method of the socket. Received bytes are given to the current wire protocol to create messages. If wire protocol could created any messages, a MessageReceived event is raised for each message using OnMessageReceived method of the base class. Lastly, non-blocking BeginRecieve method of Socket is called again to receive next incoming bytes from socket (Note that this is a kind of recursive call since callback method is same as caller method).

Now, let's see how a message is being sent. The ICommunicationChannel.SendMessage(IScsMessage message) method is implemented by the CommunicationChannelBase class and it calls SendMessageInternal method of it's subclass TcpCommunicationChannel:

C#
/// <summary>
/// Sends a message to the remote application.
/// </summary>
/// <param name="message">Message to be sent</param>
protected override void SendMessageInternal(IScsMessage message)
{
    //Send message
    var totalSent = 0;
    lock (_syncLock)
    {
        //Create a byte array from message according to current protocol
        var messageBytes = WireProtocol.GetBytes(message);
        //Send all bytes to the remote application
        while (totalSent < messageBytes.Length)
        {
            var sent = _clientSocket.Send(messageBytes, totalSent, messageBytes.Length - totalSent, SocketFlags.None);
            if (sent <= 0)
            {
                throw new CommunicationException("Message could not be sent via TCP socket. Only " + 
                    totalSent + " bytes of " + messageBytes.Length + " bytes are sent.");
            }

            totalSent += sent;
        }

        LastSentMessageTime = DateTime.Now;
        OnMessageSent(message);
    }
}

It serializes the message (IScsMessage) object to a byte array using the WireProtocol object and sends it to the remote application using the Socket object (_clientSocket). 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, others wait.

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

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 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. ConnectTimeout is used to set a timeout value while connecting to the server. Finally, it defines the CommunicationState property to check current state of the connection (same as channels).

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

C#
/// <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:

C#
/// <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;
    ConnectTimeout = DefaultConnectionAttemptTimeout;
    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 in the last 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):

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

We first reset the wire protocol. This is needed if user re-connect to the server (disconnects and connects over same object). Then we 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 set the WireProtocol of the channel, register to events of the channel and start it. 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:

C#
/// <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.

C#
/// <summary>
/// Creates a communication channel using ServerIpAddress and ServerPort.
/// </summary>
/// <returns>Ready communication channel to communicate</returns>
protected override ICommunicationChannel CreateCommunicationChannel()
{
    return new TcpCommunicationChannel(
        TcpHelper.ConnectToServer(
            new IPEndPoint(IPAddress.Parse(_serverEndPoint.IpAddress), _serverEndPoint.TcpPort),
            ConnectTimeout
            ));
}

It creates a .NET Socket (System.Net.Sockets.Socket) object and creates 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 (ConnectTimeout), throws an exception.

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

C#
/// <summary>
/// This class is used to create SCS Clients to connect to a SCS 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();
    }

    /// <summary>
    /// Creates a new client to connect to a server using an end point.
    /// </summary>
    /// <param name="endpointAddress">End point address of the server to connect it</param>
    /// <returns>Created TCP client</returns>
    public static IScsClient CreateClient(string endpointAddress)
    {
        return CreateClient(ScsEndPoint.CreateEndPoint(endpointAddress));
    }
}

One of ScsClientFactory.CreateClient(...) methods 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 are added, there is no need to change the ScsClientFactory. If the endPoint arguments is a ScsTcpEndPoint (the only end point as default), then a ScsTcpClient object is created in the ScsTcpEndPoint.CreateClient() method as shown below.

C#
/// <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(this);
}

Also, user can pass a string address (like tcp://127.0.0.1:10048 for 10048 TCP port on localhos) instead of an end point. An endpoint is created from the string using ScsEndPoint.CreateEndPoint static method.

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:

C#
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()
        {
            //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;
            
            Console.WriteLine("Press enter to connect to the server...");
            Console.ReadLine(); //Wait user to press enter

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

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

            //Send message to the server
            client.SendMessage(new ScsTextMessage(messageText));                
            
            Console.WriteLine("Press enter to disconnect from server...");
            Console.ReadLine(); //Wait user to press enter
            
            client.Disconnect(); //Close connection to server
        }

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

            Console.WriteLine("Server sent a message: " + message.Text);
        }
    }
}

To demonstrate, client just sends/receives ScsTextMessage objects but can send any type of objects that is derived from ScsMessage (or implements IScsMessage) as mentioned before. 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 client side of SCS. In this section, I will examine that how the server side of SCS Messaging Layer is implemented. The main interface on the server side is IScsServer. Like other inheritance models in 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.

Server classes

Server classes in the SCS framework

IScsServer defines WireProtocolFactory to allow the user to change the messaging protocol (Since server creates a wire protocol object for each client, a factory class is used instead of direct use of WireProtocol). 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). Server class defines ClientConnected and ClientDisonnected events. Using this events, we can be informed when a client has successfully connected to and disconnected from the server. We can get a reference to a connected/disconnected clients in the event handlers using the ServerClientEventArgs event argument.

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

C#
/// <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 = WireProtocolFactory.CreateWireProtocol()
                 };
    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)
{
    var client = (IScsServerClient) sender;
    Clients.Remove(client.ClientId);
    OnClientDisconnected(client);
}

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.

C#
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

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:

C#
/// <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.MessageSent += CommunicationChannel_MessageSent;
    _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.

C#
/// <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.

C#
/// <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(this);
}

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.

C#
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()
        {
            //Create a server that listens 10085 TCP port for incoming connections
            var server = ScsServerFactory.CreateServer(new ScsTcpEndPoint(10085));

            //Register events of the server to be informed about clients
            server.ClientConnected += Server_ClientConnected;
            server.ClientDisconnected += Server_ClientDisconnected;

            server.Start(); //Start the server

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

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

        static void Server_ClientConnected(object sender, ServerClientEventArgs e)
        {
            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;
        }

        static void Server_ClientDisconnected(object sender, ServerClientEventArgs e)
        {
            Console.WriteLine("A client is disconnected! Client Id = " + e.Client.ClientId);
        }

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

            //Get a reference to the client
            var client = (IScsServerClient)sender; 

            Console.WriteLine("Client sent a message: " + message.Text +
                              " (Cliend Id = " + client.ClientId + ")");

            //Send reply message to the client
            client.SendMessage(
                new ScsTextMessage(
                    "Hello client. I got your message (" + message.Text + ")",
                    message.MessageId //Set first message's id as replied message id
                    ));
        }
    }
}

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 you to take a look 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:

Image 13

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. It uses IScsMessage.RepliedMessageId property to match messages.
  • 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.

C#
using System;
using Hik.Communication.Scs.Client;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.Scs.Communication.Messages;
using Hik.Communication.Scs.Communication.Messengers;

namespace RequestReplyStyleClient
{
    class Program
    {
        static void Main()
        {
            Console.WriteLine("Press enter to connect to the server...");
            Console.ReadLine(); //Wait user to press enter

            //Create a client object to connect a server on 127.0.0.1 (local) IP and listens 10085 TCP port
            using (var client = ScsClientFactory.CreateClient(new ScsTcpEndPoint("127.0.0.1", 10085)))
            {
                //Create a RequestReplyMessenger that uses the client as internal messenger.
                using (var requestReplyMessenger = new RequestReplyMessenger<IScsClient>(client))
                {
                    requestReplyMessenger.Start(); //Start request/reply messenger
                    client.Connect(); //Connect to the server

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

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

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

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). Since it is IDisposible, stops automatically when disposed by using statement. Now, let's examine how the RequestReplyMessenger class is implemented.

Since RequestReplyMessenger can 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.

C#
/// <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.

C#
/// <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.

C#
/// <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:

C#
/// <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;
        }
    }

    _incomingMessageProcessor.EnqueueMessage(e.Message);
}

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 _incomingMessageProcessor to be processed (please see the source code for processing of messages in this queue). MessageReceived event is called for non-reply messages.

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

C#
/// <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()
{
    _incomingMessageProcessor.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.

Image 14

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:

C#
/// <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:

C#
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:

C#
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 the real class. User can call a method of the proxy object instead of directly calling the same method of the real object. The proxy object can do some actions before/after calling the real object's method, it can call more than one method or does whatever it wants.

We discussed how a proxy class can be implemented in the Mapping messages with classes and methods section. When a 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:

C#
/// <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.

C#
/// <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.

C#
/// <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:

C#
/// <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;

    _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.

C#
/// <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 Applications

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:

C#
/// <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;
    _scsServer.ClientDisconnected += ScsServer_ClientDisconnected;
    _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.

C#
/// <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 client 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. IScsServiceClient also defines RemoteEndPoint that can be used to get end point address (IP address and TCP port for TCP servers) of the client application.

All services that are built on the SCS framework must be derived from 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.

C#
using System;

namespace Hik.Communication.ScsServices.Service
{
    /// <summary>
    /// Base class for all services that is serviced by IScsServiceApplication.
    /// </summary>
    public abstract class ScsService
    {
        /// <summary>
        /// The current client for a thread that called service method.
        /// </summary>
        [ThreadStatic]
        private static IScsServiceClient _currentClient;

        /// <summary>
        /// Gets the current client which called this service method.
        /// </summary>
        protected internal IScsServiceClient CurrentClient
        {
            get
            {
                if (_currentClient == null)
                {
                    throw new Exception("Client channel can not be obtained. CurrentClient property must be called by the thread which runs the service method.");
                }

                return _currentClient;
            }
            internal set
            {
                _currentClient = value;
            }
        }
    }
}

The CurrentClient is used to get the current client which called the service method. It is used in a method to get a reference to the client object. Note that _currentClient is ThreadStatic, so it is thread safe and gets the right client object even multi clients call the same service method concurrently. Setter is internal and used by SCS system to set the client object before and after calling the service method. Related parts of this operation are shown below:

C#
...

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;
    //Set client to service, so user service can get client
    //in service method using CurrentClient property
    serviceObject.Service.CurrentClient = client;
    try
    {
        returnValue = serviceObject.InvokeMethod(invokeMessage.MethodName, 
                                                 invokeMessage.Parameters);
    }
    finally
    {
        //Set CurrentClient as null since method call completed
        serviceObject.Service.CurrentClient = null;
    }

    //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 setl the ScsService.CurrentClient property, invoking the requested method using Reflection with the given parameters in the message. Finally, we reset theScsService.CurrentClient property. Thus, user can get the client object by using the CurrentClientt property during method invocation.

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.

C#
/// <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

Here, I'll show some performance results of the SCS framework. I made tests in my own PC. All communications are made between application those are running on same computer. You can download all test applications in the download file.

Test platform: Intel Core2 Duo 3,00Ghz CPU, 4GB RAM, Windows 7 64bit.

Speed

SCS performance

As you have seen, approximately 5500 remote method call is made with default serialization. Messaging is far more fast. 34,742 messages are sent from one application to another in a second. When I used a custom wire protocol, I could sent 62,410 messages in a second.

Scalability

In my tests, maximum 15,000 concurrent clients connected to a SCS service. Normally, Windows limits open connection count to ~5,000. I added a Windows registry value to allow more connections. To do that, open HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters registry key and add a DWord value MaxUserPort. Default value of MaxUserPort is 5,000. You can change up to 65,534. You may need to restart your computer to take affect. Also, you can find some other parameters to increase TCP connections here: http://smallvoid.com/article/winnt-tcpip-max-limit.html

Even with 15,000 clients are concurrently connected and communicating with the service, total thread count is approximately 50-60 and one client can get response for a method call in 1 milliseconds or less.

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/WCF services while it is connection oriented. Then I explained almost all constructions of the framework. It's a stable, robust and scalable framework. If you find bugs, please inform me.

Changes/Updates

  • 13.06.2011 (v1.1.0.1)
    •  BugFix: Ping messages must not be raised by messaging layer. 
  • 30.05.2011 (v1.1.0.0)
    • Additions:
      • ClientDisconnected event added to IScsServiceApplication and IScsServer classes.
      • MessageSent event is added to IMessenger interface.
      • SynchronizedMessenger class is added to receive messages as synchronized.
    • Changes/Improvements:
      • Changed background thread mechanism to provide more scalable framework. Used TPL Tasks and Async sockets instead of directly use of threads and blocking sockets (Added SequentialItemProcessor class).
      • Added IScsWireProtocolFactory interface and changed IScsServer.WireProtocol to IScsServer.WireProtocolFactory. Also, IScsWireProtocol is completely changed.
      • BinarySerializationProtocol class is made public to allow user to override serialization methods.
      • Codes completely revised, some parts refactored and commented.
    • Bugfix:
      • Fixed a potential minor bug in Timer.
    • Article:
      • This article is completely updated according to changes in the framework.
  • 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.

License

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