Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Networking in Silverlight and WPF or How to Make them Speak to Each Other

0.00/5 (No votes)
22 Apr 2008 1  
How to use raw sockets in Silverlight application and how to make Silverlight speak with Windows Forms and WPF

Introduction

You probably know how to use raw sockets in WinForms. It's pretty much the same in WPF, however it is very different (and limited) in Silverlight. Today, we'll create a sample application in Silverlight, WPF and WinForm that sends and receives updates via TCP as well as broadcasts it via UDP (singlecast and multicast). So let's start.

Let's Start

First of all, we'll create a WinForms server that should distribute updates. It knows what the time is now and broadcasts the time message via UDP. Also it has a TCP server that distributes updates to all its clients.

First of all, UDP. We should create the working Socket first. It uses all IP addresses to broadcast changes via a given port. In order to make the socket be multicast, we should set appropriate socket options. Let's see the code.

lock (this) 
    { 
        if (m_mainSocket == null) 
        { 
             m_mainSocket = new Socket(AddressFamily.InterNetwork, 
			SocketType.Dgram,  ProtocolType.Udp); 
             IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any,port); 
                        
             m_mainSocket.SetSocketOption(SocketOptionLevel.Socket,
		SocketOptionName.ReuseAddress, 1); 
             m_mainSocket.Bind(ipLocal); 
         } 

         IPAddress ip = IPAddress.Parse(castGroupIp); 
         EPCast = new IPEndPoint(ip, port); 

         m_mainSocket.SetSocketOption(SocketOptionLevel.IP, 
	SocketOptionName.AddMembership, new MulticastOption(ip, IPAddress.Any)); 
    }

Now, the TCP part. It's very similar (for server, we do not need specific IP address to bind), however, we should not set multicast options there aside with beginning to listen right after connection was established.

lock (this) 
    { 
        if (m_mainSocket == null) 
        { 
             m_mainSocket = new Socket(AddressFamily.InterNetwork, 
		SocketType.Stream, ProtocolType.Tcp); 

             IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port); 
             m_mainSocket.Bind(ipLocal); 

             m_mainSocket.Listen(Backlog); 

             m_mainSocket.BeginAccept(new AsyncCallback(acceptCallback), null); 
        } 
    } 

In order to send data via UDP, all we have to do is to write it into the current socket.

socket.SendTo(data, EPCast); 

In the TCP world, we should first know who we want to send to, thus we have to enumerate all incoming clients to save the references to their sockets.

Socket workerSocket = m_mainSocket.EndAccept(asyn); 

m_workerSocketList.Add(workerSocket.GetHashCode(), workerSocket); 
m_mainSocket.BeginAccept(new AsyncCallback(acceptCallback), null); 

Then when we know who to send, all we have to do is to send:

socket.Send(data);

Now, when we know how to send, we should learn how to receive network messages. It's exactly the same within TCP or UDP. First check is whether there is data to receive, then receive it.

if (m_pfnCallBack == null) 
                m_pfnCallBack = new AsyncCallback(dataReceivedCallback); 
            SocketPacket theSocPkt = new SocketPacket(socket, bufferSize); 
            socket.BeginReceive(theSocPkt.dataBuffer, 0, 
		theSocPkt.dataBuffer.Length,  m_pfnCallBack, 
                
theSocPkt); 

We are done with WinForms and WPF networking. So we can start with graphics. Since we have not a lot of graphics in WinForms, we'll focus on WPF stuff.

We'll use ContentControl to present the content with datatemplate of our message. We'll create Ellipse for the clock and three rectangles for clock hands. Once data is received, we should change RotateTransform value of RenderTransform for each of our rectangles (first set the TransformOrigin to the center of the clock). Bind it together:

<Ellipse Width="250" Height="250" StrokeThickness="2" Stroke="Black"/> 
<Rectangle Height="100" Width="20" RadiusX="10" RadiusY="10" 
	Fill="Black" RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform/> 
<TranslateTransform Y="25" X="115"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle> 
<Rectangle Height="125" Width="10" RadiusX="5" RadiusY="5" 
	Fill="Black" RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform/> 
<TranslateTransform Y="0" X="120"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle> 
<Rectangle Height="125" Width="4"  RadiusX="2" RadiusY="2" 
	Fill="Black" RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform/> 
<TranslateTransform Y="0" X="123"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle>

How should we convert values received to the angle to turn the clock hand. We can use only one converter here and bind to the message, according to the following formula:

HourAngle = (Hours * 30)+(12*Minutes/60); 
MinuteAngle = Minutes * 6; 
(it's 360/60) 
SecondAngle = Seconds * 6; 

Let's run it. Nothing happens. Why? The reason is that even as each property of the object changes, it does not trigger binding, due to the fact that the whole object has not been changed. In order to fix it, we should bind to each property. And in case of hours to Hours and Minutes properties both. But how to make my converter be single and multi value converter? Simple - all this about interfaces. So, the following converter will do the work:

public class TimeToAngleConverter : IValueConverter, IMultiValueConverter 
    {        
public object Convert(object value, Type targetType, 
	object parameter, System.Globalization.CultureInfo culture) 
        { 
            return (double)value * 6; 
        } 
public object Convert(object[] values, Type targetType, 
	object parameter, System.Globalization.CultureInfo culture) 
        { 
            return ((double)values[0] * 30) + (12 * (double)values[1] / 60); 
        }
public object ConvertBack(object value, Type targetType, 
	object parameter, System.Globalization.CultureInfo culture)        
    { throw new 
NotImplementedException(); } 
public object[] ConvertBack(object value, Type[] targetTypes, 
	object parameter, System.Globalization.CultureInfo culture)        
    { throw new 
NotImplementedException(); } 
      } 

Now binding expressions:

<Rectangle Height="100" Width="20" RadiusX="10" RadiusY="10" Fill="Black" 
RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform> <RotateTransform.Angle>
<MultiBinding Converter="{StaticResource timeToAngle}"> 
<Binding Path="Hour"/> 
<Binding Path="Minute"/> 
</MultiBinding> 
</RotateTransform.Angle> 
</RotateTransform> 
<TranslateTransform Y="25" X="115"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle> 
<Rectangle Height="125" Width="10" RadiusX="5" RadiusY="5" 
	Fill="Black" RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform Angle="{Binding Path=Minute, Converter={StaticResource timeToAngle}}"/> 
<TranslateTransform Y="0" X="120"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle> 
<Rectangle Height="125" Width="4"  RadiusX="2" RadiusY="2" 
	Fill="Black" RenderTransformOrigin="0.5,1"> 
<Rectangle.RenderTransform> 
<TransformGroup> 
<RotateTransform Angle="{Binding Path=Second, Converter={StaticResource timeToAngle}}"/> 
<TranslateTransform Y="0" X="123"/> 
</TransformGroup> 
</Rectangle.RenderTransform> 
</Rectangle> 

We are done with graphics in WPF. Let's start it over in Silverlight. We cannot do at least half of what has been done in WPF. MultiBinding is not supported, there is no implicit templating and Transform class does not support Binding. What do we do? Let's remember the old good world.

Set the control without using templates, then find resources and save references and set values explicitly (after subscribing to OnPropertyChanged event of cause. In other words, make binding with your own hands.

void onLoaded(object s, RoutedEventArgs e) 
{ 
mt = ((RotateTransform)FindName("mTransform")); 
ht = ((RotateTransform)FindName("hTransform")); 
st = ((RotateTransform)FindName("sTransform")); 
((NetTimeProvider)Resources["timeProvider"]).PropertyChanged += 
		new PropertyChangedEventHandler(Page_PropertyChanged); 
} 
RotateTransform mt, ht, st; 
void Page_PropertyChanged(object sender, PropertyChangedEventArgs e) 
{ 
//Binding to Transform not supported (yet??); 
NetTimeProvider s = sender as NetTimeProvider; 
Dispatcher.BeginInvoke((SendOrPostCallback) delegate (object o) 
{ 
NetTimeProvider ntp = o as NetTimeProvider; 
if (e.PropertyName == "Hour") 
{ 
ht.Angle = (ntp.Hour * 30) + (12 * ntp.Minute / 60); ; 
} 
else if (e.PropertyName == "Minute") 
{ 
mt.Angle = ntp.Minute * 6; 
} 
else if (e.PropertyName == "Second") 
{ 
st.Angle = ntp.Second * 6; 
} 
}, s); 
}

Now, when we have a layout for our Silverlight control, we should connect to the distribution network server. Reuse the manager, used for Winforms and WPF? We can't. Silverlight is a subset of the .NET Framework, and it does not rely on it, so we have to write a new network provider for Silverlight. UDP is not supported in Silverlight, thus we'll use TCP networking. Let's see what we have. WebRequest/WebResponse HttpWebRequest/HttpWebResponse - to use it - no. Our server is neither HTTP nor Web server. We should use raw sockets in Silverlight. Socket class exists in System.Net DLL for Silverlight, however it is very limited. Let's make the connection. First of all, we should know what IP to connect.

Due to security restrictions, we cannot do DNS queries in Silverlight. On the other hand, we do not want to restrict it to hardcoded name or IP address. In the application class of Silverlight, we have a very handy property, named DnsSafeHost (Application.Current.Host.Source.DnsSafeHost). So let's use it.

What about ports? Can I use TCP socket for any port I want? No. This is another security restriction. The only port range available for Silverlight is 4502-5432 (only 30 ports). So with those restrictions, we'll create the connection as follows:

socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, 
ProtocolType.Tcp); 
            DnsEndPoint ep = new DnsEndPoint
		(Application.Current.Host.Source.DnsSafeHost, 4502); 
            SocketAsyncEventArgs args = new SocketAsyncEventArgs() 
            { 

                RemoteEndPoint = ep 
            }; 
            
args.Completed += onConnected; 
            
socket.ConnectAsync(args);

Now we should check if the connection is established successfully. The only place we can do it is in the onConnected handler. Here also, we'll reuse completed event of SocketAsyncArgs to perform read sequence. Upon the end of the handler, we'll try to read something from inline socket.

void onConnected(object sender, SocketAsyncEventArgs e) 
       { 

           if (e.SocketError == SocketError.Success) 
           { 

               e.Completed -= onConnected; 
               e.Completed += onRead; 
               Message = "Connected"; 
           } 
           readMoreData(e); 
       }  

If you remember, in the regular framework, we can wait on socket. We can do it as well in Silverlight.

void readMoreData(SocketAsyncEventArgs e) 
{ 
    e.SetBuffer(buffer, bytesRead, (buffer.Length - bytesRead)); 
    if (!socket.ReceiveAsync(e)) 

    { 
        onRead(socket, e); 
    } 
    else 
    { 

        Message = "Disconnected"; 
    } 
} 

So, if everything is ok and we have data in the socket, let's read it. There is some fault proofing that should be done in it. First, we should check if we got all the message. We know that the message size is 20 bytes (5 integers - we check first four). Then we should check that the message we got is our message. So in the header, we'll check for the Magic number. Then if it's ok, we'll parse it and fill all properties of our class.

void onRead(object sender, SocketAsyncEventArgs e) 
        { 

            if (e.BytesTransferred > 0) 
            { 

                bytesRead += e.BytesTransferred; 
                if (bytesRead == 20 && BitConverter.ToUInt32(buffer, 0) == Magic) 

                { 
                    Hour = BitConverter.ToInt32(buffer, 4); 
    OnPropertyChanged("Hour"); 
                    Minute = BitConverter.ToInt32(buffer, 8);                     
OnPropertyChanged("Minute"); 
                    Second = BitConverter.ToInt32(buffer, 12);                     
OnPropertyChanged("Second"); 
                    bytesRead = 0; 

                } 
                readMoreData(e); 
            } 

        } 

If everything is fine, we'll return to wait for the next message to arrive.

What's Next?

Next, we can try to connect Silverlight 1.0 application to this sample. How, you ask? Wait for the next article or visit my blog.

History

  • 22nd April, 2008: Initial post

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here