This article is compatible with the latest version of Silverlight.
Download the sample here: SilverlightPlayground.Pop3Sample.zip (40KB)
There are plenty of ways to handle network communications in Silverlight, but also with this great number of tools, the task is not always simple. The main problem is due to the need to always make calls to the network in an asyncronous way. If this is not a great trouble when we use a traditional HTTP channel, it may become source of headaches when you have to implement a low-level network protocol using a socket. When you have to handle this situation it is often required by protocols to exchange a great number of small messages, each with its own format, size and syntax, so the programming become a sort of push & pull game with a huge number of methods, hard to handle when you are debugging or maintaining the software.
Recently I had to handle this problem when I have implemented my SilverVNC 1.0 client for Remote Frame Buffer protocol. When I've started programming the RFB protocol I immediately realized that it would be an hard task to handle some kind of client-server messages, when in response to a few bytes I had to receive some other few bytes and so on. So finally, after some hours, writing hundreds rows of code I decided to write a generic class to try to simplify the task of implementing network protocols. The following paragraphs in this article will show you how my SocketClient class works and how to use it to implement a POP3 client.
Using Sockets in Silverlight
The support to low-level sockets was introduced in Silverlight starting from the release 2.0 and it has not been changed since them introduction across the following two versions of the plugin. Also in Silverlight 4.0 the programming model is still the same but finally the Silverlight's team has answered positively to a request repeatedly asked by the community. The problem with sockets was that, due to security concerns, before Silverlight 4.0 you was required to provide a socket server on a well-known port (943) with the sole responsibility of distributing a clientaccesspolicy.xml file and also you was restricted to use only the range of ports from 4502 to 4534. This prevented the use of the most common network protocols, so in the last release of Silverlight they added a more relaxed security sandbox for out-of-browser applications installed with the new elevated-trust modality.
With this new useful feature you can concentrate on programming the protocols without the need to have something server-side and with the full range of existing protocols ready to build applications. So the only concern is how to open a channel and how to make calls and receive answers. First of all we have to create an instance of a Socket class:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
After the creation of the Socket you have three possible actions: ConnectAsync(), SendAsync() and ReceiveAsync(). Obviously first of receive or send something, you have to make the connection with the socket, but the way you can make this call is pretty similar to the others. You must create and instance of the SocketAsyncEventArgs class, initialize it and then place the call. Here is a snippet:
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.RemoteEndPoint = new DnsEndPoint("pop.myhost.com", 110);
args.Completed += new EventHandler<SocketAsyncEventArgs>(Operation_Completed);
socket.ConnectAsync(args);
Then, in the Operation_Completed method you get the result of the operation you have made. In case of ConnectAsync() you will get the notification if your connection has been completed or not, but when you make a ReceiveAsync() you will be notified with the received data (upto the max size of a buffer you specified): So let me show you how to handle the operation it has been completed:
void Operation_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError != SocketError.Success)
{
MessageBox.Show(string.Format("Error in socket operation '{0}'", e.LastOperation));
}
else if (e.LastOperation == SocketAsyncOperation.Connect)
{
// do what you want...
}
}
This pattern works for every kind of connection. Only when you make a receive call, you have to check that all the available data have been pulled from the network and if it is not so you have to call again "receive" until you are not sure you have received everything. This is because you have to specify a buffer to fill with received data, but if the buffer is too short, the exceeding data will remain on the network stream.
SocketClient
When you have to implement a protocol, like POP3 (RFC 1939), usually you have to follow a pattern made of chained operations.When you make a call, you wait the completion and then you make the next call and so on:
-
Open connection
-
Wait connection to be opened
-
Send something
-
Wait send is completed
-
Receive something
-
Wait receive is completed
-
Go to 3 for multiple times
-
Drop the connection
There are plenty of differences between protocol and protocol, but the pattern is pretty similar when you implement POP3, SMTP, RFB, and all the other TCP protocols. So when I first created my SocketClient class I have decided to have:
-
A Connect() method and the counterpart Close()
-
A bunch of Send() methods overloads for different casualties
-
A similar bunch of Receive() methods
-
4 overridable methods called OnConnect(), OnReceive(), OnSent() adn OnFailure()
What I have done to simplify the tasks is first of all removing the need of create (and destroy) SocketAsyncEventArgs for every single operation, leaving this responsibility to the underlying SocketClient class. The only concern for the developer is to name correctly (and unique) an operation made of the call and of the wait for the call completion. So if you make a Send() you have to give it a name (a string called "action") and then in the OnSent() method you will receive the same name. This let you easily understand how to continue the chained communications. Additionally the methods let the developer specify an object payload that is useful to share state between different calls. The following snippet shows the implementation of the main Send method and of a public overload:
protected virtual void Send(byte[] data, string action, object state)
{
this.Send(this, data, action, state);
}
private void Send(object owner, byte[] data, string action, object state)
{
if (this.Socket.Connected)
{
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = new SocketAction { State = state, Action = action };
args.RemoteEndPoint = this.EndPoint;
args.Completed += new EventHandler<SocketAsyncEventArgs>(Operation_Completed);
args.SetBuffer(data, 0, data.Length);
this.Socket.SendAsync(args);
}
else
throw new InvalidOperationException("Socket not connected");
}
Just before sending (but also with connect and receive) the action and the state are wrapped in a SocketAction class (a private inner class) and then it is put in the UserToken property. This let the call to push this instance to the Operation_Completed method responsible of discern from who's the call has been made and of how to handle it:
void Operation_Completed(object sender, SocketAsyncEventArgs e)
{
try
{
if (e.SocketError != SocketError.Success)
throw new SocketFailureException(
string.Format("Error in socket operation '{0}'", e.LastOperation))
{ ErrorCode = e.SocketError };
switch (e.LastOperation)
{
case SocketAsyncOperation.Connect:
this.Destroy(e);
this.IsConnected = true;
this.OnConnect();
break;
case SocketAsyncOperation.Receive:
this.OnReceiveChunk(e);
break;
case SocketAsyncOperation.Send:
SocketAction action = e.UserToken as SocketAction ?? SocketAction.Default;
this.Destroy(e);
this.OnSent(action.Owner, action.Action, action.State);
break;
default:
throw new InvalidOperationException("Unknown status");
}
}
catch (Exception ex)
{
this.OnFailure(ex);
}
}
How you can see the Operation_Completed method uses the LastOperation property to understand how to handle the asyncronous callback and forward it to the right method. It is also responsible of detecting errors. When an error is found an exception is generated and the overriding class will be notified throught the OnFailure method. The method take also care of correctly destroy the SocketAsyncEventArgs instance to avoid possible memory leaks. The destroy imply also the removal of the attached completion EventHandler.
You may have noticed that the Receive operation is handled in a subtly different way. It is not immediately notified to the OnReceive method but it is forwarded to another (private) fragment of code that is responsible of pulling all the data requested by the original receive call. This because if I place a Receive() specifying 100000 bytes I expect to receive all the requested bytes and not a small part of them. So in the OnReceiveChunk() I handle this situation:
private void OnReceiveChunk(SocketAsyncEventArgs args)
{
if (args.BytesTransferred + args.Offset < args.Buffer.Length)
{
args.SetBuffer(
args.Offset + args.BytesTransferred,
args.Buffer.Length - (args.Offset + args.BytesTransferred));
}
else
{
SocketAction action = args.UserToken as SocketAction ?? SocketAction.Default;
if (action.LineBuffer == null)
{
this.OnReceive(action.Owner, args.Buffer, action.Action, action.State);
this.Destroy(args);
return;
}
else
{
action.LineBuffer.Add(args.Buffer[0]);
if (action.LineBuffer.Count > 1 && action.LineBuffer[action.LineBuffer.Count - 2] == '\r' && action.LineBuffer[action.LineBuffer.Count - 1] == '\n')
{
this.OnReceive(action.Owner, action.LineBuffer.ToArray(), action.Action, action.State);
this.Destroy(args);
return;
}
}
}
if (this.Socket.Connected)
this.Socket.ReceiveAsync(args);
}
This method handles three different situation. In the first part it checks that all the data has been received and if it is not so, it prepares the buffer for a next Receive operation. Differently, in the second block, it extracts the data from the buffer and forward them to the OnReceive method that will be notified with the entire buffer of received bytes. In the last part it handles another way of make Receive calls. It reads one byte at a time until it detects a CRFL. There are some scenarios - and POP3 is one of these - where the messages ends with CRLF and my class is provided with a ReceiveLine method for these situations.
Using the SocketClient class
The SocketClient class is an abstract class. It simply handles communication via Socket but it needs to be completed with the logic needed to understand protocols. In the sample, I provided attached to this article, I prepared a simple Pop3Client class that extends SocketClient to implement the Post Office Protocol version 3 (POP3).
The extending class needs to override the abstract methods OnConnect, OnSent, OnReceive and OnFailure to be notified of socket answers. It is also able to override a huge number of virtual methods, including Connect, Close and many flavours of Send and Receive. This is required is some cases to prepare the message before to make the base class manage it. Here after I show a snippet that contains a simple Receive operation:
protected override void OnConnect()
{
this.ReceiveLine("receive_halo", null);
}
protected override void OnReceive(object owner, byte[] data, string action, object state)
{
try
{
string output = SocketUtilities.ToASCIIString(data);
if (output.StartsWith("-ERR"))
this.RaiseProtocolException(output);
switch (action)
{
case "receive_halo":
{
string user = string.Format("USER {0}\r\n", this.UserName);
this.Send(user, "sent_user");
break;
}
// handle here other kind of messages
}
}
catch (Exception ex)
{
this.OnFailure(ex);
}
}
Imagine a final user creates an instance of the Pop3Client class providing host, port and credentials. Then he will call the Connect() method. At the end of the connection we need to start the authentication process (send USER and then PASS). So in the OnConnect method we trigger a call to the ReceiveLine method, passing "receive_halo" as action. In the OnReceive method override we will receive the read line (upto CRLF) and the action, stating that the completed operation is "receive_halo". So we can trigger another stage of the chain sending the USER command just after checking there is not any errors in the response ("-ERR" means error in the POP3 protocol while "+OK" means all is fine).
As simple as possible
With this class I was able to implement the RFB protocol that is really more complex and hard to manage than POP3. The good of the class is that you don't have any need of handling raw messages but you can stay at a upper level and concentrate on the protocol instead of communication layer. This make the work as simple as possible.