|
Lately there's been a lot of buzz about Orcas. You keep hearing about Silverlight and the Entity Framework and Jasper and Astoria and who knows how many other code names. One of the things that you don't hear about is PNRP. Why? I haven't the faintest idea because this is some of the coolest stuff to surface as a .NET API since .NET 1.0 reshaped our opinion of streams.
While experimenting with Orcas, I noticed that the PNRP (Peer Name Resolution Protocol) did in fact have a managed API. It is exposed through System.Net.PeerToPeer in the Beta 1 bits. PNRP is basically a service registry. Whenever you have a service that has become available that you want other clients on your network to know about, you register that information with PNRP. The great thing about PNRP is that you don't need a server like you do with DNS. You register the peer name with your local PNRP (Windows Service on XP SP 2 w/PNRP update or Vista RTM) service. If you are near other PNRP implementations, they will share information and propogate the service registration depending on the scope you've chosen for your service name.
Why is this cool and why should you care? Simple. Peer networking is not all about sharing music and downloading l33t w4rez. Let's say you have a corporate backbone that's running all of your company's systems. Now let's say that you bring up a client application (which could be a desktop app or even a public-facing web application). How does your client application know where to go in order to find the service for submitting purchase orders? Sure, you could hard-code it, or even put it in an App.config, but a lot of the time people actually end up writing complex infrastructures to deal with service registries. Also, let's face it, UDDI sucks.
What if your client application were to be able to come up and do a quick PNRP query and get results that indicate there are 3 servers on the network currently implementing the purchase order service. Additionally, each service registry entry has a little bit of info to help your client application pick which service to use. This is where it gets good: WCF + PNRP = Pure Gold.
So I set off to write a sample application. Basically I created a couple of host servers that pretend to be photo libraries. As each service comes up, it not only turns on the WCF listeners, but it registers the service location with PNRP. Then, a single client comes up and queries the list of available network photo albums. For each one that it finds, it dynamically connects to that photo album and retrieves an icon representing the album by invoking a method on the advertised WCF photo album service.
The end result is an application that looks like this:

It might not look like much, but this application is enumerating a list of WCF services that were dynamically registered by WCF host applications. In addition, the client application is connecting to each of these advertised services and querying the service for additional information (in this case, an icon).
To take a look at how ridiculously easy this is, here's the code that starts the WCF photo album service and registers a publicly available peer name with PNRP:
ServiceHost host = new ServiceHost(typeof(PhotoService));
host.Open();
PeerName pName = new PeerName(
"PhotoService",
PeerNameType.Unsecured);
PeerNameRegistration reg = new PeerNameRegistration(
pName, Int32.Parse(advertPort));
reg.Comment = advertName;
reg.Data = System.Text.ASCIIEncoding.ASCII.GetBytes(
advertData);
reg.Start();
The advertPort is a port number defined in the app.config file, and advertData is a string containing a comment description (such as "Bob's Photos") that was also defined in the app.config of the service host. There is a limited amount of data you can store on the peer name registration, so keep it short and sweet. If you need additional information about the service, you can connect to the service and invoke methods on it (like I"m doing to obtain the icon for the service).
Here's the code on the client that enumerates the list of all "PhotoService" services on the network registered with PNRP. For each enumerated service, the client ignores the IPv6 address, and connects to the IPv4 address, establishes the WCF proxy instance, obtains the image icon (in a Hex64 encoded string of bytes), and saves it (the idea was I might cache the file so I don't have to query every time), and then finally binds the GUI to the URI of the locally cached image:
public void EnumeratePhotoServices()
{
ModelRoot model = ModelRoot.Current;
model.DiscoveredServices.Clear();
PeerNameResolver resolver = new PeerNameResolver();
PeerNameRecordCollection peers =
resolver.Resolve(new PeerName("0.PhotoService"));
foreach (PeerNameRecord peer in peers)
{
Console.WriteLine("{0}", peer.PeerName.ToString());
DiscoveredService service = new DiscoveredService();
service.Name = peer.PeerName.ToString();
service.Comment = peer.Comment;
service.Data = System.Text.ASCIIEncoding.ASCII.GetString(
peer.Data);
service.EndPoints = peer.EndPointCollection;
foreach (IPEndPoint ep in peer.EndPointCollection)
{
Console.WriteLine("Service discovered at {0}:{1}",
ep.Address.ToString(),
ep.Port.ToString());
Console.WriteLine(ep.Address.AddressFamily.ToString());
if (ep.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
{
string Url = "net.tcp://" + ep.Address.ToString() + ":" + ep.Port.ToString() +
"/PhotoService";
NetTcpBinding binding = new NetTcpBinding();
binding.ReaderQuotas.MaxStringContentLength = int.MaxValue;
binding.MaxReceivedMessageSize = int.MaxValue;
binding.MaxBufferSize = int.MaxValue;
EndpointAddress epAddress = new EndpointAddress(Url);
ChannelFactory<IPhotoService> fact = new ChannelFactory<IPhotoService>(binding,
epAddress);
IPhotoService photos = fact.CreateChannel();
string iconByteString = photos.GetIconBytes();
byte[] iconBytes = Convert.FromBase64String(iconByteString);
Console.WriteLine("received {0} image bytes.", iconBytes.Length);
FileStream fs = File.Create(@"C:\" +ep.Address.ToString() +"_" + ep.Port.ToString() + ".png");
fs.Write(iconBytes, 0, iconBytes.Length);
fs.Close();
service.Icon = @"C:\" + ep.Address.ToString() + "_" + ep.Port.ToString() + ".png";
}
}
model.DiscoveredServices.Add(service);
}
}
In short, if you're using WCF and you're using Orcas, you need to give the PNRP stack a look, because being able to dynamically, in a shared, serverless, peer fashion, register the location of services is extremely useful.
Is this a zeroconf implementation that is interoperable with Bonjour?
It looks a bit like the multicast DNS that Bonjour (nee Rendezvous) is
built on. I like the NSNetService APIs better though; they send messages
to a delegate upon discovery, making discovery asynchronous. (They've also
been part of Cocoa since the release of Mac OS X 10.2 Jaguar in 2002.)
This implementation is (as far as I know) not interoperable with Bonjour. I
was trying to be fair and not mention Bonjour in this post because I find
Apple's Distributed Objects in conjunction with Bonjour to be far more
pleasant to use than WCF and Orcas' PNRP stack.
What I find hilarious is that this reinvented wheel is handed down from on
high and we're all supposed to accept it as if this is the first time
anyone has seen multicast/peer2peer DNS before. How old is
Rendezvous/Bounjour??
PNRP is more than a multicast discovery technology. It does name
resolution through an overlay network. You can publish and resolve names
through the internet (your laptop at a coffee shop can find your PC at
home). Microsoft has a couple of technologies that work like Bonjour:
SSDP, WSD and function discovery. PNRP is something completely different.
There's a bunch of information on the Microsoft peer-to-peer website if
you're interested (www.microsoft.com/p2p).