Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Socket Support in Silverlight 2: Part I


Dan Wahlin (Microsoft Most Valuable Professional for ASP.NET and XML Web Services) is a .NET development instructor and architecture consultant at Interface Technical Training. Dan founded the XML for ASP.NET Developers site, which focuses on using ASP.NET, XML, Silverlight, AJAX, and Web Services on .NET and runs smartwebcontrols.com. He’s also on the INETA Speaker's Bureau and speaks at several conferences. Dan has co-authored/authored several different books on .NET, including ASP.NET 2.0 MVP Hacks, Professional ASP.NET AJAX, XML for ASP.NET Developers and is currently working on a new book on Silverlight 2. Dan blogs at http://weblogs.asp.net/dwahlin.


Sockets have been around since the marriage of the IP address and port and are used heavily behind the scenes in products such as Internet Information Server (IIS), chat applications and even military applications and devices. Although programming directly against sockets isn't as common as it may have been in years past, sockets are still a key technology used to send data back and forth between applications, devices, or other controls. The .NET framework has supported sockets since its initial release and with the release of Silverlight 2 sockets can also be used with Rich Internet Applications (RIAs) to push data from a server to a client. Other RIA application frameworks such as Flash also provide support for socket to socket communications.

If you've worked on a client-side application that needed to receive up-to-date data then you're probably used to solving the problem by polling. With polling, the client contacts the server on a consistent, timed basis to see if any updates are available. ASP.NET AJAX provides a Timer control that makes this process straightforward. The problem with polling is that unless the data is changing a lot, most of the client checks to the server are simply wasted bandwidth in cases where no changes have occurred. If you're calling a Web Service and passing JSON messages back and forth then the overhead is certainly minimal, but wouldn't it be nice if the server could push updates to the client as needed instead? In fact, wouldn't it be nice if multiple clients could receive the same updates on a consistent basis so everyone is in sync? Silverlight 2's support for sockets makes this a reality.

In the first installment of this multi-part article, I describe how to create a socket server that clients can connect to and receive data. In the second part in the series, I cover the client-side and explain how to connect a Silverlight client to a server and process data.

What are Sockets?

Sockets let a host server listen for clients that would like to connect to a specific socket (an IP address combined with a port). Once clients connect to the server, data can be exchanged in both directions. This means that the server can push data to the client as opposed to having the client poll the server on a timed basis to check for changes (the technique used in AJAX applications).

The .NET framework provides direct support for using sockets through the System.Net.Sockets namespace and provides classes such as Socket and TcpListener that can be used to create a server application that can listen for client connections. Figure 1 shows a Silverlight 2 application built around the concept of sockets that allows a server application to push data down to a client that displays the score of a basketball game. The game is simulated on the server and as each team scores the update is pushed to the client so that the score and action that occurred can be displayed.

[Click image to view at full size]
Figure 1: Using Sockets to push data down to a Silverlight 2 client.

Creating the Server

To create a server that uses sockets you'll need to import the System.Net and System.Net.Sockets namespace. You can use the Socket or TcpListener class to listen for client connection attempts but I opted for the TcpListener class since my Silverlight 2 clients would only be connecting over TCP anyway (as opposed to UDP or another protocol). The server application listens on port 4530 which is in the range Silverlight 2 Beta 1 allows. The sample application uses a standard console application to host the server but a Windows Service would certainly be preferable in a more real-life situation.

Listing One shows the code that starts up the TcpListener class within the server as well as a timer that is used to send data to the client on a random basis. The _TcpClientConnected object is a ManualResetEvent object that blocks threads until previous ones have connected. The key line of code is the BeginAcceptTcpClient() method which asynchronously routes client connection attempts to a callback method named OnBeginAccept().

public void StartSocketServer()
{
    InitializeData();
    _Timer = new System.Timers.Timer();
    _Timer.Enabled = false;
    _Timer.Interval = 2000D;
    _Timer.Elapsed += new ElapsedEventHandler(_Timer_Elapsed);
    
    try
    {
        //Allowed port range 4502-4532
        _Listener = new TcpListener(IPAddress.Any, 4530);
        _Listener.Start();
        Console.WriteLine("Server listening...");
        while (true)
        {
            _TcpClientConnected.Reset();
            Console.WriteLine("Waiting for client connection...");
            _Listener.BeginAcceptTcpClient(
               new AsyncCallback(OnBeginAccept),null);
            _TcpClientConnected.WaitOne(); //Block until client connects
        }
    }
    catch (Exception exp)
    {
        LogError(exp);
    }       
}
Listing One: Creating a socket server that listens for connections using the TcpListener class.

Once a client tries to connect the OnBeginAccept() method is called (see Listing Two) which sends team data down to the client and starts the timer which simulates scores and game actions. The stream used to write to each client is added to a collection since it's used later as changes on the server occur.

private void OnBeginAccept(IAsyncResult ar)
{
    _TcpClientConnected.Set(); //Allow waiting thread to proceed
    TcpListener listener = _Listener;
    TcpClient client = listener.EndAcceptTcpClient(ar);
    if (client.Connected)
    {
        Console.WriteLine("Client connected...");
        StreamWriter writer = new StreamWriter(client.GetStream());
        writer.AutoFlush = true;
        _ClientStreams.Add(writer);
        Console.WriteLine("Sending initial team data...");
        writer.WriteLine(GetTeamData());
        if (_Timer.Enabled == false)
        {
            _Timer.Start();
        }
    }
}
Listing Two: Accept client connections and send basketball team data to the client.

The initial team data that is sent from the server to the client immediately after a client connects is created in the GetTeamData() method in Listing Three. This code uses the XmlWriter to generate XML data that is parsed by a Silverlight client. The data contains teams and players involved in the simulated game.

private string GetTeamData()
{
    StringWriter sw = new StringWriter();
    using (XmlWriter writer = XmlWriter.Create(sw))
    {
        writer.WriteStartElement("Teams");
        foreach (string key in _Teams.Keys)
        {
            writer.WriteStartElement("Team");
            writer.WriteAttributeString("Name", key);
            Dictionary<Guid, string> players = _Teams[key];
            foreach (Guid playerKey in players.Keys)
            {
                writer.WriteStartElement("Player");
                writer.WriteAttributeString("ID", playerKey.ToString());
                writer.WriteAttributeString("Name", players[playerKey]);
                writer.WriteEndElement();
            }
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
    }
    return sw.ToString();
}
Listing Three: Creating XML data to send to a client using the XmlWriter class.

As the Timer object fires, the _Timer_Elapsed() method is called which handles sending score updates to the client on a random basis to simulate plays in a game. Listing Four shows the code for the timer _Timer_Elapsed() event handler.

private void _Timer_Elapsed(object sender, ElapsedEventArgs e)
{
    SendData(GetScoreData());
    //Setting new interval to simulate different data feed times
    Random r = new Random();
    _Timer.Interval = (double)r.Next(3000, 7000);
}

private void SendData(string data)
{
    if (_ClientStreams != null)
    {
        foreach (StreamWriter writer in _ClientStreams)
        {
            if (writer != null)
            {
                writer.Write(data);
            }
        }
    }
}
Listing Four: The _Timer_Elapsed() event handler is called on a random basis to send score data down to the client.

The GetScoreData() method handles creating fictitious basketball scores and actions. It picks values from an enum named ActionsEnum and generates messages about what action was performed that the client can display. The ActionsEnum type is shown next:

public enum ActionsEnum
{
    Foul = 0,
    MadeFoulShot = 1,
    Made2Pointer = 2,
    Made3Pointer = 3,
    Turnover = 4,
    Miss = 5
}

Listing Five shows the GetScoreData() method and a few supporting methods. This particular code doesn't have anything to do with sockets, but it's used to send data to the client using a socket.

private string GetScoreData()
{
    UpdateScoreData();

    Console.WriteLine("Sending score data...");

    StringWriter sw = new StringWriter();
    XmlSerializer xm = new XmlSerializer(typeof(ScoreData));
    xm.Serialize(sw, _ScoreData);
    return sw.ToString();
}
private void UpdateScoreData()
{
    Random r = new Random();
    //Get last action
    ActionsEnum action = (ActionsEnum)r.Next(0, 
     Enum.GetNames(typeof(ActionsEnum)).Length);
    //Get player that performed action
    KeyValuePair<Guid,string> player = GetActionPlayer(action);
    string defensiveTeam = _Teams.Keys.Where(key => 
      key != _ScoreData.TeamOnOffense).First();
    string message = _ScoreData.TeamOnOffense + ": " + player.Value;
    switch (action)
    {
        case ActionsEnum.Foul:
            message = defensiveTeam + ": " + player.Value + 
              " committed a foul.";
            break;
        case ActionsEnum.Made2Pointer:
            message += " scored 2 points.";
            break;
        case ActionsEnum.Made3Pointer:
            message += " scored 3 points.";
            break;
        case ActionsEnum.MadeFoulShot:
            message += " made a foul shot after foul.";
            break;
        case ActionsEnum.Miss:
            message += " missed.";
            break;
        case ActionsEnum.Turnover:
            message += " turned it over";
            break;
    }
    int teamPos = 1;
    int points = ((int)action > 3)?0:(int)action;
    foreach (string name in _Teams.Keys)
    {
        if (teamPos == 1 && name == _ScoreData.TeamOnOffense)
        {
            _ScoreData.Team1Score += points;
        }
        if (teamPos == 2 && name == _ScoreData.TeamOnOffense)
        {
            _ScoreData.Team2Score += points;
        }
        teamPos++;
    }
    _ScoreData.Action = action;
    _ScoreData.LastActionPlayerID = player.Key;
    _ScoreData.LastAction = message;
    _ScoreData.LastActionPoints = points;

    //Change teams if a foul wasn't committed
    if (action != ActionsEnum.Foul)
    {
        _ScoreData.TeamOnOffense = defensiveTeam;
    }
}
private KeyValuePair<Guid, string> GetActionPlayer(ActionsEnum action)
{
    Dictionary<Guid, string> players = null;
    if (action == ActionsEnum.Foul)
    {
        //Get defensive team players
        players = _Teams[_Teams.Keys.Where(name => 
         name != _ScoreData.TeamOnOffense).First()];
    }
    else
    {
        players = _Teams[_Teams.Keys.Where(name => 
          name == _ScoreData.TeamOnOffense).First()];
    }
    Random r = new Random();
    return players.ElementAt(r.Next(0, players.Count));
}
Listing Five: Simulating a basketball game and creating data that can be sent to a client.

There's a little more code that the server application relies on which I won't show. However, you can download the complete application here. The code shown above is really all that's involved for creating a server that listens for clients on a specific IP address and port using the TcpListener class. I chose the asynchronous route for listening for client connections but other viable techniques also exist.

In Part II, I cover the client-side and discuss how Silverlight 2 can connect to a server using sockets.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.