Networking

Get Connected With The .NET Framework 3.5

Mariya Atanasova and Larry Cleeton and Mike Flasko and Amit Paka

This article discusses:

  • Socket class performance
  • Internationalized URLs
  • The System.Net.PeerToPeer namespace
This article uses the following technologies:
.NET Framework

Contents

Socket Class Performance
International Resource Identifier Support
The System.Net.PeerToPeer Namespace
Wrapping Up

In the Microsoft® .NET Framework, the System.Net namespace exposes the functionality of many networking protocols, such as HTTP and SMTP. The upcoming release of the .NET Framework 3.5 (which will ship with Visual Studio® 2008, formerly code-named "Orcas") includes a number of performance and functional enhancements to these core networking layers. In this article, we'll look at three key changes provided by the System.Net team:

  • High performance Socket API
  • International Resource Identifier support for URIs
  • Peer-to-Peer (P2P) namespace

The upcoming release of the .NET Framework 3.5 will introduce changes to the Framework itself. The features described in this article are available in the Beta 1 release of Visual Studio 2008, available for download from MSDN®.

Socket Class Performance

In version 2.0 of the .NET Framework, the System.Net.Sockets namespace provides a Socket class that offers virtually all the functionality of the Windows® WinSock Win32® APIs. This functionality comes in a class with methods and properties designed for developers of managed code. On Socket, there is a set of synchronous methods, including Send and Receive, with various parameter overloads for a variety of scenarios. These synchronous methods are easy to use and are very suitable for simple networking tasks using sockets. There is also a set of asynchronous methods on Socket that are based on the Asynchronous Programming Model (APM) that is prevalent throughout the .NET Framework (see msdn.microsoft.com/msdnmag/issues/07/03/ConcurrentAffairs for more information). These asynchronous methods make the Socket class relatively easy to use asynchronously and provide for a way to handle a lot of sockets or many send and receive operations on a number of sockets.

The Socket class in version 2.0 is suitable for a variety of client applications that need to use network sockets as well as for some server and service type applications. Unfortunately, there are some service application scenarios that are not possible with the Socket class in version 2.0, but they are possible with native languages that directly use the Windows WinSock APIs. The primary issue with the version 2.0 Socket class is that it consumes excess CPU cycles to perform a single socket I/O operation as well as when allocating the necessary underlying objects to maintain I/O operations on a large number of sockets simultaneously.

With the .NET Framework 3.5, the common language runtime (CLR) can now more efficiently manage a large number of Overlapped objects simultaneously. Overlapped objects in the CLR effectively wrap the native Windows OVERLAPPED structure used to manage asynchronous I/O operations. There is one Overlapped object instance for every Socket asynchronous I/O operation in progress. And it is now possible to have 60,000 or more connected sockets while maintaining a pending asynchronous receive I/O operation on each socket.

The Socket class in version 2.0 uses Windows I/O Completion Ports for asynchronous I/O operation completion. This allows applications to easily scale to a large number of open sockets. The .NET Framework implements the System.Threading.ThreadPool class that provides the completion threads that read Completion Ports and complete asynchronous I/O operations. In developing the upcoming 3.5 version of the .NET Framework, we put a significant focus on removing overhead in the code paths between reading a Completion Port and calling an application's completion delegate or signaling the I/O completion event object in an IAsyncResult object.

The APM in the .NET Framework is also referred to as the Begin/End pattern. This is because a Begin method is called to initiate an asynchronous operation and returns an IAsyncResult object. A delegate can optionally be provided as a parameter to the Begin method that will be called when the asynchronous operation completes. Alternatively a thread can wait on the IAsyncResult.AsyncWaitHandle. When the callback is called or the wait signaled, the End method is called to get the results of the asynchronous operation. This pattern is flexible, relatively easy to use, and common throughout the .NET Framework.

It is important for you to note, though, that there is a price when doing a significant number of asynchronous socket operations. For each operation, an IAsyncResult object must be created—and it cannot be reused. This can impact performance by heavily exercising object allocation and garbage collection. To work around this issue, the new release offers a different pattern of methods to do asynchronous I/O with sockets. This new pattern doesn't require the allocation of an operation context object for every socket operation.

Rather than create an entirely new pattern, we took an existing pattern and made one fundamental change. There are now methods in the Socket class that use a variation of the Event-based completion model. In version 2.0, you would use the following code to initiate an asynchronous send on a Socket:

void OnSendCompletion(IAsyncResult ar) { } IAsyncResult ar = socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, OnSendCompletion, state);

In the new version, you can also do this:

void OnSendCompletion(object src, SocketAsyncEventArgs sae) { } SocketAsyncEventArgs sae = new SocketAsyncEventArgs(); sae.Completed += OnSendCompletion; sae.SetBuffer(buffer, 0, buffer.Length); socket.SendAsync(sae);

There are a few notable differences here. The object that wraps the context of the operation is a SocketAsyncEventArgs object instead of an IAsyncResult object. The application creates and manages (and can even reuse) SocketAsyncEventArgs objects. All the parameters to the socket operation are specified by properties and methods of the SocketAsyncEventArgs object. Completion status is provided by properties of the SocketAsyncEventArgs object as well. And finally, the use of an event handler callback completion method is required.

All of these changes result in significantly improved performance and scalability of the System.Net.Sockets class in the .NET Framework 3.5. Two of these improvements will be automatically realized by existing applications. The third improvement, the new Socket methods, can only be used by modifying an application, but these methods provide increased scalability for the most demanding socket-based applications.

International Resource Identifier Support

Web addresses are typically expressed using Uniform Resource Identifiers (URIs) that consist of a very restricted set of characters. Basically, they contain only upper- and lowercase letters from the English alphabet, digits from 0 to 9, and a small number of other ASCII symbols including commas and hyphens.

This syntax is not very convenient for parts of the world that rely on a character set other than a Latin alphabet—say, Japanese or Hebrew. Consider an address like www.BaldwinMuseumOfScience.com. If you are an English speaker, this address is easy to understand and remember. However, if you don't speak English, this URL looks like little more than a random assortment of symbols. If you only speak English, could you remember a long address in Chinese?

International Resource Identifiers (or IRIs) support non-ASCII characters—or more precisely Unicode/ISO 10646 characters. This means a domain name can contain Unicode characters, which means you can end up with a URL that looks like this: https://微軟香港.com.

We have extended the existing System.Uri class to provide IRI support based on RFC 3987 (see faqs.org/rfcs/rfc3987.html). Current users will not see any change from the .NET Framework 2.0 behavior unless they specifically opt for IRI capabilities to be enabled. The reason for this is to ensure application compatibility between the 3.5 release and prior versions.

To opt in, you need to make two changes. First, add the following element to the machine.config file:

<section name="uri" type="System.Configuration.UriSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />

Then specify whether Internationalized Domain Name (IDN) parsing should be applied to the domain name and whether IRI parsing rules should be applied. This can be done in the machine-wide machine.config or in an individual application's app.config. For example:

<configuration> <uri> <idn enabled="All" /> <iriParsing enabled="true" /> </uri> </configuration>

Enabling IDN will convert all Unicode labels in a domain name to their Punicode equivalents. Punicode names contain only ASCII characters and always start with the prefix "xn--". This is because most DNS servers currently deployed on the Internet only support ASCII characters. Enabling IDN only affects the value of the Uri.DnsSafeHost property. For 微軟香港.com, it will contain xn--g5tu63aivy37i.com, while Uri.Host will contain the Unicode characters.

There are three possible values for IDN you can use in the idn element's enabled attribute depending on the DNS servers you're using:

  • "All" will use IDN names (Punicode) for all domains.
  • "AllExceptIntranet" will use IDN names for all external domains and Unicode names for all internal domains. This case only applies when the intranet DNS servers support Unicode names.
  • "None", which is the default, is consistent with the .NET Framework 2.0 behavior.

Enabling IRI parsing (iriParsing enabled = "true") causes normalization and character checking according to the latest IRI rules in RFC 3987. The default value is false and will do normalization and character checking according to RFC 2396 (see faqs.org/rfcs/rfc2396.html). To learn more about the uniform resource identifiers and the Uri class, see the online documentation at msdn2.microsoft.com/system.uri.

The System.Net.PeerToPeer Namespace

An exciting new namespace added to the .NET Framework 3.5, the System.Net.PeerToPeer namespace, is located in the System.Net.dll assembly and provides the core building blocks needed to easily create a peer-to-peer (P2P) application. The namespace was designed in accordance with the three phases of a typical P2P application: discover, connect, and communicate. The discover phase is about dynamically locating peers and their associated network locations. The connect phase deals with establishing a network connection between peers. And the communicate phase is when data is passed back and forth among peers.

The features in the System.Net.PeerToPeer namespace provide a number of options that facilitate the discover and connect phases. Meanwhile, complementary technologies—such as Sockets, HTTPWebRequest, and the Window Communication Foundation Peer Channel—provide solutions for the communicate phase.

Before communication can occur between peers, they must be able to discover each other and resolve their network locations (addresses, protocols, and ports) from names or other types of identifiers. How peers discover each other and resolve locations is complicated by transient connectivity, the lack of address records in the DNS, and dynamic IP addresses. The Peer Name Resolution Protocol (PNRP) feature, supported in the .NET Framework 3.5, enables discovery and makes communication among peers possible by enabling serverless name resolution of any resource to a set of network endpoints. PNRP is responsible for two core tasks—publishing a PeerName for others to resolve and resolving a PeerName that was published by another peer and retrieving the associated metadata.

In the System.Net.PeerToPeer namespace, a PeerName represents an endpoint of communication, which can be anything (such as a computer, service, or application) you wish to associate metadata with. PeerNames come in two forms: secured and unsecured. A secured PeerName is backed by a public\private key pair and, when registered with PNRP, it can't be spoofed. Each PeerName string has an Authority section followed by a period and then a Classifier section. The Authority is machine-generated based on the type (secured or unsecured) of the PeerName, whereas the classifier is a user-defined string.

For a secured PeerName, the Framework automatically creates a 40-character hex string as the Authority. This hex string is a hash of the public key associated with the PeerName and is used to ensure registrations of such PeerNames are not spoofable. Here's how you create a secure PeerName:

PeerName p = new PeerName("My PeerName", PeerNameType.Secured);

For unsecured PeerNames, the Authority component is always the character 0. An unsecured PeerName is just a string and does not offer security guarantees:

PeerName p = new PeerName("My PeerName", PeerNameType.UnSecured);

After creating a PeerName, the next step is to associate the name with relevant metadata by instantiating a PeerNameRegistration object. A PeerName is typically associated with an IP address but, as you'll see, a PeerName can also be associated with a comment string and binary data blob. In the following code snippet, an IPAddress is explicitly associated with the PeerName by adding a PeerEndPoint instance to the registrations endpoint collection:

PeerName peerName = new PeerName("My PeerName", PeerNameType.Secured); PeerNameRegistration pnReg = new PeerNameRegistration(); pnReg.PeerName = peerName; pnReg.EndPointCollection.Add(new IPEndPoint( IPAddress.Parse("<ip address to associate with the name>"), 5000)); pnReg.Comment = "up to 39 unicode char comment"; pnReg.Data = System.Text.Encoding.UTF8.GetBytes( "A data blob up to 4K associated with the name");

While this is a valid use of the PeerNameRegistration class, we have found a common scenario is to associate all addresses assigned to the local machine with the PeerName. To do this, you simply ensure that the PeerNameRegistration.UseAutoEndPointSelection property is set to true and refrain from adding to the PeerNameRegistration.EndPointCollection.

Now that all the relevant metadata is assigned to the PeerName by virtue of the PeerNameRegistration object, the final step is to publish the PeerName to a cloud so that other peers can resolve the name. In PNRP, a cloud is simply a set of computers participating in PNRP and defines the scope to which names are published to or resolved from. When publishing a name, you need to determine which cloud (or scope) you want to publish the name to.

PNRP currently uses two clouds: link-local and global. A PeerName published to the link-local cloud means only other peers on the same link can resolve the name. A name published to the global cloud enables anyone on the Internet to resolve the PeerName. To publish a PeerName into the global cloud, you simply assign the global cloud, via the Cloud.Global enumeration, to the Cloud property on the PeerNameRegsitration object and then call the Start method on the registration object. Once the call to start completes, the name is published and can be resolved by remote peers. Figure 1 shows the code used to create and publish a PeerName.

Figure 1 Create and Publish a PeerName

using System; using System.Collections.Generic; using System.Text; using System.Net.PeerToPeer; namespace CreateAndPublish { class Program { static void Main(string[] args) { // Creates a secured PeerName. PeerName peerName = new PeerName( "MyWebServer", PeerNameType.Secured); PeerNameRegistration pnReg = new PeerNameRegistration(); pnReg.PeerName = peerName; pnReg.Port = 80; //Starting the registration means the name is published //for other peers to resolve. pnReg.Start(); Console.WriteLine("Registration of Peer Name: {0} complete.", peerName.ToString()); Console.WriteLine("Press any key to stop the registration " + "and close the program"); Console.ReadKey(); pnReg.Stop(); } } }

Now that you've seen how to create and publish a PeerName, you need to know how to resolve a PeerName. You start by instating an instance of the PeerNameResolver class, which is then used to resolve a name synchronously (see Figure 2) or asynchronously. If you are resolving names asynchronously, it is important to note that the same PeerNameResolver can be used to resolve multiple PeerNames. That is, you can start multiple asynchronous resolve operations for different PeerNames before the first operation completes, thus eliminating the need to instantiate a new resolver object for each resolve operation happening in parallel.

Figure 2 Synchronous Name Resolution

using System; using System.Collections.Generic; using System.Text; using System.Net.PeerToPeer; using System.Net; namespace SyncResolve { class Program { // The application accepts the peer name to resolve as the first // and only command line parameter. static void Main(string[] args) { // Create a resolver object to resolve a peername. PeerNameResolver resolver = new PeerNameResolver(); PeerName peerName = new PeerName(args[0]); // Resolve the PeerName - this is a network operation and will // block until the resolve request is completed. PeerNameRecordCollection results = resolver.Resolve(peerName); // Show the data returned by the resolve operation. Console.WriteLine("Records from resolution of PeerName: {0}", peerName); Console.WriteLine(); int count = 1; foreach (PeerNameRecord record in results) { Console.WriteLine("Record #{0} results...", count); Console.WriteLine("Comment:"); if (record.Comment != null) { Console.WriteLine(record.Comment); } Console.WriteLine("Data:"); if (record.Data != null) { //Assumes the data is an ASCII formatted string Console.WriteLine( System.Text.Encoding.ASCII.GetString(record.Data)); } Console.WriteLine("Endpoints:"); foreach (IPEndPoint endpoint in record.EndPointCollection) { Console.WriteLine("\t Endpoint:{0}", endpoint); Console.WriteLine(); } count++; } Console.ReadKey(); } } }

At this point, you can use the code shown in Figure 1 and Figure 2 to try creating and resolving PeerNames. But note that when trying out the PNRP functionality, you can't resolve a PeerName from the same process that published the name. We should also mention that the PNRP APIs discussed here use the Windows PNRP infrastructure and are supported on Windows XP (with the PNRP update available at support.microsoft.com/kb/920342 installed), Windows Vista®, and Windows Server 2008.

The .NET Framework 3.5 also introduces the System.Net.PeerToPeer.Collaboration namespace. Collaboration works in two contexts. The first, "People Near Me", deals with users who are logged into the collaboration infrastructure and located on the same subnet. The other context, referred to as "Contacts", involves people who have been added into your Windows Address book (this can be found in your Users directory in the Contacts folder). In the Contacts context, users do not have to be on the same subnet.

Both of these contexts allow a user to share applications and objects, send invites, and get notice of events involving other users. Consider a person in sales who needs to "meet" with her colleagues while waiting for a flight at the airport. Ideally, this person should be able to fire up her laptop, launch her company's main line of business (LOB) application, and have the application dynamically find her colleagues whether they are sitting with her in the airport or remain back in the office. The application will initially have to sign into the Collaboration infrastructure and specify the scope of the sign-in. In this example, PeerScope.All includes both the Internet and peer-near-me scope:

PeerCollaboration.Signin(PeerScope.All);

Once signed in, the application looks up all people near this salesperson using:

PeerNearMeCollection peers = PeerCollaboration.GetPeersNearMe();

The PeerNearMeCollection contains an instance of the PeerNearMe class for each peer located on the same subnet as the caller. Thus, the application now has a list of all the "People Near Me" in the form of PeerNearMe instances. A PeerNearMe instance includes a property that specifies the network location or IPEndPoint (IP Address + Port) of the remote peer.

To look up all the contacts stored in the salesperson's address book, the application needs to get all the contacts with the following lines of code:

ContactManager contactManager = PeerCollaboration.ContactManager(); PeerContactCollection contacts = contactManager.GetContacts();

The ContactManager represents the Windows Address Book, which is the contact store used by the System.Net.PeerToPeer.Collaboration infrastructure.

Now that a list of all peers (consisting of PeerNearMe and PeerContact) has been generated, this salesperson can choose which colleagues to interact with and quickly send invitations to each. An invitation, in the context of System.Net.PeerToPeer.Collaboration, is a mechanism to request a remote peer to start a particular application. To establish a network connection between two peer computers, one of the peers needs to be actively listening for incoming data. This mechanism allows one peer to tell another which application it needs to be running.

Say the salesperson in our example wants to interact with another colleague using the company LOB application. The salesperson needs to ensure that her colleague is running the LOB application. To do this, she sends an invitation to the colleague. When the colleague's system receives the invitation, a dialog box is presented to the user stating that a particular person wants them to start a specific application (see Figure 3). The dialog gives the option to accept or decline the invitation. If the recipient clicks the Accept button, the application stated in the invitation automatically launches on the recipients PC—the LOB app in this case—assuming the application is already installed.

Figure 3 Invitation to Launch an App from a Peer

Figure 3** Invitation to Launch an App from a Peer **(Click the image for a larger view)

Now both colleagues have the necessary application running on their systems and are ready to collaborate. At this point, the LOB application uses another networking technology (such as Windows Communication Foundation Peer Channel, Sockets, or HTTP) to communicate.

Now that we've described invitations, let's look at the details and associated code needed to enable invitations using the System.Net.PeerToPeer.Collaboration namespace. As far as the P2P infrastructure is concerned, an application is any executable on your machine. To support invitations, the application has to be registered with the peer collaboration infrastructure using the same GUID on both the inviter and the invitee machines. This code shows how to create and register an application based on an executable installed on the local machine:

PeerApplication application = new PeerApplication( appGuid, "Collaboration Application", bytes, pathToApp, arguments, PeerScope.Internet); PeerCollaboration.RegisterApplication( application, PeerApplicationRegistrationType.AllUsers);

Registered applications that get launched via invitations can query which contact or endpoint sent the invitation using PeerCollboration.ApplicationLaunchInfo. This allows the application that just launched to know it was launched via an invitation from a remote peer and thus it knows to connect back to the peer that sent the invitation. An invitation can be sent by calling Invite on the peer object returned from GetPeersNearMe or to a contact returned from GetContacts with Invite as follows:

PeerInvitationResponse pir = peerNearMe.Invite( app, "Hello World", data); PeerInvitationResponse pir = contact.Invite( app, "Hello World", data);

Say the salesperson is communicating with a colleague at the airport and is planning to follow later when the two are in different locations. She should add that person as a Contact on her machine. This is done using the peer class returned from the GetPeersNearMe call with peer.AddToContactManager. Once the salesperson and her colleague have added each other as Contacts, they can look up and interact with each other from wherever they may be.

The collaboration APIs in the .NET Framework 3.5 also give you the ability to share data. For example, if all the sales people from the company want to share their business cards electronically, this can be done using peer objects. Peer objects are simply blobs of data (such as an image file) that can be viewed by remote peers. Objects can be shared by scope, specifying People Near Me to allow users on the same subnet to see the objects or in the Internet scope so your contacts can see the objects regardless of where the users are. For example, to create an object and share it in the People Near Me scope, you simply do this:

PeerObject object = new PeerObject( objectGuid, bytes, PeerScope.NearMe); PeerCollaboration.SetObject(object);

Presence and change notifications are also an important element of P2P collaboration. Say, for instance, our salesperson wants to chat with her colleague Steve at the airport as soon as Steve is signed in and available. An event called PeerNearMeChanged is provided on the PeerNearMe class to update peers on the same subnet. Any changes to the peer will trigger the attached delegate to be called with the change information. This way, the salesperson can be notified when Steve enters (or leaves) the network. The necessary code looks like this:

PeerNearMe.PeerNearMeChanged += PeerNearMeChangedCallback; ... void PeerNearMeChangedCallback( object sender, PeerNearMeChangedEventArgs args) { // Check which PeerNearMe has changed and what the change was // from the args parameter. }

Change notifications for Contacts in another location entail a few more steps. The process requires reciprocal trust, meaning you must choose to watch the Contact and that user must choose to allow you to watch him. So first you elect to watch the contact—by calling the Subscribe method on the Contact. Then the peer you want to watch must add you as a Contact and allow you to watch—by setting the SubscribeAllowed property on your Contact to SubscriptionType.Allowed. When these steps have been done, you can track specific changes to names, objects, applications, and so on for the peer you are watching.

For example, suppose our salesperson wants to chat with a customer contact as soon as he advertises himself as being online. The following code will allow the salesperson to get the necessary status changes of the customer:

custContact.PresenceChanged += ContactPresenceChangedCallback; ... void ContactPresenceChangedCallback( object sender, PresenceChangedEventArgs args) { if (args.PeerPresenceInfo.PresenceStatus == PeerPresenceStatus.Online) { // Start chatting with the customer } }

Note that change information can be for a particular object or application as well. Attaching a delegate to the ObjectChanged event in the PeerObject class will provide information about any changes to that object and where the changed object came from, such as from a Contact or a PeerNearMe. In a similar manner, adding a delegate to the ApplicationChanged event in PeerApplication will provide information about any changes to that application.

For example, to monitor whether a particular application changed from the list of applications exposed by any contact in your contact list, a delegate needs to be added to the ApplicationChanged event on PeerApplication:

peerApplication.ApplicationChanged += AppChangedCallback; ... void AppChangedCallback( object sender, ApplicationChangedEventArgs args) { // Check what the change was and which contact and endpoint it // originated from the args parameter. }

Now let's consider a situation in which the salesperson is going to be occupied and therefore her status needs to be changed to alert peers that she is not immediately available. This is done by modifying the data associated with the PeerCollaboration.LocalPresence property, like so:

PeerCollaboration.LocalPresenceInfo = new PeerPresenceInfo( PeerPresenceStatus.Away, "Talking with Customer");

Wrapping Up

This article provides only a brief look at the new core networking features being added to the .NET Framework 3.5. If you can't wait, you can download the latest CTP release at msdn2.microsoft.com/aa700831 to give these new features a try. To keep up with the latest news regarding System.Net, as well as Windows Networking technologies, visit the Windows Network Developer Platform team blog at blogs.msdn.com/wndp.

Mariya Atanasova is a Software Design Engineer in Test on the System.Net team. She can be reached via her blog at blogs.msdn.com/mariya.

Larry Cleeton is a Software Design Engineer on the System.Net team. He can be reached via his blog at blogs.msdn.com/lcleeton.

Mike Flasko is the Program Manager for the System.Net, Winsock, and Winsock Kernel teams. He can be reached via his blog at blogs.msdn.com/mflasko.

Amit Paka is a Software Design Engineer on the System.Net team. He can be reached via his blog at blogs.msdn.com/amitpaka.