Article outline
What is SCS?
In this article, I will examine an Open Source, lightweight, fast and scalable framework (named Simple Client Server Library (SCS)) that was developed to create server/client applications using the simple Remote Method Invocation mechanism. It is entirely developed in C# and .NET Framework 4.0. It uses TCP as the transport layer protocol, but the core library is written protocol-independent and can be extended to be able to use any other protocol. You can take a look at the second article to see performance of the framework.
SCS allows clients to call methods of a server application over an Interface just like usual method calls in the same application (like Service Contracts in WCF). Server application can also easily call client methods in the same way over an Interface. The SCS framework is a double-way, connection-oriented, and asynchronous communication library. So, after a client connects to the server, they can communicate in both directions (server-to-client or client-to-server) asynchronously until client or server closes the connection. It is not just a Request/Reply communication like web services.
I developed the SCS framework and have been using it in real life. Here, there is a list of some features of SCS:
- It is an Open Source server/client based framework.
- Allows remote method calls from client to server and from server to client easily. Can throw exceptions across applications.
- Allows acynhronous or synchronous low level messaging instead of remote method calls.
- It is scalable (15000+ clients concurrently connected and communicating while server has only 50-60 threads) and fast (5,500 remote method calls, 62,500 messages transfer between applications in a second running in a regular PC).
- Allows clients to automatically reconnect to the server.
- Allows clients to automatically ping to the server to keep the connection available when no communication occurs with the server for a while.
- Allows a server to register events for new client connections, disconnecting of a client, etc.
- Allows a client to register events for connecting and disconnecting.
- It is suitable for long session connections between clients and server.
In this article, we will develop two sample applications using the SCS framework to examine the usage and its benefits.
The first example is a simple phone book application. In this example, we will see how to build a Request/Reply style phone book server/client architecture. The phone book will be stored in the server, and clients can connect to the server, add/delete or query a phone number for a person. For simplicity, they will be console based applications.
In the second example, we will develop a complete chat system that has public and private messaging. The user interface will be developed in WPF. The user will take a nick and connect to a chat server. A connected user can see all other users in the chat room, can send public messages to the room (all users see the message), and can chat with any other user privately.
A simple server/client based phone book application using SCS
As I mentioned above, clients and the server communicate with remote method calls over service/client contract interfaces. These interfaces are generally defined in a separate assembly (DLL) to be used by both client and server applications. So, let's create a new Class Library Project named PhoneBookCommonLib in the solution OnlinePhoneBook, as shown below.
Figure 1: Creating a Class Library project to define the service contract for the PhoneBook server
First, we must add a reference to the SCS framework:
Now, we will define the service contract interface named IPhoneBookService
to define the methods that will be remotely called by the clients.
using Hik.Communication.ScsServices.Service;
namespace PhoneBookCommonLib
{
[ScsService(Version = "1.0.0.0")]
public interface IPhoneBookService
{
void AddPerson(PhoneBookRecord recordToAdd);
bool DeletePerson(string name);
PhoneBookRecord FindPerson(string name);
}
}
Service contracts must be marked by the ScsService
attribute. You can define the version of your service. In any RMI exception, clients get this version information in the exception message, so they can know if the service has changed. The PhoneBookRecord
class is shown below:
using System;
namespace PhoneBookCommonLib
{
[Serializable]
public class PhoneBookRecord
{
public string Name { get; set; }
public string Phone { get; set; }
public DateTime CreationDate { get; set; }
public PhoneBookRecord()
{
CreationDate = DateTime.Now;
}
public override string ToString()
{
return string.Format("Name = {0}, Phone = {1}", Name, Phone);
}
}
}
If the service/client contracts define a custom class in method invocations, that class must be marked as Serializable
. So, here it is just like the usual interface and class definitions. Let's start developing the server side by creating a new Console Application Project in our solution, named PhoneBookServer.
Figure 2: Creating a Console Application to build the PhoneBook server
First we must add references to the SCS framework and the PhoneBookCommonLib project. Then we must implement the IPhoneBookService
interface. We create a class named PhoneBookService
as shown below:
using System;
using System.Collections.Generic;
using Hik.Communication.ScsServices.Service;
using PhoneBookCommonLib;
namespace PhoneBookServer
{
class PhoneBookService : ScsService, IPhoneBookService
{
private readonly SortedList<string, PhoneBookRecord> _records;
public PhoneBookService()
{
_records = new SortedList<string, PhoneBookRecord>();
}
public void AddPerson(PhoneBookRecord recordToAdd)
{
if (recordToAdd == null)
{
throw new ArgumentNullException("recordToAdd");
}
_records[recordToAdd.Name] = recordToAdd;
}
public bool DeletePerson(string name)
{
if (!_records.ContainsKey(name))
{
return false;
}
_records.Remove(name);
return true;
}
public PhoneBookRecord FindPerson(string name)
{
if (_records.ContainsKey(name))
{
return _records[name];
}
foreach (var record in _records)
{
if (record.Key.ToLower().Contains(name.ToLower()))
{
return record.Value;
}
}
return null;
}
}
}
All services must derive from the ScsService
class and implement the service contract. Our phone book service is almost complete. Now we change application's Main
method to create the server application to finish the phone book service.
using System;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.ScsServices.Service;
using PhoneBookCommonLib;
namespace PhoneBookServer
{
class Program
{
static void Main(string[] args)
{
var server = ScsServiceBuilder.CreateService(new ScsTcpEndPoint(10048));
server.AddService<IPhoneBookService,
PhoneBookService>(new PhoneBookService());
server.Start();
Console.WriteLine(
"Phone Book Server started successfully. Press enter to stop...");
Console.ReadLine();
server.Stop();
}
}
}
We first create a service application that listens to the 10048 TCP port for incoming client connections. Then we add our phone book service to this service application. While adding a service, we must specify the service contract (IPhoneBookService
) and the service class (PhoneBookService
) that implements the contract. We can add more than one service to the service application to run more than one service from the same end point (same TCP port). That's all; until the service is stopped by the user, clients can connect to the service and call the service methods.
Now, we can create a client application that uses this service. Create a new Console Application Project named PhoneBookClient in the OnlinePhoneBook solution.
Figure 3: Creating a Console Application to build the PhoneBook client
First add references to the SCS framework and the PhoneBookCommonLib project. Then we can write a sample client code that uses the phone book service:
Using System;
using Hik.Communication.Scs.Communication.EndPoints.Tcp;
using Hik.Communication.ScsServices.Client;
using PhoneBookCommonLib;
namespace PhoneBookClient
{
class Program
{
static void Main(string[] args)
{
Var client = ScsServiceClientBuilder.CreateClient<IPhoneBookService>(
new ScsTcpEndPoint("127.0.0.1", 10048));
Console.WriteLine("Press enter to connect to phone book service...");
Console.ReadLine();
client.Connect();
var person1 = new PhoneBookRecord { Name = "Halil Ibrahim",
Phone = "5881112233" };
var person2 =
new PhoneBookRecord { Name = "John Nash", Phone = "58833322211" };
client.ServiceProxy.AddPerson(person1);
client.ServiceProxy.AddPerson(person2);
var person = client.ServiceProxy.FindPerson("Halil");
if (person != null)
{
Console.WriteLine("Person is found:");
Console.WriteLine(person);
}
else
{
Console.WriteLine("Can not find person!");
}
Console.WriteLine();
Console.WriteLine("Press enter to disconnect from phone book service...");
Console.ReadLine();
client.Disconnect();
}
}
}
When you look at the code above, calling a method of the service is very easy and straightforward. You can use all the methods that are defined in IPhoneBookService
. We first create a client object that connects to the server to use IPhoneBookService
that is running on the local machine (127.0.0.1), 10048 TCP port. Then we connect to the server, call the remote methods, and disconnects in the end. If you first run PhoneBookServer, then run PhoneBookClient, and press Enter, you will see two console screens like:
Figure 4: Screenshot of running PhoneBook client and server
As shown in this example application, you can easily create TCP based server/client applications in .NET using the SCS framework.
More features on the client side
The SCS client supports the IDisposible
interface, so you do not have to disconnect from the server if you write the code in a using
block like this:
using (var client = ScsServiceClientBuilder.CreateClient<IPhoneBookService>(
new ScsTcpEndPoint("127.0.0.1", 10048)))
{
client.Connect();
client.ServiceProxy.AddPerson(
new PhoneBookRecord {Name = "Halil", Phone = "2221144"});
}
This is useful if you want to connect to the server, call service methods, and disconnect just like web service method calls. In fact, you do not have to explicitly connect/disconnect. This code will also work fine:
var client = ScsServiceClientBuilder.CreateClient<IPhoneBookService>(
SscEndPoint.CreateEndPoint("tcp://127.0.0.1:10048")))
client.ServiceProxy.AddPerson(
new PhoneBookRecord {Name = "Halil", Phone = "2221144"});
In this situation, SCS automatically connects, calls AddPerson method and disconnects. If you want to make a single method call, this approach is usefull. But if you want to make more than one call with a client object, it is much more performant to connect and disconnect explicitly. Note that I used ScsEndPoint.CreateEndPoint(...)
method to create a ScsTcpEndPoint
object from a string address.
SCS client also provides a Timeout
property (as milliseconds) for a remote method call, a CommunicationState
property to check if a client is connected to the server, and Connected
and Disconnected
events that are raised when a client is connected to or disconnected from server, respectively.
If server throws an exception, a client can catch it using a try/catch
statement. For example, PhoneBookService
throws an ArgumentNullException
in the AddPerson
method if the recordToAdd
object is null
. You can write code like this to catch it:
try
{
client.ServiceProxy.AddPerson(null);
}
catch (Exception ex)
{
Console.WriteLine();
Console.WriteLine(ex.Message);
}
When you run this code, you will see a message like this:
Figure 5: Console output of PhoneBook client application on an exception situation
As you can see, the service version is automatically added to the exception message.
More features on the server side
We create a server side that just responds to remote method calls and does nothing else. We can also call client methods just like client-to-server method invocations. I will briefly talk about some functionality here, but we will better understand these functionalities in the next example (in the IRC (chat) application).
To control a client on the server side, we first need to get a reference to the IScsServiceClient
interface of the client by using the ScsService.CurrentClient
property. We can use this property in any method of PhoneBookService
like:
Figure 6: The CurrentClient property of the ScsService class
This property is only valid if this method is defined in the service contract (IPhoneBookService
) and called by the client. It is thread safe, and it gets the current client which is calling this method. After getting (and storing) a reference to the client, we can get its unique number using the ClientId
property, communication state (Connected/Disconnected) using the CommunicationState
property, and we can register to the Disconnected
event to be informed when the client disconnects from the server. Last but not least, we can get a Proxy
object to call remote methods of the client using the GetClientProxy
method. This method is generic; it gets the type of the client contract interface as a generic parameter. Before using this method, we must define an interface that the client implements it (we will see it in the next example chatg application). So, if we get the proxy object and store it, we can call the client methods when we want. This way, we can go beyond the Request/Reply style server/client architecture.
An IRC chat system using the SCS framework
You probably know what IRC is. Although it is not widely used nowadays as MSN, it was very popular in the past. IRC is an acronym for Internet Relay Chat. Simply, in IRC, there are rooms that users join. After joining a room, you can see all the other users who are currently in the room. You can write a message to the room (that is seen by all users in the room), or you can click a user in the room and chat with the user privately (other users don't see your private messages except the user who you are messaging). Our simple IRC system provides just one room for users. They automatically join the default room and can begin to chat immediately after selecting a nick.
In this example application, I will demonstrate the usage of the system, then explain how I implemented it over the SCS framework. I will not go deep into all the parts of the application other than the SCS-related parts.
Using the chat system
After downloading the chat system solution to your computer, first run the server application (ChatServerApp.exe), enter a TCP port number (or leave 10048 as the default value) to listen to clients, then click the Start Server button. Now you can see the server form as shown below:
Figure 7: Chat server application
In the sample screen above, you see four online users that are connected to the server and chatting. Now run ChatClientApp.exe to start the client application. You will see a window like below.
Figure 8: Login screen of the chat client application
You can enter a nick, the server's IP, and the TCP port. Leave the IP and port as default if you are testing in the same computer with the server application; otherwise you can enter the IP and port of the server application. You can change your avatar picture by right clicking the picture as shown below:
Figure 9: Changing the avatar picture in the chat client application
You can select the default male/female picture, or your own image. If you run a few copies of the client application with different nicks, you will see a screen like this:
Figure 10: Sample screenshot from the chat client application while chatting on a public room
You can write messages in different message styles, set your status, set sound on/off... etc. The main window of the client allows you to chat publicly. All users in a room can see your messages. If you double click a user in the user list, you can chat privately with that user:
Figure 11: Chatting in a private window
You are free to investigate the chat application more deeply. Maybe it is more than a sample application to demonstrate the usage of the SCS framework.
Implementation of the chat server
Now I will examine the implementation of the chat server. Ihave created three project: ChatServerApp (WPF application), ChatClientApp (WPF application), and ChatCommonLib (DLL project). The last one is used to define the service and client contracts and the objects that are transferred between the clients and the server.
The server contract interface defines the methods of the server that can be called by the clients remotely. It is defined in the IChatServer
interface in the ChatCommonLib assembly.
using Hik.Communication.ScsServices.Service;
using Hik.Samples.Scs.IrcChat.Arguments;
namespace Hik.Samples.Scs.IrcChat.Contracts
{
[ScsService(Version = "1.0.0.0")]
public interface IChatService
{
void Login(UserInfo userInfo);
void SendMessageToRoom(ChatMessage message);
void SendPrivateMessage(string destinationNick, ChatMessage message);
void ChangeStatus(UserStatus newStatus);
void Logout();
}
}
I think all methods describe themselves. The client first calls the Login()
method (when the user clicks the Login button on the client window) to login to the server. Then it can send messages to the room by using the SendMessageToRoom(...)
method, send private messages by using the SendPrivateMessage(...)
method, and so on. Finally, when the user closes the main window, the client calls the Logout()
method.
We must first implement the server contract (IChatServer
), then register it to a SCS Server Application object (as we did before in the PhoneBook application). I implemented the IChatServer application using the ChatServer
class. I will examine here the various methods in detail. The first one is the Login
method. It is defined as below:
public void Login(UserInfo userInfo)
{
if (FindClientByNick(userInfo.Nick) != null)
{
throw new NickInUseException("The nick '" + userInfo.Nick +
"' is being used by another user. Please select another one.");
}
var client = CurrentClient;
var clientProxy = client.GetClientProxy<IChatClient>();
var chatClient = new ChatClient(client, clientProxy, userInfo);
_clients[client.ClientId] = chatClient;
client.Disconnected += Client_Disconnected;
Task.Factory.StartNew(
() =>
{
OnUserListChanged();
SendUserListToClient(chatClient);
SendUserLoginInfoToAllClients(userInfo);
});
}
First we check if there is a user with the same nick. If so, we throw a NickInUseException
. We can safely throw exceptions in service methods. It is handled by the SCS framework and thrown on the client side (the exception must be serializable; see NickInUseException
for a sample; as far as I know, all predefined exception classes in the .NET Framework are serializable, you can throw them or define your own if needed). Then we get a reference to the current client object (that implements IScsServiceClient
) by using the CurrentClient
property. This is the key object if our system uses client side contract interfaces (that are called by the server). This property is thread safe (even if more than one client thread calls the same method, you can get the right client object using this property). Now, we can obtain a reference to the client contract interface by calling the GetClientProxy<IChatClient>()
method. This is a generic method. You can call it with any interface. We call it with IChatClient
because our client side methods are defined in the IChatClient
interface (we will see the definition and implementation of this interface soon; we will call all client methods using this dynamic and transparent proxy reference). Then we create a ChatClient
object to store client information. It is defined as below as a subclass of the ChatService
class:
private sealed class ChatClient
{
public IScsServiceClient Client { get; private set; }
public IChatClient ClientProxy { get; private set; }
public UserInfo User { get; private set; }
...
}
After creating a ChatClient
object, we add it to a kind of SortedList
collection named _clients
. We use the ClientId
of the current client as the key for that client. ClientId
is a unique (long) number that is generated automatically by the SCS framework and assigned to clients. We can safely use this value to distinguish clients. The _clients
collection is defined as below:
private readonly ThreadSafeSortedList<long, ChatClient> _clients;
ThreadSafeSortedList
is a kind of wrapper I wrote to perform thread safe operations on a SortedList
collection. Because we are serving all clients in a multithreaded manner, all methods may be called simultaneously. So we must prevent a collection from being accessed by two or more threads simultaneously. ThreadSafeSortedList
internally does that.
Last but not least, we register to the Disconnect
event of the client. This is needed to be informed when the user disconnects from the server (especially if it is disconnected before calling Logout
). The rest of the Login
method is implementation details. We inform the GUI for user list changing, send a list of all online users to a newly connected client (in the SendUserListToClient(...)
method), and send a notification to all other users that there is a new user connected to the server (these three operations are performed in a separate thread to not block a user for a long time, because remote method invocations on SCS are blocking operations).
Now, let's look at the implementation of the Logout()
method in the ChatServer
class.
public void Logout()
{
ClientLogout(CurrentClient.ClientId);
}
private void Client_Disconnected(object sender, EventArgs e)
{
var client = (IScsServiceClient)sender;
ClientLogout(client.ClientId);
}
private void ClientLogout(long clientId)
{
var client = _clients[clientId];
if (client == null)
{
return;
}
_clients.Remove(client.Client.ClientId);
client.Client.Disconnected -= Client_Disconnected;
Task.Factory.StartNew(
() =>
{
OnUserListChanged();
SendUserLogoutInfoToAllClients(client.User.Nick);
});
}
In the Logout
method, we get the unique ClientId
value of the client that has called the Logout
method and is calling the ClientLogout(...)
method. We also handle the Disconnect
event of clients in the Client_Disconnected(...)
method. Getting a reference to the client object is different in this event handler method. It is obtained by casting the sender
object to the IScsServiceClient
interface (CurrentClient
property is only valid in the service contract methods). The ClientLogout
method simply gets and removes the client object from the _clients
collection, unregisters the Disconnected
event (even though it is not an obligation), then informs all other users that a user logged out from the server.
Now, I will examine the implementation of the SendPrivateMessage(...)
method. This method is used to send a private message to a user, and is defined as below.
public void SendPrivateMessage(string destinationNick, ChatMessage message)
{
var senderClient = _clients[CurrentClient.ClientId];
if (senderClient == null)
{
throw new ApplicationException("Can not send message before login.");
}
var receiverClient = FindClientByNick(destinationNick);
if (receiverClient == null)
{
throw new ApplicationException("There is no online " +
"user with nick " + destinationNick);
}
receiverClient.ClientProxy.OnPrivateMessage(senderClient.User.Nick, message);
}
First, we get the client
object (that is a ChatClient
object) from the _clients
list and checks if the current user logged in (remember that we store clients in the _clients
list in the Login()
method using their unique ClientId
). Then we try to find the destination user using the FindClientByNick(...)
method. It is a simple method that is defined as below:
private ChatClient FindClientByNick(string nick)
{
return (from client in _clients.GetAllItems()
where client.User.Nick == nick
select client).FirstOrDefault();
}
It uses a LINQ expression to find a ChatClient
object in the _clients
collection using the user nick. If there is no user with that nick, it returns null
. The SendPrivateMessage(...)
method throws an exception if no client exists that uses the destination nick (this may happen if the destination user has left the chat room just before this method was invoked; this is low probability, but we must handle it anyway (Murphy's law says that "If anything bad can happen, it probably will.")). At the end of the SendPrivateMessage(...)
method, we call the destination client's OnPrivateMessage(...)
method remotely (this method is defined in the IChatClient
interface and will be examined soon) so it can get the incoming message.
It will not be explained here but the ChatServer
class also defines an event named UserListChanged
to notify the server GUI (WPF window) when a user is added or removed from the _clients
collection. The ChatServer
class is created and used by MainWindow
of the chat server. In the handler method of the Click
event of the btnStart
button, we create and start the service just like below:
_serviceApplication = ScsServiceBuilder.CreateService(new ScsTcpEndPoint(port));
_chatService = new ChatService();
_serviceApplication.AddService<IChatService, ChatService>(_chatService);
_chatService.UserListChanged += chatService_UserListChanged;
_serviceApplication.Start();
It is very similar to the PhoneBookService application. We create a SCS service application that runs on a TCP port (that is entered by the user in the txtPort
textbox). Then we create an instance of the ChatService
class, registers it to the service application using the AddService
method, registers to the UserListChanged
event (to reflect user list changes to the GUI), and start the service application. To stop the server, we call the Stop
method of IScsServiceApplication
.
_serviceApplication.Stop();
We have learned how to write a multithreaded server that can be invoked by clients and can invoke client methods. By the way, you can add more than one service to a service application that runs on the same end point (TCP port). The SCS framework handles this situation, and calls the correct method of the correct service according to the clients that are using the service (remember that you supply the service contract interface when creating a client object on the client side). This approach is very useful if you don't want to use separate ports for each service and run more than one service in the same process.
Implementation of the chat client
Although our sample chat application is a little complex (because of WPF and other additional functionalities), the SCS related parts are easy to understand. Using the service by calling remote methods of the service contract is the same as the PhoneBook client. In addition, the chat client can be invoked by the server (this is the two-way communication of the SCS framework), so it must define a client contract. It is defined in the IChatClient
interface like below:
using Hik.Communication.ScsServices.Service;
using Hik.Samples.Scs.IrcChat.Arguments;
namespace Hik.Samples.Scs.IrcChat.Contracts
{
public interface IChatClient
{
void GetUserList(UserInfo[] users);
void OnMessageToRoom(string nick, ChatMessage message);
void OnPrivateMessage(string nick, ChatMessage message);
void OnUserLogin(UserInfo userInfo);
void OnUserLogout(string nick);
void OnUserStatusChange(string nick, UserStatus newStatus);
}
}
The client contract methods define themselves in code comments. This interface is implemented in the client application by the ChatClient
class of ChatClientApp
. For example, the OnPrivateMessage(...)
method is called by the server when another user sends a private message to this user. So, we must open a private chat window (if not already open) with the sender of the message and write a message to the message area. In this article, I will explain the implementation of the OnPrivateMessage(..)
method; for other methods, please see the ChatClient
class. The implementation of this method is shown below:
public void OnPrivateMessage(string nick, ChatMessage message)
{
_chatRoom.OnPrivateMessageReceived(nick, message);
}
It does nothing but calls the OnPrivateMessageReceived(...)
method of the IChatRoomView
interface. This interface is independent from the GUI. The IChatRoomView
interface is implemented by the MainWindow
class as shown below:
public partial class MainWindow : Window, IChatRoomView,
ILoginFormView, IMessagingAreaContainer
{
. . .
}
As you can see, MainWindow
also implements ILoginFormView
(it has GUI controls to allow the user to login to the server; see figure 8) and IMessagingAreaContainer
(it has a messaging area to show incoming messages to the public room; see figure 10.). By the way, MainWindow
implements the IChatRoomView.OnPrivateMessageReceived
method as shown below:
public void OnPrivateMessage(string nick, ChatMessage message)
{
_chatRoom.OnPrivateMessageReceived(nick, message);
}
If you have worked before with WPF, you know that you can only use a WPF object on the thread on which it was created. Using it on other threads will cause a runtime exception. So, for example, you can not change the Text
value of a TextBox
in another thread than the UI thread. The SCS framework is multithreaded, therefore OnPrivateMessageReceived
is called by another thread than the UI thread. So, we use a Dispatcher
object to call a method from the UI thread. This method is OnPrivateMessageReceivedInternal
and is defined as below:
private void OnPrivateMessageReceivedInternal(string nick, ChatMessage message)
{
var userCard = FindUserInList(nick);
if (userCard == null)
{
return;
}
if (!_privateChatWindows.ContainsKey(nick))
{
_privateChatWindows[nick] = CreatePrivateChatWindow(userCard);
_privateChatWindows[nick].WindowState = WindowState.Minimized;
WindowsHelper.FlashWindow(new WindowInteropHelper(
_privateChatWindows[nick]).Handle,
WindowsHelper.FlashWindowFlags.FLASHW_ALL, 2, 1000);
}
_privateChatWindows[nick].MessageReceived(message);
}
We first check if the user is in the user list. Then we check if _privateChatWindows
(list of open private chat windows) contains a window with that user. If not, we create a new window, set its state minimized, and we apply a flashing effect in the taskbar like MSN. Finally, we call the MessageReceived
event of the private messaging window (PrivateChatWindow
class).
public void MessageReceived(ChatMessage message)
{
MessageHistory.MessageReceived(_remoteUserNick, message);
if (!IsActive)
{
WindowsHelper.FlashWindow(_windowInteropHelper.Handle,
WindowsHelper.FlashWindowFlags.FLASHW_TRAY, 1, 1000);
ClientHelper.PlayIncomingMessageSound();
}
}
This methods calls the MessageReceived(...)
method of the messaging area (MessagingAreaControl
user control), than flashes the taskbar button of the window to inform the user if this window is not active. Finally, the MessagingAreaControl.MessageReceived
method adds a message to the RichTextBox
. That's all; now we know how a private message is sent from the server, received from the client, and shown to the user.
Now, let's see how the client connects to the SCS chat server. It's done in the Connect()
method of the ChatController
class.
public void Connect()
{
Disconnect();
_chatClient = new ChatClient(ChatRoom);
_scsClient = ScsServiceClientBuilder.CreateClient<IChatService>(
new ScsTcpEndPoint(LoginForm.ServerIpAddress,
LoginForm.ServerTcpPort), _chatClient);
_scsClient.Connected += ScsClient_Connected;
_scsClient.Disconnected += ScsClient_Disconnected;
_scsClient.Connect();
}
The only difference with the PhoneBook client in creating a client object is that we pass an object (_chatClient
) that implements the IChatClient
interface to handle incoming remote method calls from the server. We examined OnPrivateMessage(...)
method of ChatClient
class above.
As you know from the PhoneBook client application, we are using a ServiceProxy
to call a method of the server as below.
public void SendPrivateMessage(string nick, ChatMessage message)
{
_scsClient.ServiceProxy.SendPrivateMessage(nick, message);
}
With the SendPrivateMessage(...)
method of the ChatController, we have finished examining the client side. You can see the code to more deeply understand how I have implemented the client. But we have seen all the important SCS-related parts for this article.
Some other benefits of the SCS client side
Automatically reconnecting
If you are building client applications that must always stay connected to the server, you encounter the re-connecting problem when disconnected from a server. You can, of course, handle this problem by registering the Disconnect
event of the client object and trying to reconnect to the server until a connection is successfully established. Fortunately, the SCS framework has a class named ClientReconnecter
that does reconnecting to the server on your behalf. First, you must create a ClientReconnecter
object using your client object, as shown below.
var reconnecter = new ClientReConnecter(_scsClient) {ReConnectCheckPeriod = 30000};
That's all (see the ChatController.Connect()
method to know what _scsClient
is). If your connection fails, the reconnecter will try to reconnect in 30 second intervals until it is successfully connected. The default value for ReConnectCheckPeriod
is 20 seconds. To stop/dispose the reconnecter, you can use the Dispose()
method.
Automatically pinging to stay connected
In TCP, if the server and clients don't send messages to each other (that means the communication line is idle), the connection may be lost (because of the Operating System, firewalls, routers, etc.). The SCS framework overcomes this issue by sending a special kind of ping message periodically if the communication line is idle. The period of ping messages is 30 seconds. If the client and server are already communicating, no ping message is sent.
Last words on this article
In this article, I have introduced a new framework that can be used to develop TCP based client/server systems in .NET. It has similarities with WCF but is much more simple. I think SCS may be enough if you are developing both the server and client in .NET. SCS Services are built upon a layer that can communicate with messaging. This layer is also available to the user. It has no RMI support, but has other benefits like allowing you to change the wire protocol for messaging so your application can communicate with other applications that are not built in .NET. I did not explain this layer in this article but will in the second article.
About the second article
I have explained the implementation of the SCS framework in the second article. It demonstrates how a complete RMI framework can be accomplished upon raw TCP socket streaming. Here is the link to the second article: A Complete TCP Server/Client Communication and RMI Framework in C# .NET - Implementation.
History
- Jun 13, 2011
- SCS framework updated to v1.1.0.1.
- May 29, 2011
- SCS framework updated to v1.1.0.0. (Click here to see all changes/additions)
- Article updated according to changes.
- Apr 12, 2011
- Updated source code of SCS.