This article is compatible with the latest version of Silverlight.
1. Introduction
Currently Silverlight supports two ways of duplex communication: sockets and WCF polling duplex services. I was really impressed by the simplicity of using WCF polling duplex in Silverlight, but its responsiveness and scalability leaves much to be desired. On the opposite site we have sockets, which are rather tricky to use, but their responsiveness and scalability satisfy real world application requirements.
In this article I’m going to introduce my SocketsLight framework, which should simplify the working with sockets by providing high level API over .NET sockets library.
Download source code
2. Content
2.1 Problem
As you know sockets have rather low level API, which allows you to transfer byte arrays frames using TCP. Working with them is not simple and error-prone, that is why I have decided to create lightweight framework, which would hide all complexity and provide similar message oriented API like that we have in polling duplex.
2.2 Solution
My SocketsLight framework provides a set of classes, which encapsulate byte array frames communication behavior. It automatically serializes user defined message objects and then split them into byte array frames, which would be transferred using sockets. The next picture illustrates the fundamental idea on which the library is based.
2.3 How it works
The whole framework is divided into client (Silverlight class library) and server (.NET class library) parts.
On the server side you will mostly deal with IMessageServer and IMessageSerializer interfaces and their implementations MessagerServer and JsonMessageSerializer. I strongly recommend you to only use interfaces and inject implementations in application bootstrapper with your favorite DI container.
IMessageServer informs you, when a client has connected or disconnected or a new message has been received, and provides a method to send message to the client with specified identifier (Guid).
IMessageSerializer defines how a message will be serialized. Currently there is only one implementation of this interface based on DataContractJsonSerializer. I found JSON format most effective and compact for this purpose, but you can easily create your own. I’ve also tried DataContractSerialzier and XmlSerializer, but its serialized message size is much bigger.
All message classes should be inherited from base Message class (empty class, used to identify that an object is a message) and be decorated with DataContract and DataMembers attributes, which is needed for .NET DataContract serializes.
On the client side you also need IMessageSerializer and another interface called IMessageClient and their implementations DataContractJsonSerializer and MessageClient. As for server side classes you should also use interfaces instead of implementations.
IMessageClient provides a set of methods to connect and disconnect to the message server, send and receive messages. It also informs if the connection with the server has been lost.
3. Example
In this example I will create a simple weather service, which will supply temperature info to its subscribers.
Weather service.
To use SocketLight framework we will inject IMessageServer through the constructor of our weather service and subscribe on its events. I’ve also created Timer object, which will invoke weather update on each 5 second.
public WeatherService(IMessageServer messageServer)
{
this.messageServer = messageServer;
this.messageServer.ClientConnected += this.messageServer_ClientConnected;
this.messageServer.ClientDisconnected += this.messageServer_ClientDisconnected;
this.messageServer.MessageRecieved += this.messageServer_MessageRecieved;
this.updateTimer = new Timer(this.UpdateWeather, null, 0, 5000);
}
To store service clients I will use dictionary: Key is client identifier (Guid), which is automatically set by message server after client connection, and Value is Subscriber class, which stores info about weather subscriber city. The adding and removing of clients will be performed during ClientConnected and ClientDisconnected events.
private void messageServer_ClientConnected(object sender, ClientStatusChangedEventArgs e)
{
this.subscribers.Add(e.ClientId, new Subscriber());
}
private void messageServer_ClientDisconnected(object sender, ClientStatusChangedEventArgs e)
{
this.subscribers.Remove(e.ClientId);
}
To set up weather subscriber city I’ve created specific message - SubscriberMessage. When the weather service receives SubscriberMessage it finds specified subscriber (MessageRecievedEventArgs have ClientId property, which allows to identify the client who has sent the message) and changes its city property.
[DataContract]
public class SubscribeMessage : Message
{
[DataMember]
public string City { get; set; }
}
private void messageServer_MessageRecieved(object sender, MessageRecievedEventArgs e)
{
if (e.Message is SubscribeMessage)
{
var message = (SubscribeMessage)e.Message;
if (this.subscribers.ContainsKey(e.ClientId))
{
this.subscribers[e.ClientId].City = message.City;
}
}
}
As I said before I have created timer, which will invoke method for updating weather information on each 5 seconds. In this method I loop through all existing weather subscribers and if their city property is not null I send them the weather forecast for their city. For transferring weather data I have created WeatherMessage class, which holds information about the temperature in the weather subscriber’s city.
[DataContract]
public class WeatherMessage : Message
{
[DataMember]
public int Temperature { get; set; }
}
private void UpdateWeather(object state)
{
var random = new Random();
foreach (var weatherData in this.weather)
{
weatherData.Value.Temperature = random.Next(-30, 30);
}
foreach (var subscriber in this.subscribers)
{
if (subscriber.Value.City != null)
{
if (this.weather.ContainsKey(subscriber.Value.City))
{
this.messageServer.SendMessageToClient(
subscriber.Key,
new WeatherMessage() { Temperature = this.weather[subscriber.Value.City].Temperature });
}
}
}
}
Finally, to start the weather service we should perform the following actions during ApplicationStart event:
1. Start policy server, which is used to provide clientaccesspolicy.xml file to the client to enable remote access.
2. Create instance of JsonMessageSerializer and supply all message types to it.
3. Inject JsonMessageSerializer to MessageServer constructor and start it.
4. Create instance of Weather service.
Be sure that you have started policy and message servers on the new threads, otherwise you may get serious performance problems.
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
PolicyServer policyServer = new PolicyServer("clientaccesspolicy.xml");
IMessageServer messageServer = new MessageServer(
IPAddress.Any,
4530,
new JsonMessageSerializer(new List<Type>() { typeof(WeatherMessage), typeof(SubscribeMessage) }));
ThreadPool.QueueUserWorkItem((o) => {policyServer.Start(); });
ThreadPool.QueueUserWorkItem((o) => { messageServer.Start(); });
WeatherService weatherService = new WeatherService(messageServer);
}
}
Also I want to point out that you can use single message server for different services. Just inject the same instance of message server to a different service.
Weather client.
For simplicity, in this example I won't create a specific class which will encapsulate weather client logic and will perform all actions in the code behind of Silverlight web form.
First of all we should create IMessageClient (Don’t forget to supply the same port number to message client and message server constructor), connect it to message server and subscribe on message received event. Message client events are invoked on non-UI threads that’s why you should Dispatcher BeginInvoke method if you need to bind something to UI.
public MainPage()
{
this.InitializeComponent();
this.messageClient = new MessageClient(
4530, new JsonMessageSerializer(new List<Type>() { typeof(WeatherMessage), typeof(SubscribeMessage) }));
this.messageClient.MessageRecieved += this.messageClient_MessageRecieved;
this.messageClient.ConnectCompleted +=
(s, args) => {
this.Dispatcher.BeginInvoke(() => { this.SubscribeButton.IsEnabled = true; });
};
this.messageClient.ConnectAsync();
}
private void messageClient_MessageRecieved(object sender, MessageRecievedEventArgs e)
{
if (e.Message is WeatherMessage)
{
var message = (WeatherMessage)e.Message;
this.Dispatcher.BeginInvoke(() => { this.Temperature.Text = message.Temperature.ToString(); });
}
}
To subscribe on weather you should simply send SubscriberMessage with specified city.
private void Subscribe_Click(object sender, RoutedEventArgs e)
{
this.messageClient.SendMessageAsync(
new SubscribeMessage() { City = ((ComboBoxItem)this.CityComboBox.SelectedItem).Content.ToString() });
}
4. Summary
Some of the ideas described in this article have been already blogged or written in some books, but there is still no reliable library, which unites them all together. I hope that this library would attract other developers and together we could make robust solution, which will make socket development as easy as possible. Currently it is in alpha version. Soon I’m going to refactor it and add more complex examples. And surely You are always welcome to contribute!
Also I’m still looking forward at Silverlight WCF polling duplex services. I’ve already learnt much about its issues. Some of them you will find on my blog. But I’m sure that they will never be so responsive as sockets.
5. Links
Here are some useful links, which will be a good start to learn more about sockets.
1. Full implementation of a Silverlight Policy Server by Mike Snow.
2. TCP/IP Sockets in C#: Practical Guide for Programmers by David Makofske