Friday, January 11, 2008

Revving .NET Remoting

For the first remoting app we build, everything we do will be explicit in the code. No config files or IIS setup required. The first class to look at is the Logger, itself:

namespace DotNetDan.Samples.Remoting.Log
{
using System;

public class Logger : MarshalByRefObject
{
public void Log(String aString)
{
Console.WriteLine(aString);
}
}
}

Note that the only thing we had to do to make the class remotable was implement MarshalByRefObject.

OK, let's move our attention to the server that will listen for log requests:

namespace DotNetDan.Samples.Remoting
{
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
using DotNetDan.Samples.Remoting.Log;

public class Server01
{
public static void Main()
{
ChannelServices.RegisterChannel(new HttpChannel(8000));
ChannelServices.RegisterChannel(new TcpChannel(8001));

RemotingConfiguration.RegisterWellKnownServiceType(
typeof(Logger), "Log", WellKnownObjectMode.Singleton);

Console.WriteLine("Log Server Listening on endpoints:\r\n" +
"\thttp://localhost:8000/Log\r\n" +
"\ttcp://localhost:8001/Log");
Console.WriteLine("Press enter to stop the server...");
Console.ReadLine();
}
}
}

Don't you love code where the using statements take up as much space as the procedural logic?
The first thing we do is register some "Channels". Channels are used by the .NET Remoting Framework to transport messages to and from objects. The client will send details relating to the method call through the channel to the remote server object. It is at the server's discretion as to what types of channels to support. In the code above we chose to register two channels to support both the TCP and HTTP transport mechanisms. These listen on ports 8000 and 8001 respectively. The HTTP channel uses the SOAP protocol; the TCP channel uses a binary protocol.

After registering the channels we register the object that will be actually listening to the server end of the channel with the RegisterWellKnownServiceType method. The parameters should be fairly self-explanatory. For a more detailed discussion refer to the online help.

When the server is now run it will wait for the enter key to be pressed. Whilst the programming is running (ie. until someone presses the enter key) it's ready to receive method calls made on the Log object.

So, let's look at the client code that will make the actual Log method calls:

namespace DotNetDan.Samples.Remoting
{
using System;
using System.Runtime.Remoting;
using DotNetDan.Samples.Remoting.Log;

public class Client01
{
public static void Main(string[] args)
{
Logger httpLogger =
(Logger)Activator.GetObject(
typeof(Logger), "http://localhost:8000/Log");
Logger tcpLogger =
(Logger)Activator.GetObject(
typeof(Logger), "tcp://localhost:8001/Log");

httpLogger.Log("HTTP Client Request");
tcpLogger.Log("TCP Client Request");
}
}
}
Since we assume the server is already running, we create the Log objects using the Server Activation Model, Activator.GetObject.

To build these examples use the following build script:

csc /t:library Log.cs
csc /r:System.Runtime.Remoting.dll /r:System.dll /r:Log.dll Server01.cs
csc /r:System.Runtime.Remoting.dll /r:system.dll ...
/r:Server01.exe /r:Log.dll Client01.cs

Using Configuration Files
In the previous example we made everything explicit so we could see what was going on. We can now make use of configuration files to make the client and server code more transparent; the code will have the appearance of everything running within the one application domain.

Let's create a server configuration file that supports both our TCP and HTTP channels:


The server code now becomes simpler, as all the imperative remoting-configuration code has been removed and replaced by a single call that points to the configuration file:

namespace DotNetDan.Samples.Remoting
{
using System;
using System.Runtime.Remoting;
using DotNetDan.Samples.Remoting.Log;

public class Server02
{
public static void Main()
{
RemotingConfiguration.Configure("server.config");

Console.WriteLine("Log Server Listening on:\r\n" +
"\thttp://localhost:8000/Log");
Console.WriteLine("Press enter to stop the server...");
Console.ReadLine();
}
}
}

Let's now look at the configuration file that supports the client:






url="http://localhost:8000/Log" />






Using this configuration file on the client allows us to write the code that has all the appearances of local instantiation. Due to the client configuration file, though, the Log object is actually invoked remotely over an HTTP channel:

namespace DotNetDan.Samples.Remoting
{
using System.Runtime.Remoting;
using DotNetDan.Samples.Remoting.Log;

public class Client02
{
public static void Main(string[] args)
{
RemotingConfiguration.Configure("client.config");

new Logger().Log("HTTP Client Request");
}
}
}

No comments: