Implementing client-server communication using serialization and TCP/IP in C#
As software developers, we are always developing applications that can communication with other components: A server side script that echoes html to the browser, the client application that send information to a remote server endpoint and etc. One of the requirements that I got from my project was to display feedback from a windows service. However, because of session 0 isolation in windows 7, invocations of visual display logic from the windows service application is not enough to fulfill the requirement. In order to display feedback from a windows service application, I created a separate form application that runs when users log in and have the form application connects to the windows service application via TCP/IP to listen for feedback. Communication between the two applications is achieved via Object Serialization in .NET framework.
Reasons for using Object Serialization over TCP/IP
While there are a few ways to workaround the session 0 isolation problem, I chose TCP/IP mainly because of my familiarity with the protocol. In addition, the keep alive nature of the protocol made the coding work more straightforward. By using serialization and TCP/IP as the communication protocol between my applications, I saved some time for developing other areas of my project.
Defining the message payload
I modeled the Message
class based on the ShowBalloonTip
method of the NotifyIcon
class. Hence, each message received from the windows service application includes a title, a content/description, as well as a type. With this design in mind, defining the message payload is pretty straightforward:
using System; using System.Runtime.Serialization; // Enumeration that define the type of message public enum MessageType { Error, Warning, Info } [Serializable] public class Message { private MessageType _messageType; private string _messageTitle; private string _messageContents; public Message(string title, string contents, MessageType type) { this.Title = title; this.Contents = contents; this.Type = type; } public MessageType Type { get { return this._messageType; } set { this._messageType = value; } } public string Title { get { return this._messageTitle; } set { this._messageTitle = value; } } public string Contents { get { return this._messageContents; } set { this._messageContents = value; } } } // end public class Message
This is it, by marking the class with Serializable
attribute, instances of the Message
class can be sent and received via Object Serialization. The next step would then to implement the MessageServer
and the MessageClient
classes.
The MessageServer class
The MessageServer
class utilizes TcpListener
to allow instances of MessageClient
to connect to it. Connected clients are then stored in a Dictionary with the username of the current active client, which is retrieved via Machine.getInstance().getUsername()
function that was discussed in a previous post. Also, the MessageServer
provides a SendMessageToActiveClient
method that utilizes the Serialize
method of BinaryFormatter
to send instances of the Message
class through the TcpClient
instance that is associated with the active user. With such configurations, whenever there is a feedback from the windows service application, the feedback is always sent to the windows form application instance of the currently active user. The windows form application instance can then display the feedback to the active user.
using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; public class MessageServer { private int _port; private TcpListener _tcpListener; private Dictionary<string, TcpClient> _clientsDictionary; private bool _running, _disposed; private BinaryFormatter _bFormatter; private Thread _connectionThread; // Create a message server that listens on the indicated port public MessageServer(int port) { this._port = port; this._tcpListener = new TcpListener(IPAddress.Loopback, port); this._clientsDictionary = new Dictionary<string,TcpClient>(); this._running = false; this._bFormatter = new BinaryFormatter(); } // end public MessageServer(int port) public void Start() { if (!_running) { this._tcpListener.Start(); this._running = true; this._connectionThread = new Thread (new ThreadStart(ListenForClientConnections)); this._connectionThread.Start(); } // end if (!_running) } // end public void Start() public void Stop() { if (this._running) { this._tcpListener.Stop(); this._running = false; } } // end public void Stop() public bool Running() { return this._running; } // end public bool Running() // Thread body for listening for client connections private void ListenForClientConnections() { while(this._running) { TcpClient connectedTcpClient = this._tcpListener.AcceptTcpClient(); // Remember the current client connection string activeUsername = Machine.getInstance().getUsername(); lock (this) { // If there is another connection from a same client if (this._clientsDictionary.ContainsKey(activeUsername)) { // close the connection. this._clientsDictionary[activeUsername].Close(); // Remember the new connection this._clientsDictionary[activeUsername] = connectedTcpClient; } // Else else { // Remember the new connection this._clientsDictionary.Add(activeUsername, connectedTcpClient); } // end if } // end lock(this._clientsDictionary) } // end while(this._running) } // end private void ListenForClientConnections() // Send a message to the currently logged in user public void SendMessageToActiveClient(Message message) { lock(this) { // Get the current active user string activeUsername = Machine.getInstance().getUsername(); // If client had connected to the message server if (this._clientsDictionary.ContainsKey(activeUsername)) { try { // send message to client this.sendMessage (this._clientsDictionary[activeUsername], message); } catch (Exception) { // close the client connection this._clientsDictionary[activeUsername].Close(); // Remove the client connection from memory this._clientsDictionary.Remove(activeUsername); } // end try-catch } // end if } // end lock } // end public void sendMessageToActiveClient() private void sendMessage(TcpClient client, Message message) { // Send message to the client. _bFormatter.Serialize(client.GetStream(), message); } // end private void sendMessage(TcpClient client, Message message) } // end public class MessageServer
The MessageClient class
The MessageClient
utilises the TcpClient
class to connect to the MessageServer
. After connecting to the MessageServer
, the TcpClient
uses the Deserialize
method of the BinaryFormatter
class to listen to instances of Message
received from the MessageServer
. When an instance of Message
is received, the MessageClient
will trigger the MessageReceived
event.
using System; using System.Net; using System.Net.Sockets; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; public delegate void MessageReceivedEventHandler(object sender, Message message); public class MessageClient { private int _port; private TcpClient _tcpClient; private BinaryFormatter _bFormatter; private Thread _listenThread; private bool _running, _disposed; public event MessageReceivedEventHandler MessageReceived; public MessageClient(int port) { this._port = port; this._tcpClient = new TcpClient("localhost", port); this._bFormatter = new BinaryFormatter(); this._running = false; } // end public MessageClient(int port) public void StartListening() { lock (this) { if (!_running) { this._running = true; this._listenThread = new Thread (new ThreadStart(listenForMessage)); this._listenThread.Start(); } else { this._running = true; this._tcpClient = new TcpClient("localhost", this._port); this._listenThread = new Thread (new ThreadStart(listenForMessage)); this._listenThread.Start(); } // end if (!_running) } // end lock (this) } // end public void StartListening() private void ListenForMessage() { try { while (this._running) { // Block until an instance Message is received Message message = (Message)this._bFormatter.Deserialize (this._tcpClient.GetStream()); // Notify all registered event handlers about the message. if (MessageReceived != null && message != null) { MessageReceived(this, message); } // end if MessageReceived != null } // end while } catch (Exception) { this._running = false; } // end try-catch } // end private void ListenForMessage(); public void StopListening() { lock (this) { if (this._running) { this._tcpClient.Close(); _running = false; } } // end lock(this) } // end public void StopListening() } // end class MessageClient
2 Comments
I really like this post. However, looking at the MessageServer code, it’s hard to see how the server can store multiple MessageClients (tcp clients). I can see a dictionary, but how does it get populated with more than one client The following line suggest that the activeUsername will always be the same, i.e the user name of the machine, running the server (TcpListener).
string activeUsername = Machine.getInstance().getUsername();
Am I missing something.
Hi Rob,
Thank you for your thoughts.
This post is an abstract from a project which I had done in the past. Machine.getInstance().getUsername() is supposed to return the username of the current client (windows user) who just logs into the machine.
You can find out more about the Machine class in “How to retrieve the username of the user who logged onto Windows from windows service“.