Ryan Dawson on Longhorn

The software we think, but do not write

Browse Together

Browse Together

 

This article will focus on the Real Time Communication stack that is present in Longhorn.  We will implement an instant messaging application that allows two users to browse the internet and chat together.  Further, an introduction to the managed web browser; and, hosting WinForms controls inside Avalon.

 

 

 

 

 

The source code can be found here

 

 

Peer To Peer Real Time Collaboration

 

It must be noted that in this application we will be creating a Peer To Peer chat session.  There is also the ability create other types of sessions; for example, a multiple peer network controlled by a server (i.e. MSN Messenger).  Further, audio and video are also supported for richer communication sessions.

 

 

Background for Session Initiated Protocol (SIP):

 

For this sample and most Real Time Communication, there is not much that needs to be known about SIP, except for the creation of a well-formed SIP address.  SIP follows most other naming conventions for addressing an end point, as in http://www.microsoft.com/, which in this case can be shortened to something like http://207.46.144.188/.  The protocol (“http”) followed by the physical address (207.46.144.188).  In the same manner, a sample SIP address: sip:Ryan@192.168.0.1, considering my application is running at 192.168.0.1.  But, the point is that you must have a valid Internet Protocol (IP) address.

 

You can easily get an instance of the user’s computer IP address with the following code:

 

string localIP = Dns.Resolve(Environment.MachineName).AddressList[0].ToString();

 

However, for a real application, it must be noted that this method will not always work.  There can be cases where the IP address that needs to be used is not the first in the AddressList.  There are other cases, where the AddressList will be empty.  In this case, you must query WMI and get a list through which you will iterate and try the different addresses.

 

 

Receiving Side:

 

1)      The namespaces and assemblies are scattered as of the PDC M4 build.  In case you are constructing an RTC application, you must manually locate the dlls and add them to your project.  Navigate to the root .NET directory (“C:\Windows\Microsoft .NET”), and then to the “Collaboration” folder.  Add the Microsoft.Collaboration and System.Collaboration dlls.  Further, as for namespace declarations:

 

using System.Collaboration;

using Microsoft.Collaboration;

using Microsoft.Collaboration.Activity;

 

2)      Following is the initialization code required to participate in a Peer To Peer RTC session:

 

RtcProvider provider = new RtcProvider();

 

RtcPeerToPeerProfile profile = provider.CreateRealTimePeerToPeerProfile("sip:Ryan@192.168.0.1");

 

profile.Signaling.SessionReceived += new IncomingSessionEventHandler(Signaling_SessionReceived);    

 

           

·         The RtcProvider is the RTC profile factory; it is required in all cases.

·         We call CreateRealTimePeerToPeerProfile on the provider, passing in the real time address of our location, to which we get a RtcPeerToPeerProfile back.

·         Then, no matter the session type, SignalingSession is the low level transport.  As you will see later, we actually use a higher level transport, but, we will access the Signaling instance and register for the SessionReceived event since it is session agnostic.  This event actually lets us know when another user is trying to connect.  We need to handle the event so we can accept the invitation, as you will see below.

 

 

3)      The Session Received event:

 

private void Signaling_SessionReceived(object sender, IncomingSessionEventArgs e)

            {

InstantMessagingActivity activity = new InstantMessagingActivity(e.Session);

activity.LocalParticipant.ConnectionStateChanged += new EventHandler(LocalParticipant_ConnectionStateChanged);

 

activity.MessageReceived += new MessageReceivedEventHandler(activity_MessageReceived);

 

activity.BeginAccept(new AsyncCallback(this.AcceptCallback), null);

   }

 

 

·         The higher level session object that we are going to be using is the InstantMessagingActivity object.  It abstracts away some of the details of instant messaging that aren’t important.  We actually just pass in the session that the sender has sent.

·         Register for the ConnectionStateChanged in case we want to add processing logic depending on the state of the peer.  We will ignore this event, as it is not interesting and self-describing.

·         Register for a message delivery event from the peer.

·         Finally, it is critical that we accept the session request to continue.  In reality, you would probably want to have a UI dialog at this point asking the user whether they want to accept the invitation.  If not, it is the case that a variant of a denial of service attack could be played on the computer.

 

 

4)      The AcceptCallback method for the asynchronous BeginAccept is as follows:

 

private void AcceptCallback(IAsyncResult result)

{

                  this.activity.EndAccept(result);

}

 

Nothing interesting, purely procedural gunk for asynchronous processing.

 

5)      The MessageReceived event:

 

private void activity_MessageReceived(object sender, MessageReceivedEventArgs e)

            {

                  XPathDocument2 document = new XPathDocument2();

                  document.LoadXml(e.StringBody);

 

XPathNavigator2 navigator = document.CreateXPathNavigator2();

     

                  // Do Processing...

      }

 

This method is application specific, so I won’t go into the details.  The data will probably be stripped from the wire object and passed to the UI for processing.  You can either get the byte[] or string representation of the body.  In the Browse Together application, I passed around an XML string, but that is an implementation detail.

 

 

 

Sending Side

 

1)      The first act in collaborating with a peer is to invite them into a session.  So, an invitation is as follows:

 

SignalingSession session = profile.Signaling.CreateSession("InstantMessaging", null, null);

 

            activity = new InstantMessagingActivity(session);

 

activity.LocalParticipant.ConnectionStateChanged += new EventHandler(LocalParticipant_ConnectionStateChanged);

           

activity.MessageReceived += new MessageReceivedEventHandler(activity_MessageReceived);

 

activity.BeginInvite("sip:John@192.168.0.1", new AsyncCallback(this.InviteCallback), null);

 

 

So, as opposed to receiving a session as above, we are actually going to create it.

 

·         The SignalingSession is the low level session, but as mentioned above, we actually want to pass that into an InstantMessagingActivity to abstract away some instant messaging details.  We call CreateSession and pass in the type of session, which is "InstantMessaging".

·         Register for the events exactly as in the Signaling_SessionReceived method.  We want to be able the receive messages from the peer who we are inviting.

·         Lastly, the guts of the operation.  We call BeginInvite passing in the real time address of the peer we want to invite.  Also, pass in our callback for asynchronous purposes.

 

 

2)      The InviteCallback:

 

            private void InviteCallback(IAsyncResult result)

            {

                  this.activity.EndInvite(result);

      }

 

 

3)      If all is well, we will now be connected to a peer.  The last thing to do is send a message:

 

            UTF8Encoding encoding = new UTF8Encoding();

            byte[] body = encoding.GetBytes("Hello");

 

this.activity.BeginSend(new ContentType("text/plain", "UTF-8"), body, new AsyncCallback(this.SendMessageCallback), null);

 

 

·         First, we must convert the content string into a byte array.

·         Then, we send.  We specify a content type, pass in the byte array of the message, and define a callback.

 

 

4)      SendMessageCallback:

 

            private void SendMessageCallback(IAsyncResult result)

            {

                  this.activity.EndSend(result);

         }

 

 

 

 

Browse Together Application

 

 

 

What is special?

 

The application mimics the functionality of the MSN Messenger browsing-together feature.  It enables 2 people to chat and browse together, maybe looking at specific sites and talking about the content.  An easy improvement would be to mimic all control messages, such as scrolling within the browser, and possibly a drawing surface on top of the browser so the users could draw pictures and annotate the content.

 

 

.NET v1.2 Managed Web Browser

 

Really, this took a long time for Microsoft to get out the door, but I am not sure it as special as I once thought it would be when I saw the 1.2 feature list.  Unfortunately, there are plenty of wrappers around the ActiveX control that one can downloaded by searching Google -that will be of equal quality.  Even the performance seems to be of the equal quality.  At least it is there, though.

 

System.Windows.Forms.WebBrowser browser = new System.Windows.Forms.WebBrowser();

 

browser.Navigate("www.msn.com");

 

                       

 

Hosting WinForms in Avalon

 

This is an important feature, as I soon found out when I wanted to use the browser control, which is a WindowsForms control.  Keep in mind that the reverse class also exists; you can host Avalon within WindowsForms.

 

WindowsFormsHost browserHost = new WindowsFormsHost();

 

System.Windows.Forms.Panel browserPanel = new System.Windows.Forms.Panel();

browserPanel.Controls.Add(browser);

     

browserHost.Controls.Add(browserPanel);

 

FlowPanel avalonBrowserContainer = new FlowPanel();

avalonBrowserContainer.Children.Add(browserHost);

 

 

Combining the WebBrowser code, we get the above.  First, we create a WindowsFormsHost control, which is actually an Avalon control, naturally.  Since I was getting buggy behavior if I added the WebBrowser directly to the WindowsFormsHost, I had to wrap it in a Panel.  Keep in mind, this also meant that I had to create special code for resizing.  Look at the attached code if you are interested.  Finally, I added the browserHost to an Avalon FlowPanel.  Simple.

 

 

Application flow

 

1)      Sign-in with your name at the very beginning.  This will initialize a Peer To Peer profile for you.

 

 

2)      If you know the other peer has done the same, click “Connect To User.”

 

 

3)      After a few seconds, you should be ready to chat and browse with your peer.

 

 

Complaints

 

I will say that by release time, this will be a solid stack.  Although, there is one thing that I am disappointed with: the RTC and P2P libraries are both COM.  So, that means that it is impossible to debug.  I think both of the dlls were released in the WinXP SP1 Advanced Networking Pack.  I have to ask, though.  How much code is actually behind the product that you couldn’t write it in .NET, and secondly, if it was released at SP1, .NET was in full swing?  I don’t understand where the communication went.  Most Microsoft products are aligning behind .NET, but this one decided to interop.

 

 

Where Are We?

 

You should have basic understanding of the Peer To Peer functionality in the RTC stack.  It should be straight forward to implement it with products that need two way chat sessions.  More interesting scenarios will be available when the full product has been released, since audio and video will be first class citizens.  Finally, this may be the beginning of the end of the Instant Messaging war.

           

 

PostTypeIcon
9,571 Views