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.
|