Saturday, March 22, 2008

Networking (raw sockets) in Silverlight and WPF - messaging in general

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

image

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

First of all UDP. We should create the working Socket first. It uses all ip addresses to broadcast changes via given port. In order to make the socket to 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 begin listening 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 current socket.

socket.SendTo(data, EPCast);

In TCP world, we should first know who we want to sent 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 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,
                SocketFlags.None,
                m_pfnCallBack,
                theSocPkt);

We done with WinForms and WPF networking. So, we can start with graphics. Since we have no 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 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 we should convert values received to the angle to turn the clock hand. We can use only one converter here and bind to the message, according 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 each property of the object changes, it does not trigger binding, due to the fact, that 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 to be single and multi value converter? Simple - all this about interfaces. So, 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 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 implicitly templating and Transform class does not support Binding. What to do? Let's remember 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. 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 does 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 layout for our Silverlight control, we should connect to distribution network server. Reuse the manager, used for Winforms and WPF? We can't. Silverlight is subset of .NET framework, and it is not relays on it, so we have to write 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 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. From the other hand we do not want to restrict it to hardcoded name or IP address. In application class of Silverlight we have 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 able available for Silverlight is 4502-5432 (only 30 ports). So with those restrictions we'll create the connection as following.

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 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 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 should be done in it. First we should check if we go 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 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 fine, we'll return to wait for next message to arrive.

We done. WinForms, WPF and Silverlight speaks over the network one with another, by using TCP and UDP raw sockets. What can be better? When our message is not just clock information, but something related to gaming world (e.g. current position or state of other network players). What am I speaking about? Come to reveal it on my session DEV335: Game Development Using Microsoft’s Latest Technologies in TechEd Israel.

Have a nice day and be good people.

Source code for this article.

2 comments:

Jake said...

Great Article!!
For clarification is the port range really 4502-5432, because that is greater the 30 ports...

Thanks

Tamir Khason said...

Thank you for clarification. I'll fix it