whateverblog.
Can zeroconf .NET LAN services be this easy?
Thursday, September 11, 2003 08:40 AM

I haven't written about zeroconf services lately so I thought I'd follow up on this comment I made about a month ago:

I've got some ideas about how to create a nice high-level C# binding to this. You should be able to take a marshal-by-ref object and just make one API call that says "make this available to everyone on the network."

Let's compare this idea to making an object available to .NET Remoting clients. Start with the following class:

public class MediaLibrary : MarshalByRefObj
{
  ///
  /// .NET Remoting housekeeping--make this a singleton
  ///
  public override object InitializeLifetimeService()
  {
    return null;
  }

  ///
  /// Returns a list of media on this server that 
  /// matches the given pattern.
  ///
  public string[] ListMedia(string pattern)
  {
    // ...details, details...
  }

  ///
  /// Returns a stream that is the raw file data
  /// for the requested media.
  ///
  public Stream GetMediaStream(string path)
  {
    // ...details, details...
  }
}

If you want to make this a marshal-by-ref singleton object, you need to add the italicized text. As you can see, it's not very expensive in terms of extra lines of code you have to add (though having to derive from MarshalByRefObj could obviously be quite invasive if you're building off an existing class hierarchy).

So now we have a class that can be remoted. How do we get the CLR process to start listening for requests? Well, there are a lot of knobs you can turn here, depending on what kind of serialization you want (binary or XML) and what kind of communications protocol (bare TCP/IP sockets or HTTP) or if you want to customize/extend the process. Let's ignore all of that; here is a simple cut-and-paste template for basic binary TCP/IP communication (which is generally what you want anyway when you're not dealing with firewalls or interop):

ChannelServices.RegisterChannel(new TcpChannel(1234));
RemotingConfiguration.RegisterWellKnownServiceType(
    typeof(MediaLibrary),
    "MediaLibrary.binary",
    WellKnownObjectMode.Singleton);

The first line creates a TcpChannel on port 1234 and registers it with the runtime, which will from then on listen for remoting calls on that port. The second line takes our particular class and publishes an instance of it at the URL "/MediaLibrary.binary". Thus, these two lines make a media library available at tcp://hostname:1234/MediaLibrary.binary.

Clients connect to the server like so:

RemotingConfiguration.RegisterWellKnownClientType(
    typeof(MediaLibrary),
    "tcp://myserver:1234/MediaLibrary.binary");
MediaLibrary remoteLibrary = new MediaLibrary();

The first method call needs to be done just once (per CLR lifetime), and then any call to MediaLibrary's constructor will result in the creation of a MediaLibrary remoting proxy that connects to our server, as demonstrated in the last line. You can bury the first call somewhere in your client startup routine, and after that it all looks like magic. Need a MediaLibrary? Just new one up, don't worry about how it is implemented, pay no attention to the man behind the curtain.

While remoting as I've presented it is convenient, it's not particularly dynamic. The client has to know the server hostname, the port, and the service URI in advance; I suppose this info is usually gleaned from a config file, registry key, or user dialog. That's pretty annoying for something like a media library (well, I don't mind having a static service URI; just the hostname and port). What I really want is to be able to automatically get references to any (and hopefully, something approximating "every") MediaLibrary service on the LAN. Furthermore, I'd like to be able to do it without the user ever explicitly setting up a directory server.

Using the same MediaLibrary class, I want the following API on the server:

void EasyZeroConf.Publish(MarshalByRefObj serviceObj, string serviceUri);

And the following on the client:

object[] EasyZeroConf.Find(Type desiredType, string serviceUri);

The Publish method is simple enough: Take serviceObj and make it available to anyone requesting the service identified by serviceUri.

The Find method is a little less obvious: Search the LAN for serviceUri, and return the results as an array of remoting proxies of type desiredType. Here's an example:

MediaLibrary[] libraries = 
    (MediaLibrary[])EasyZeroConf.Find(typeof(MediaLibrary), "MediaLibrary");

That should be literally the only two methods most library users should need to get simple zeroconf LAN services, though I can think of many scenarios where more customization would be very desirable. This post is already getting long though.

I'll also save design/implementation details for another time. My day job beckons...