BuiltWithNOF
SSDN Overview

SSDN: Simple Service Discovery for .NET

Spiro Michaylov:   spiro AT michaylov DOT com

This is an open source (MIT-style license) effort to develop a simple service discovery mechanism  (alternately, a simple naming and directory service) for .NET remoting.

Latest release (recommended): Version 0.6.0 (zip file).

Old releases:


SSDN is a C#.NET framework for discovery of services implemented as .NET remote  objects. It consists of a Windows Service that implements a service registry,  which itself is a .NET remote object. Server processes running on a number of  machines create instances of remote objects and bind them to names in the registry server. (Only server-activated, published objects are supported.) Client processes then look up the registered remote objects by name, by type or by user-defined attributes, and can then make remote calls on them. The lookup operations all take place on the registry server, so that only the entries matching the  lookup criteria are transported to the client. The various clients, publishing servers and the SSDN registry may all be on different hosts.

This document assumes that the reader has basic familiarity with .NET remoting and how to use it from C#.

The distribution

SSDN is available as a downloadable zip containing sources (for the framework, a sample, and some nunit tests), libraries and executables.

Running the Registry Service

The registry server, SSDRegSv.exe,  is a Windows service.

To install it, you need to run the command

installutil SSDRegSv.exe

and to uninstall it you need to run

installutil /u SSDRegSv.exe

The service, listed in the services MMC as “SSDN Registry”, is set to start automatically. Diagnostic messages are sent to the “Application” event log, which you can look at using the Windows Event Viewer.

When the service starts up it reads the port entry from the configuration file SSDRegSv.exe.config to determine the port on which it should publish the registry -- by default this is 8086. To change the port you need to edit this configuration file and restart the service.

The SSDN registry is then registered with .NET remoting services under the URL tcp://host-name:port/SSD -- so, if you run the server on a host called fred using the default port (8086), it will be registered as tcp://fred:8086/SSD,  and so on. You will need this information later.

Implementing and publishing a service object

All published objects must extend the .NET framework class System.MarshalByRefObject.  However, since another process that looks up the objects will want to call methods on them, some appropriate class or interface that the object extends or implements  will have to be made widely available. For obvious abstraction reasons it shouldn't be the actual implementation class of the object.

We will discuss how to publish a remote service object by following through  the sample included with the distribution.

The sample provided is divided into three packages as follows:


SSD.Samples.Publisher
       Contains the published object implementation class, and the server wrapper.
SSD.Samples.Client
       Contains the code that looks up the published object in the registry and then calls the object.
SSD.Samples.Common
       Contains the code that is needed by both the published object and the client:  in this case two interfaces that the published object implements -- IStore and ISum.  They need to be available to the client so that the client can call the methods  from them.

In the sample provided, the class SSD.Samples.Publisher.SummingStoreServer is the remote service object we wish to publish. Hence, it extends System.MarshalByRefObject.  It also implements two interfaces: SSD.Samples.Common.IStore and SSD.Samples.Common.ISum.  These two interfaces will be used by clients access the object. The remote service is a store in which we can place named numbers that we can lookup by name. It also allows us to get the sum of all the stored numbers at any point in time.

The class SSD.Samples.Publisher.PublisherMain is used to create one instance of the SummingStoreServer and register it with the SSD registry.

First, the usual .NET remoting setup is needed to access the SSD registry server:


TcpChannel chan = new TcpChannel(port);
ChannelServices.RegisterChannel(chan);
IRegistry registry =
  (IRegistry)Activator.GetObject(
       typeof(IRegistry), URL);    

Where SSD.Common.IRegistry is the common interface to the service registry, port is some unused TCP port number, and the URL is a command line argument. If you ran the registry server (above) without passing any arguments and you're on the same host then the URL is tcp://localhost:8086/SSD.

Next the published object is created:

SummingStoreServer sss = new SummingStoreServer();
 

Finally the object is bound in the registry:

registry.bind(new Entry("summingStore", "Store and sum values", sss)); 

 And then the object is available for use by other processes -- we then go into  an endless loop to make sure the object remains available.. (We've omitted some details of exception handling for clarity: to see them, look in the source code).

Note: to build this part of the sample, we need:

  • the SSD.Samples.Common package to be available in an assembly: let's call it SSD.Samples.Common.dll
  • to build the entire SSD.Samples.Publisher package into an executable: doing so depends on SSD.Samples.Common.dll (above) and SSD.Common.dll (from the SSDN framework).

Looking up service objects

To call a remote object that we've published in the SSDN registry, we need to connect to the registry and then we have a number of choices for querying the registry to get the object we want.

In the distributed sample, we see an example of this in SSD.Samples.Client.ConsoleClient.

To connect to the registry:

TcpChannel chan = new TcpChannel();
ChannelServices.RegisterChannel(chan);
IRegistry registry = (IRegistry)Activator.GetObject(
  typeof(IRegistry), URL);
 

Where URL needs to be the same as above. We could then simply lookup the service by its name:

Entry e = registry.lookup("summingStore"); 

Which will return the corresponding entry object, if found, or null otherwise.

Alternately, we could get an array of everything in the registry, and use whatever way we wish to find our entry:

Entry[] entries = registry.entries();

 But this would be wasteful as it would result in a lot of network traffic and still leave us with a lot of work to do.

In the sample, we instead look it up by type:

Entry[] entries = 
  registry.entries(new String[] {"SSD.Samples.Common.IStore",
                                 "SSD.Samples.Common.ISum"});

This gives us all the entries for which the published object implements all  both of the specified interfaces. Of course, it just turns out that the one  object we've published implements both of them, so its entry is returned (ie:  an array with one element is returned).

Finally, we need to get at the remote object itself: in the sample we do it  twice because we want to access the object both as an IStore and as an ISum.

IStore storeView = (IStore) entries[0].Obj;
ISum sumView = (ISum) entries[0].Obj;

 We can make calls like:

storeView.put("bar", 7);

Note: to build this part of the sample, we need build the entire SSD.Samples.Client package into an executable: doing so depends on SSD.Samples.Common.dll (above) and SSD.Common.dll (from the SSDN framework).

Using attributes

Sometimes you may want to look up remote objects by criteria other than name or type. You can associate single-valued attributes with an entry -- an attribute can have any String name and any String value, but each name can be used once for each entry.

For example:

SummingStoreServer sss = new SummingStoreServer()
Entry entry = new Entry(name, "Store and sum values", sss);
entry.addAttribute("container", "Hashtable");
entry.addAttribute("MTsafe", "true");
registry.bind(entry);

Here we have declared that our summing store implementation has two attributes: “container” with value “Hashtable” and “MTsafe” with value “true”. Then when we look up a remote object we can impose requirements on its attribute values, using any number of attribute filters. An attribute filter is a requirement that a specific attribute be present and have one of the possible values.

For example, to find an implementation of IStore and ISum that is also declared to be MT safe, we can use the following:

String[] typeStrings = 
  new String[] {"SSD.Samples.Common.IStore",
               "SSD.Samples.Common.ISum"};
AttributeFilter[] attributeFilters =
  new AttributeFilter[] { new AttributeFilter("MTsafe", "true") };
Entry[] entries =
  registry.entries(typeStrings, attributeFilters);

When you use an of attribute filters to lookup an object, each filter is applied to the set of objects returned. A filter may be either a single value as shown above, or an array of values: in which case at least one of the filter values must match.

For example, if we wanted to find a summing store whose container was either a “Hashtable” or a “Linkedlist”, we could use:

AttributeFilte containerFilter = 
  new AttributeFilter("container",
                     new String[] {"Hashtable", "Linkedlist") };
AttributeFilter[] attributeFilters =
  new AttributeFilter[] { containerFilter };

A note about class and interface availability

It's worth noting why we can't do the following lookup, though:

//wrong: probably don't want to do this
SummingStoreServer sss = (SummingStoreServer) entries[0].Obj

 The class SSD.Samples.Publisher.SummingStoreServer is not part of the SSD.Samples.Common.dll assembly, and so is not available to the client.

Building the software

The necessary executables and library are supplied, both for the SSDN framework  and for the sample described here.

Framework:


      
SSDRegSv.exe
                       The SSDN registry server -- takes optional port as argument.
      
SSD.Common.dll
                       Core SSDN framework assembly used by everything else
      
SSD.Server.Registry.dll
                       The registry as a library, for testing
      
SSD.Tests.dll
                       The nunit tests (Set up for nunit 2.0.2 -- see http://www.nunit.org)
      
SSD.Samples.Common.dll
                       Common sample components needed by both publisher and client
      
Publisher.exe
                       The sample publisher -- takes optional registry server URL and port (for its own use) as argument
      
ConsoleClient.exe
                       The sample client -- takes optional registry server URL as argument.

Samples:

If you wish to build from sources use nant in the root directory of the source tree -- where the default.build file lives. This build was tested with nant 0.7.9.0 -- you can obtain nant at http://nant.sourceforge.net.

Bugs and limitations

Published objects become unavailable after their leases expire, but they're  not removed from the SSDN registry.

 

[Home] [Spiro] [Projects] [Software Notes] [Book Reviews] [Jo] [Chester] [Cleo]