This article is compatible with the latest version of Silverlight.
This article is Part 3 of the series The Duplex Story in Silverlight.
In the first part of this series on duplex communication in Silverlight, we did a deep dive in one of the available options offered by the platform, namely the Http Polling Duplex binding. It’s advised that you read that article first, since this second part continues where we left of in the first part.
To summarize the first part quickly, we saw that Silverlight applications in some situations can really benefit from duplex communication. In scenarios where the client needs to be updated because of changes in the state of the server side (for example a change in a database, a file change…), the best solution is working duplex. The server can then take the initiative to start communicating with the client without us having to write a polling mechanism. That polling mechanism would be really hard to get right in terms of finding a perfectly balanced polling time. Instead, Silverlight offers us the HttpPollingDuplexBinding which takes care of the nasty details. We referred to this binding as it was creating an illusion of duplex communication: internally, the client polls continuously on the network layer, checking if new messages are available on the server. If there are any, they are transferred to the client. Because this polling happens so frequently, it looks as if the messages arrive immediately on the client side because of a push from the server. The latter isn’t the case; it’s an illusion of duplex!
Enough about the past! Let’s now look at what we will be discussing in this second part. Next to the Http Polling Duplex binding to allow for duplex communication, Silverlight has 2 other options to do duplex, namely sockets (TCP) and the net.tcp binding. Sockets are the focus of this part.
To explain things, we are going to use one demo scenario: a stock ticker. A screenshot of the running demo can be seen below. The source code for this article can be downloaded here.
Duplex with sockets
Sockets are nothing new, in fact, they are quite old already. Back in the days, sockets were your best bet to do all types of communication. Still today though, they are used in many applications such as chat applications. .NET has had support to do socket-based programming since the very first version and also Silverlight has had support for them since version 2 (some minor changes were made with Silverlight 4, more on that later). In .NET, sockets can be found in the System.Net.Sockets namespace. In Silverlight, they can be found in the exact same namespace.
Now what exactly is a socket? A socket can be seen as an endpoint, defined by an IP address and a port. Clients can connect to this endpoint: they register themselves with the socket. Once connected, data can flow over the created channel in two directions. This way, sockets are a perfect candidate to do duplex communication as well. In fact, sockets are sometimes referred to as being “real duplex”: there’s no background polling mechanism going on to create the illusion of duplex. Here, both sides of the communication channel can, once a client has registered itself with a socket, start sending data to the other side.
Internally, a socket is implemented through the use of a TcpListener (as we’ll see in the code later). This listener does like the name says: it listens for incoming client requests. Sockets use pure TCP communication. HTTP, used by the Http Polling Duplex, is a layer on top of TCP and is therefore slower.
Advantages and disadvantages of sockets
Being pure TCP endpoints, sockets are fast. As mentioned, HTTP sits on top of TCP and creates more overhead. If raw speed is what you’re after for your duplex service, sockets are a far better choice than HTTP Polling Duplex.
With that said, there are also disadvantages. Firstly, sockets in Silverlight can only work over ports between 4502 and 4534. This should ring a bell immediately: indeed, this poses a problem in internet scenarios. These ports aren’t open by default, making sockets unsuitable for duplex communication over the internet. Intranet on the other hand poses no problems, as we have control over what ports are to be opened within the boundaries of our enterprise.
Secondly, programming with sockets requires more manual coding. Quite a lot in fact. We’ve all grown accustomed with the ease of WCF, with its generated proxy classes etc. Sockets require us to do all the coding ourselves.
The road to duplex sockets
Now that you have a basic understanding of sockets, let’s take a look at what we need to do to get sockets working from Silverlight. As said, more coding is required. The first batch of code that needs to be written is the policy server.
Policy Server
If you’ve worked with Silverlight and services already, you’re probably familiar with cross-domain restrictions. Basically, they mean the following: Silverlight will by default block a call to a service not hosted in the same domain as where the Silverlight application is hosted in. However, the server can deploy a so-called (cross-domain) policy file in the root of the domain. This simple XML file contains information whether or not the service can be accessed from Silverlight applications hosted in another domain. What happens then is that Silverlight will perform a check if this file is present and if so, check if the calling domain is allowed. If yes, Silverlight will perform the cross-domain call. Otherwise, the call is blocked. Socket services are subject to the same cross-domain restrictions as any other type of service when called from Silverlight.
Serving this file upon request of Silverlight is nothing particularly difficult for a web server: it is the server’s job to deliver a file when requested. However, a socket server can be any type of application, varying from a console application to a Windows service. All of these aren’t web servers and thus when our Silverlight application would request to deliver a policy file, they wouldn’t know what to do with that request.
The solution is building a policy server. A policy server can itself be again any type of application (console application, Windows service…) and its sole purpose is sending a policy file when requested to the Silverlight application. As we’ll see in the code, it’s nothing more than a TcpListener that listens for incoming requests on a specific port: 943. Silverlight will send the policy request always to this port number.
Let’s now take a look at the code (the complete code can be found in the download). In the StartPolicyServer() method, a method that is called by the main console application, the policy server is started: a TcpListener instance starts listening for incoming requests from any client IP address on port 943. This BeginAcceptTcpClient() actually runs in a separate thread and is defined with a callback method, OnBeginAcceptTcpClient(). Upon connection of a client, this callback is invoked and using the BeginReceive, we start receiving the information from the client. This would normally be the policy file request, which always has the form <policy-file-request/>. In the OnReceive() callback, we check if the request was actually a policy request and if so, using the BeginSend(), we start transmitting a clientaccesspolicy.xml file (which is included in the project).
public class PolicyServer
{
...
public void StartPolicyServer()
{
using (FileStream stream = new FileStream(
ConfigurationManager.AppSettings["PolicyFileName"]
.ToString(), FileMode.Open))
{
clientaccesspolicy = new byte[stream.Length];
stream.Read(clientaccesspolicy, 0, clientaccesspolicy.Length);
}
tcpListener = new TcpListener(IPAddress.Any, 943);
tcpListener.Start();
tcpListener.BeginAcceptTcpClient(
new AsyncCallback(OnBeginAcceptTcpClient), null);
}
...
private void OnBeginAcceptTcpClient(IAsyncResult ar)
{
Console.WriteLine("Client connected successfully");
tcpClient = tcpListener.EndAcceptTcpClient(ar);
tcpClient.Client.BeginReceive(receivedBytes, 0,
policyRequestString.Length, SocketFlags.None,
new AsyncCallback(OnReceive), null);
}
private void OnReceive(IAsyncResult ar)
{
int receivedLength = tcpClient.Client.EndReceive(ar);
string policyRequest = Encoding.UTF8.GetString(
receivedBytes, 0, receivedLength);
if (policyRequest.Equals(policyRequestString))
{
tcpClient.Client.BeginSend(clientaccesspolicy, 0,
clientaccesspolicy.Length,
SocketFlags.None,
new AsyncCallback(OnSend), null);
}
Console.WriteLine("Waiting for new client");
tcpListener.BeginAcceptTcpClient(new AsyncCallback(OnBeginAcceptTcpClient), null);
}
...
}
Some good news: you can reuse this policy server code for all sockets. Also, there’s a Visual Studio template available through the online template gallery.
If you are building your application as a Silverlight 4 Trusted Application, you can skip the policy server: this application type doesn’t perform a cross-domain check, so there’s no need for a policy server either in this case.
Finally, Silverlight also allows that the policy calls are made over HTTP: using SocketAsyncEventArgs.SocketClientAccessPolicyProtocol you can specify which transport type you want. One remark here though: Silverlight will not perform the check on port 943 if the HTTP call fails. There’s no fallback scenario in place.
The socket server
The socket server is, code-wise, quite similar to the policy server: it’s also based on the TcpListener that listens for incoming client requests (you’ll see that in the code in just a minute). One particular task of the socket server is keeping track of all the connected clients: it’s very well possible in a real world application that more than one client is connecting at the same time with the socket.
To do this, we can keep a List<StreamWriter>, a list of StreamWriter instances. Whenever an update needs to be sent to the connected clients, the code can just loop over this list and write data to the client. As we already mentioned earlier, clients can only connect on ports between 4502 and 4534, making sockets not usable in an internet scenario.
All this can be seen in the code below. In the StartSocketServer() method, we create a Timer that ticks every 2 seconds. With this Timer, we are mocking updates of the Microsoft stock. A TcpListener is created that listens for incoming requests from any IP address on port 4530. The BeginAcceptTcpClient() starts the listening process on a separate thread. When a client connects, the callback, OnBeginAcceptTcpClient(), executes. In this method, we create a TcpClient instance and using its GetStream() method, we create a StreamWriter. The latter is than stored in a List<StreamWriter>. When an update needs to be sent out, as can be seen in the stockTimer_Elapsed() method, we loop over this list and write to this StreamWriter.
public class SocketServer
{
...
public void StartSocketServer()
{
stockTimer.Interval = 2000;
stockTimer.Enabled = true;
stockTimer.Elapsed +=
new ElapsedEventHandler(stockTimer_Elapsed);
stockTimer.Start();
tcpListener = new TcpListener(IPAddress.Any, 4530);
tcpListener.Start();
tcpListener.BeginAcceptTcpClient(OnBeginAcceptTcpClient, null);
}
...
public void OnBeginAcceptTcpClient(IAsyncResult ar)
{
Console.WriteLine("Client connected successfully");
tcpClient = tcpListener.EndAcceptTcpClient(ar);
StreamWriter streamWriter =
new StreamWriter(tcpClient.GetStream());
clientConnections.Add(streamWriter);
streamWriter.AutoFlush = true;
streamWriter.Write(GetStockInfo());
//wait again for new connection
tcpListener.BeginAcceptTcpClient(OnBeginAcceptTcpClient, null);
}
void stockTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if (clientConnections != null)
{
List<StreamWriter> clientsToRemove = new List<StreamWriter>();
foreach (var clientConnection in clientConnections)
{
if (clientConnection != null)
try
{
clientConnection.Write(GetStockInfo());
}
catch (Exception)
{
clientsToRemove.Add(clientConnection);
}
}
//remove the failed client connections
foreach (var clientConnection in clientsToRemove)
{
clientConnections.Remove(clientConnection);
Console.WriteLine("Client disconnected... bye bye!");
}
}
}
...
}
The client
Clients can actually perform several tasks: they can connect with the socket, write data to the socket or receive incoming data. Respectively, this is covered with the ConnectAsync(), WriteAsync() and ReceiveAsync() methods.
In our client application, we can’t use the TcpClient class as it is not available within Silverlight. Instead, we have to use the Socket class itself. To perform communication, we need to use an instance of the SocketAsyncEventArgs class. It is used to define callback methods and pass state (the Socket in this case) between calls.
The code below shows that we first define the endpoint with which we have to connect using the DnsEndpoint. Notice that we are connecting on port 4530 here. Next, we create a Socket and a SocketAsyncEventArgs instance. To the latter, we pass the socket instance (state), the endpoint and the callback method, which is to be executed when the asynchronous connection attempt is finished. Once connected, we want to wait for incoming, updated stock information. In the OnSocketConnected, we use the ReceiveAsync() method, which will cause our client to wait for new data.
Whenever data is sent from the socket, the callback, OnSocketStartReceive(), is executed. We can get the sent data at this point. However, when we want to update the UI, we run into one final issue: this code is executing on a background thread. Therefore, to update UI elements, we have to use the Dispatcher.BeginInvoke() that will marshall back to the UI thread.
private void StartButton_Click(object sender, RoutedEventArgs e)
{
DnsEndPoint endPoint =
new DnsEndPoint(Application.Current.Host.Source.DnsSafeHost, 4530);
Socket socket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = socket;
args.RemoteEndPoint = endPoint;
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnected);
socket.ConnectAsync(args);
}
private void OnSocketConnected(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
byte[] response = new byte[1024];
e.SetBuffer(response, 0, response.Length);
e.Completed -= new EventHandler<SocketAsyncEventArgs>(OnSocketConnected);
e.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketStartReceive);
Socket socket = (Socket)e.UserToken;
socket.ReceiveAsync(e);
}
}
private void OnSocketStartReceive(object sender, SocketAsyncEventArgs e)
{
string data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
Dispatcher.BeginInvoke(() => StockTickingTextBlock.Text = data);
Socket socket = (Socket)e.UserToken;
socket.ReceiveAsync(e);
}
Conclusion
As can be seen, working with sockets requires quite some coding work. This is mainly because we aren’t getting any help here from WCF. On the other hand, performance-wise, this solution is really fast and is a recommended approach if performance is a key requirement. In the third and final part of this series, we will look at the third way of doing duplex and that is the net.tcp binding.
About the author
Gill Cleeren is Microsoft Regional Director (www.theregion.com), MVP ASP.NET, INETA speaker bureau member and Silverlight Insider. He lives in Belgium where he works as .NET architect at Ordina. Passionate about .NET, he’s always playing with the newest bits. In his role as Regional Director, Gill has given many sessions, webcasts and trainings on new as well as existing technologies, such as Silverlight, ASP.NET and WPF. He also leads Visug (www.visug.be), the largest .NET user group in Belgium. He’s also the author of “Silverlight 4 Data and Serivices Cookbook”, published by Packt Publishing. You can find his blog at www.snowball.be.