The Microsoft Windows Peer-to-Peer Networking platform is Microsoft’s implementation of P2P technology. It is part of Windows XPSP2 and Windows Vista, and is also available as an add-on for Windows XPSPI. It includes two technologies that you can use when creating .NET P2P applications:
- The Peer Name Resolution Protocol (PNRP), which is used to publish and resolve peer addresses
- The People Near Me server, which is used to locate local peers (currently Vista only)
In this section you learn about both of these.
Peer Name Resolution Protocol (PNRP)
You can of course use any protocol at your disposal to implement a P2P application, but if you are working in a Microsoft Windows environment (and, let’s face it, if you’re reading this book you probably are) it makes sense to at least consider PNRP. There have been two versions of PNRP released to date. PNRP version 1 was included in Windows XP SP2, Windows XP Professional x64 Edition, and Windows XP SPl with the Advanced Networking Pack for Windows XP. PNRP version 2 was released with Windows Vista, and was made available to Windows XP SP2 users through a separate.
In itself, PNRP doesn’t give you everything you need to create a P2P application. Rather, it is one of the underlying technologies that you use to resolve peer addresses. PNRP enables a client to register an endpoint (known as a peer name) that is automatically circulated among peers in a cloud. This peer name is encapsulated in a PNRP /D. A peer that discovers the PNRP 10 is able to use PNRP to resolve it to the actual peer name, and can then communicate directly with it.
For example, you might define a peer name that represents a WCF service endpoint. You could use PNRP to register this peer name in a cloud as a PNRP 10. Apeer running a suitable client application that uses a discovery mechanism that can identify peer names for the service you are exposing might
then discover this PNRP 10. Once discovered, the peer would use PNRP to locate the endpoint of the WCF service and then use that service.
An important point is that PNRP makes no assumptions about what a peer name actually represents. It is lip to peers to decide how to use them once discovered. The information a peer receives from PNRP when resolving a PNRP ID includes the IPv6 (and usually also the IPv4) address of the publisher of the ID, along with a port number and optionally a small amount of additional data. Unless the peer knows what the peer name means it is unlikely to be able to do anything useful with this information.
PNRP IDs are 256-bit identifiers. The low-order 128 bits are used to uniquely identify a particular peer, and the high-order 128 bits identify a peer name. The high-order 128 bits are a hashed combination of a hashed public key from the publishing peer and a string of up to 149 characters that identifies the peer name. The hashed public key (known as the authority) combined with this string (the classifier) are together referred to as the P2P 10. It is also possible to use a value of 0 instead of a hashed public key, in which case the peer name is said to be unsecured (as opposed to secured peer names, which use a public key).
The structure of a PNRP 10 is illustrated in Figure 47-4.
The PNRP service on a peer is responsible for maintaining a list of PNRP IDs, including the ones that it publishes as well as a cached list of those it has obtained by PNRP service instances elsewhere in
the cloud. When a peer attempts to resolve a PNRP ID, the PNRP service either uses a cached copy of the endpoint to resolve the peer that published the PNRP or it asks its neighbors if they can resolve it. Eventually a connection to the publishing peer is made and the PNRP service can resolve the PNRP 10.
Note that all of this happens without you having to intervene in any way. All you have to do is ensure that peers know what to do with peer names once they have resolved them using their local PNRP service.
Peers can use PNRP to locate PNRP IDs that match a particular P2P 10. You can use this to implement a very basic form of discovery for unsecured peer names. This is because if several peers expose an unsecured peer name that uses the same classifier, the P2P 10 will be the same. Of course, because any peer can an unsecured peer name you have no guarantee that the endpoint you connect to will be the sort of endpoint you expect, so this is only really a viable solution for discovery over a local network.
In the preceding discussion you learned how PNRP registers and resolves peer names in clouds. A cloud is maintained by a seed server, which can be any server running the PNRP service that maintains a record of at least one peer. Two types of cloud are available to the PNRP service:
- Link local- These clouds consist of the computers attached to a local network. A PC may be connected to more than one link local cloud if it has multiple network adapters.
- Global – This cloud consists of computers connected to the Internet by default, although it is also possible to define a private global cloud. The difference is that Microsoft maintains the seed server for the global Internet cloud, whereas if you define a private global cloud you must use your own. If you use your own seed server you must ensure that all peers connect to it by configuring policy settings.
In past releases of PNRP there was a third type of cloud, site local. This is no longer used and you Won’t look at it ill this chapter.
You can discover what clouds you are connected to with the following command:
netsh p2p pnrp cloud show list
A typical result is shown in Figure 47-5 .
Figure 47-5 shows that a single cloud is available, and that it is a link local cloud. You can tell this from both the name and the Scope value, which is 3 for link local clouds and 1 for global clouds. In order to connect to a global cloud you must have a global IPv6 address. The computer used to generate Figure 47-5 does not have one, which is why only a local cloud is available.
Clouds may be in one of the following states:
- Active – If the state of a cloud is active, you can use it to publish and resolve peer names.
- Alone – If the peer you are querying the cloud from is not connected to any other peers, it will have a state of alone.
- No Net – If the peer is not connected to a network, the cloud state may change from active to no net. .
- Synchronizing – Clouds will be in the synchronizing state when the peer is connecting to them. This state will change to another state extremely quickly because this connection does not take long, so you .will probably never see a cloud in this state. .
- Virtual– The PNRP service connects to clouds only as required by peer name registration and resolution. If a cloud connection has been inactive for more than 15 minutes it may enter the virtual state. .
If you experience network connectivity problems you should check your firewall in case it is preventing local network traffic over the UDP ports 3540or 1900.UDP port 3540 is used by PNRP,and UDP port 1900is used by the Simple Service Discovery Protocol (SSDP),which inturn is used by the PNRP service (as well as UPnP devices).
People Near Me
PNRP, as you saw in the previous section, is used to locate peers. This is obviously important as an enabling technology when you consider the discovery! connection! communication process of a P2P application, but in itself is not a complete implementation of any of these stages. The People Near Me service is an implementation of the discovery stage, and enables you to locate peers that are signed into the Window People Near Me service in your local area (that is, in a link local cloud that you are connected to).
You may have come across this service because it is built into Vista, and is used in the Windows Meeting Space application, which you can use for sharing applications among peers. You can configure this service through the Start menu with the dialog shown in Figure 47-6.
Once signed in the service is available to any application that is built to use the PNM service.
At the time of writing, PNM is available only on the Windows Vista family of operating systems. However, it is possible that future service packs or additional downloads may make it available on Windows XP.
Building P2P Applications
Now that you have learned what P2P networking is and what technologies are available to .NET developers to implement P2P applications, it is time to look at how you can build them. From the previous discussion you know that you will be using PNRP to publish, distribute, and resolve peer names, so the first thing you look at here is how to achieve that using .NET. Next you look at how to use PNM as a framework for a P2P application. This can be advantageous because you do not have to implement your own discovery mechanisms.
To examine these subjects you need to learn about the classes in the following namespaces:
To use these classes you must have a reference to the System. Net. dll assembly.
System. Net. PeerToPeer
The classes in the System. Net. PeerToPeer namespace encapsulate the API for PNRP and enable you to interact with the PNRP service. You will use these classes for two main tasks:
- Registering peer names
- Resolving peer names
In the following sections, all the types referred to come from the System. Net. PeerToPeer namespace unless otherwise specified.
Registering Peer Names
To register a peer name you must carry out the following steps:
- Create a secured or unsecured peer name with a specified classifier.
- Configure a registration for the peer name, specifying some or none of the following optional information:
- A TCP port number
- The cloud or clouds to register the peer name with (if unspecified, PNRP will register the peer name in all available clouds)
- A comment of up to 39 characters
- Up to 4096 bytes of additional data
- Whether to generate endpoints for the peer name automatically (the default behavior, where endpoints will be generated from the lP address or addresses of the peer and, if specified, the port number)
- A collection of endpoints
3. Use the peer name registration to register the peer name with the local PNRP service.
After Step 3 the peer name will be available to all peers in the selected cloud (or clouds). Pre- registration continues until it is explicitly stopped, or until the process that registered the peer name is terminated.
To create a peer name you use the PeerName class. You create an instance of this class from a string representation of a P2P 10 in the form authority. classifier or from a classifier string and a PeerNameType. You can use PeerNameType . Secured or PeerNameType” Unsecured. For example:
PeerName pn = new PeerName(‘Peer classifier’, PeerNameType.Secured);
Because an unsecured peer name uses an authority value of 0, the following lines of code are equivalent:
PeerName pn new PeerName(‘Peer classifier’, PeerNameType.Unsecured);
PeerName pn new PeerName(‘0.Peer classifier’);
Once you have a PeerName instance you can use it along with a port number to initialize a PeerNameRegistration object:
PeerNameRegistration pnr = new PeerNameRegistration(pn, 8080);
Alternatively, you can set the PeerNarne and (optionally) the Port properties on a . PeerNameRegistration object created using its default parameter. You can also specify a Cloud instance as a third parameter of the PeerNameRegistration constructor, or through the Cloud property. You can obtain a Cloud instance from the cloud name or by using one of the following static members of Cloud:
- Cloud. Global – This static property obtains a reference to the global cloud. This may be a private global cloud depending on peer policy configuration.
- Cloud. All LinkLocal – This static field gets a cloud that contains all the link local clouds available to the peer. :
- Cloud. Available – This static field gets a cloud that contains all the clouds that are available to the peer, which includes link local clouds and (if available) the global cloud.
Once created, you can set the Comment and Data properties if you want to. Be aware of the limitations of these properties, though. You will receive a PeerToPeerException if you try to set Comment to a string of greater than 39 Unicode characters or an ArgurnentOutOfRangeException if you try to set Data to a byte of greater than 4096 bytes. You can also add endpoints by using the EndPointCollection property. This property is a System. Net. IPEndPointCollection collection of System.Net. IPEndPoint objects. If you use the EndPointCollection property you might also want to set the UseAutoEndPointSelection property to false to prevent automatic generation of endpoints.
When you are ready to register the peer name you can call the PeerNameRegistration. Start() method. To remove a peer name registration from the PNRP service you use the PeerNameRegistration. Stop() method .
The following code registers a secured peer name with a comment:
PeerName pn = new PeerName(‘Peer classifier’, PeerNameType.Unsecured);
PeerNameRegistration pnr = new PeerNameRegistration(pn, 8080);
pnr.Cornment = ‘Get pizza here’;
Resolving Peer Names
Resolving Peer Names To resolve a peer name you must carry out the following steps:
- Generate a peer name from a known P2P ID or a P2P ID obtained through a discovery technique.
- Use a resolver to resolve the peer name and obtain a collection of peer name records. You can limit the resolver to a particular cloud and/or a maximum number of results to return.
- For any peer name records that you obtain, obtain peer name, endpoint, comment, and additional data information as required.
This process starts with a PeerNarne object just like peer name registration. The difference here is that you use a peer name that is registered by one or more remote peers. The Simplest way to get a list of active peers in your link local cloud is for each peer to register an unsecured ‘peer name with the same classifier and to use the same peer name in the resolving phase. However, this is not a recommended strategy for global clouds because unsecured peer names are easily spoofed.
To resolve peer names you use the PeerNameResolver class. Once you have an instance of this class you can choose to resolve peer names synchronously by using the Resolve() method, or asynchronously using the ResolveAsync() method.
You can call the Resolve() method with a single PeerName parameter, but you can also pass an optional Cloud instance to resolve in, an int maximum number of peers to return, or both. This method returns a PeerNameRecordCollection instance, which is a collection of PeerNameRecord objects. For example, the following code resolves an unsecured peer name in all link local clouds and returns a maximum of 5 results:
PeerName pn = new PeerName(·O.Peer classifier·);
PeerNameResolver pnres = new PeerNameResolver();
PeerNameRecordCollection pnrc = pnres.Resolve(pn. Cloud.AllLinkLocal. 5);
The ResolveAsync() method uses a standard asynchronous method call pattern. You pass a unique users state object to the method, and listen for ResolveProgressChanged events for peers being found and the ResolveCompleted event when the method terminates. You can cancel a pending asynchronous request with the ResolveAsyncCancel() method.
Event handlers for the ResolveProgressChanged event use the ResolveProgressChangedEventArgs event arguments parameter, which derives from the standard System. ComponentModel . ProgressChangedEventArgs class. You can use the PeerNameRecord property of the event argument object you receive in the event handler to get a reference to the peer name record that was found.
Similarly, the ResolveCompleted event requires an event handler that uses a parameter of type ResolveCompletedEventArgs, which derives from AsyncCompletedEventArgs. This type includes a PeerNameRecordCollection parameter you can use to obtain a complete list of the peer name records that were found.
The following code shows an implementation of event handlers for these events:
Once you have one or more PeerNameRecord objects you can proceed to process them. This PeerNameRecord class exposes Comment and Data properties to examine the comment and data set in the peer name registration (if any), a PeerName property to get the PeerName object for the peer name record, and, most importantly, an EndPointCollection property. As with PeerNameRegistration, this property is a System. Net . IPEndPointCollection collection of System.Net. IPEndPoint objects. You can use these objects to connect to end points exposed by the peer in any way you want.
Code Access Security In System.Net.PeerToPeer
The System. Net. PeerToPeer namespace also includes the following two classes that you can use with CAS (see Chapter 20):
- PnrpPerrnission, which inherits from CodeAccessPerrnission
- PnrpPerrnissionAttribute, which inherits from CodeAccessSecurityAttribute
You can use these classes to provide permissions functionality for PNRP access in the usual CAS way.
The downloadable code for this chapter includes a sample P2P application (P2PSample) that uses the concepts and namespace introduced in this section. It is a WPF application that uses a WCF service for a peer endpoint.
The application is configured with an application configuration file, in which you can specify the name of the peer and port to listen on as follows:
Once you have built the application you can test it either by copying it to other computers in your local network and running all instances, or by running multiple instances on one computes)f you choose the latter option you must remember to change the port used for each instance by changing individual config files (copy the contents of the Debug directory on your local computer and edit each config file in turn). Things will be clearer in both ways of testing this application if you also change the username for each instance.
Once the peer applications are running, you can use the Refresh button to obtain a list of peers asynchronously. When you have located a peer you can send a default message by clicking the Message button for the peer.
Figure 47·7 shows this application in action with three instances running on one machine. In the figure, one peer has just messaged another and this has resulted in a dialog box .
Most of the work in this application takes place in the Window_Loaded () event handler for the window window. This method starts by loading configuration information and setting the window title with the username:
Next the peer host address is used along with the configured port to determine the endpoint on which to host the WCF service.The service will use NetTcpBinding binding, so the URL of the endpoint uses the net. tcp protocol:
The endpoint URL is validated, and then the WCF service is registered and started:
A singleton instance of the service class is used to enable easy communication between the host app and the service (for sending and receiving messages). Also, note that security is disabled in the binding. configuration for simplicity.
Next System. Net. PeerToPeer namespace classes are used to register a peer name:
When the Refresh button is clicked the RefreshButton_Click() event handler uses PeerNameResolver.Resol veAsync() to get peers asynchronously:
The remainder of the code is responsible for displaying and communicating with peers, and you can explore it at your leisure.
Exposing WCF endpoints through P2P clouds is a great way of locating services within an enterprise, as well as being an excellent way to communicate between peers as in this example.
System. Net. PeerToPeer. Collaboration
The classes in the System. Net. PeerToPeer. Collaboration namespace provide a framework you can use to create applications that use the People Near Me service and the P2P collaboration API. As mentioned earlier, this is possible only if you are using Windows Vista at the moment
You can use the classes in this namespace to interact with peers and applications in a number of ways, including:
- Signing in and signing out
- Discovering peers
- Managing contacts and detecting peer presence
You ran also use the classes in this namespace to. invite other users to join an application, and to exchange data between users and applications. However, in order to do this you need to create your own PNM- capable applications, which is beyond the scope of this chapter.
In the following sections, all the types referred to come from the System. Net . PeerToPeer. Collaboration namespace unless otherwise specified.
SignIng In and Signing Out
One of the most important classes in the System.Net. PeerToPeer . Collaboration namespace is the PeerCollaboration class. This is a static class that exposes numerous static methods that you can use for various purposes, as you will see in this and subsequent sections. You can use two of the methods it exposes, SignIn() and SignOut(), to (unsurprisingly) sign in and sign out of the People Near Me service. Both of these methods take a single parameter of type PeerScope, which can be one of the following values:
- PeerSeope. None- If you Use the value SignIn() and SignOut() will have no effect.
- PeerSeope .NearMe- This will sign you in to or out of the link local clouds.
- PeerSeope. Internet – This will sign you in to or out of the global cloud (which may be necessary to connect to a contact who is not currently on your local subnet).
- PeerSeope .All – This will sign you in to or out of all available clouds.
If necessary, calling SignIn() will cause the People Near Me configuration dialog to be displayed.
When a peer is signed in you can use the PeerCollaboration. LoealPresenceInfo property to a value of type PecrPreseneeInfo. This enables standard Mfunctionality, such as setting your status to away. You can set the PeerPresenceIn fo. DeseriptiveText property to a Unicode string of up to 255 characters, and the PeerPresenceInfo. PresenceStatus property to a value from the PeerPresenceStatus enumeration. The values that you can use for this enumeration are as follows:
- PeerPresenceStatus .Away- The peer is away.
- PeerPresenceStatus. BeRightBack – The peer is away, but will be back soor..
- PeerPresenceStatus .Busy – The peer is busy.
- PeerPresenceStatus. Idle – The peer isn’t active.
- PeerPresenceStatus. Offline – The peer is offline.
- PeerPresenceStatus. Online – The peer is online and available.
- peerpresenceStatus .OnThePhone – The peer is busy with a phone call.
- PeerPresenceStatus. OutToLunch – The peer is away, but will be back after lunch.
You can obtain a list of peers near you if you are logged in to the link local cloud. You do this by using the PeerCollaboration.GetPeersNearMe() method. This returns a PeerNearMeCollection object containing PeerNearMe objects.
You can use the Nickname property of PeerNearMe to obtain the name of a peer, Is Online to determine whether the peer is online, and (for lower-level operations) the PeerEndpoints property to determine endpoints related to the peer. PeerEndPoints is also necessary if you want to find out the online status of a PeerNearMe. You can pass an endpoint to the GetPresenceInfo() method to obtain a .PeerPresenceInfo object, as described in the previous section.
Managing Contacts and Detecting Peer Presence
Contacts are a way in which you can remember peers. You can add a peer discovered through the People Near Me service and from then onward you can connect to them whenever you are both online. You can connect to a contact through link local or global clouds (assuming you have IPv6 connectivity to the Internet).
You can add a contact from a peer that you have discovered, by calling the PeerNearMe . AddToContactManager () method. When you call this method you can choose to associate a display name, nickname, and email address with the contact. Typically, though, you will manage contacts by using the ContactManager class.
However you manipulate contacts, you will be dealing with PeerContact objects. PeerContact, inherits from the abstract Peer base class. PeerContact has more properties and methods than PeerNearMe. Peer Contact includes DisplayName and EmailAddress properties that further describe a PNM peer, for example. Another difference between these two types is that PeerContact has a more explicit relationship with the System. Net. PeerToPeer. PeerName class. You can get a PeerName from a PeerContact through the PeerContact. PeerName property. Once you have done this you can proceed to use techniques you looked at earlier to communicate with any endpoints the PeerName exposes.
Information about the local peer is also accessible through the ContactManager class, through the static ContactManager. LocalContact property. This gets you a PeerContact property with details of the local peer.
You can add PeerNearMe objects to the local list of contacts by using either the ContactManager . CreateContact() or CreateContactAsync() method, or PeerName objects by using the GetContact() method. You can remove contacts represented by a PeerNearMe or PeerName object with the DeleteContact() method.
Finally, there are events that you can handle to respond to changes to contacts. For example, you can use the PresenceChanged event to respond to changes of presence for any of the contacts known by the ContactManager.
There is a second sample application in the downloadable code for this chapter that illustrates the use of classes in the System.net PeerToPeer.Collaboration namespace, This application is similar to the other sample, but much simpler. You will need two computers that can both sign in to the PNM server in order to see this application in action, because it enumerates and displays P :M peers from the local subnet,
When you run the application with at least one peer available for discovery the display will be similar to Figure 47-8.
The code is structured in the same way as the previous example, so if you’ve read through that code you should be familiar with this code. This time there is not much work to do in the Window_Loaded() event handler except sign in, because there is no WCF service to initialize or peer name registration to achieve:
Most of the work is done in the RefreshButton_Click () event handler. This uses the PeerCollaboration. GetPeersNearMe() method to obtain a list of peers, and add those peers to the display using the PeerEntry class defined in the project,or a failure message if none are found.
As you can see from this example, interacting with the PNM service is made very simple by the classes you have learned about.