Sunday, November 30, 2008

 

Remoting without Heartburn

The infrastructure for .NET Remoting is capable of supporting any number of remotable objects, each with any number of remotable functions. For applications implemented on a LAN, the simplest and most efficient approach is a Windows service using one or more TCP channels with binary formatting serviced by one or more remotable objects with infinite life. If each potential client is serviced by its own object, no conflicts need occur, and services will be supplied simultaneously to multiple clients. This approach is not scalable, but it is suitable when there will be only a small number of clients.

Microsoft documentation and several Web logs provide instructions to create services and remotable objects. What they do not clearly explain is that remotable objects need not be registered. When registered the framework may create instances on client demand rather than let the service control objects. Microsoft refers to the approach to remoting described here as "direct remoting" and "dynamic publication" but provides only a single example and a cursory description.

Microsoft explains remotable objects with infinite life only in a Developer's Guide article and not in the class reference for MarshalByRefObject.InitializeLifetimeService. The VS2005 Microsoft documentation is misleading as to specifying paths to configuration files and as to specifying full URIs when launching remotable objects. For whatever reasons, you will not find direct remoting detailed in books that purport to explain .NET Remoting, including Ingo Rammer and Mario Szpuszta, "Advanced .NET Remoting" (2 Ed., 2005, Apress), a circumstance that probably tended to retard adoption of remoting technology.

The configuration file for a Windows service with a "TcpAgent" channel is simple, in the following form:

<~configuration~>
    <~system.runtime.remoting~>
        <~application~>
            <~channels~>
                <~channel name="TcpAgent" ref="tcp" port="9000"
                 useIpAddress="true" bindTo="10.20.40.99" /~>
            <~/channels~>
        <~/application~>
    <~/system.runtime.remoting~>
<~/configuration~>

Using ASP.NET 2.0, the OnStart and Onstop methods for an "Agent" Windows service with a "Remote" class, providing an "AgentRemote" object, are also simple, in the following form:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.ServiceProcess;
static Remote m_oRemote = null;
public partial class Agent : ServiceBase
{
    protected override void OnStart(string[] args)
    {
        try
        {
            AppDomain oAppDomain = AppDomain.CurrentDomain;
            string sBaseDirectory = oAppDomain.BaseDirectory;
            string sConfigurationPath = sBaseDirectory + "Agent.exe.config";
            RemotingConfiguration.Configure(sConfigurationPath, true);
            m_oRemote = new Remote();
            RemotingServices.Marshal(m_oRemote, "AgentRemote", typeof(Remote));
        }
        catch (Exception oException)
        {
        }
    }
    protected override void OnStop()
    {
        if (m_oRemote != null)
            RemotingServices.Disconnect(m_oRemote);
        m_oRemote = null;
    }
}
public class Remote : MarshalByRefObject
{
    public override Object InitializeLifetimeService()
    {
        return null;
    }
    // remotable service functions
}

In a VS2005 Windows service project, add an Application Configuration File item named app.config, and it will be automatically copied to the executable file directory and renamed using the ".exe.config" form shown above. For Windows services it is necessary to provide a full path to the configuration file, done as above when in the same directory as the executable. Returning null from InitializeLifetimeService allows infinite life. Multiple, named remotable objects from the same or different classes can be launched with multiple copies of new and Marshal statements.

Client access to the "AgentRemote" object above is also simple, in the following form:

using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.ServiceProcess;
class AgentRemoteClient
{
    void ClientFunction()
    {
        TcpChannel oTcpChannel = new TcpChannel();
        ChannelServices.RegisterChannel(oTcpChannel, true);
        Remote oRemote = (Remote)Activator.GetObject(
         typeof(Remote), "tcp://10.20.40.99:9000/AgentRemote");
        if (oRemote != null)
        {   // invoke methods on oRemote
        }
        else
        {   // log exception
        }
    }
}

The client needs a reference to the server executable and a  using  statement specifying the server namespace. Both client and server need references to System.Runtime.Remoting.dll, usually found in C:\Windows\Microsoft.NET\Framework\v2.0.50727.

A Microsoft remoting configuration does not provide syntax for specifying multiple, named object instances launched via Marshal, but it is simple to provide an auxiliary text file that designates an object type and name for each, thus obtaining the benefits of configuration files without undesirable complications. The OnStart and OnStop methods and the remotable object functions can be elaborated with a variety of logging techniques to create a record of operations and exceptions.

The Microsoft framework does not provide a way to associate remotable objects launched by Marshal with channels. That could be achieved with a Windows service for each channel. Otherwise, as a Developer's Guide article says, remotable objects can share but cannot own channels. Contrary to what Microsoft documentation implies in places, adding network addresses and ports to object names in the second Marshal argument will not work.

To configure multiple channels and multiple remotable objects dynamically, the Microsoft configuration file can be replaced with a text file specifying channels to be registered with ChannelServices.RegisterChannel and remotable objects to be launched with RemotingServices.Marshal. Channels created using the TcpServerChannel class can be associated with IP adddresses, port numbers, formatters and other sink objects. With that approach, custom sinks can be added to provide interpretation, compression, encryption and other services.

[Note: because of display limitations, characters "< " and " >" here are shown with a tilde ~ after or before them.]

Saturday, November 22, 2008

 

Remotely Possible

The .NET Remoting services introduced with ASP.NET made interprocess communication much less burdensome for the C# (and C++) developer. Visual Basic developers long had the benefit of COM automation, but C++ developers were stuck with the tedious tasks of writing proxies and marshalling code. So .NET Remoting is life in the fast lane.

However, the Microsoft examples of .NET Remoting and their emulations in most Web logs are only half debuggable (on the client side). With Microsoft's approach, the work on the server side is done in an object encapsulated in a DLL, not accessible to the debugger running a server (actually a server launcher). Beneficiaries of welcome automation must resort to archaic, file-writing techniques to debug a server-side process.

The solution is actually simple. Just insert the remote object code in the server code, and don't put it in a separate project. In the client code, create a reference to the .exe file for the server. The Server code now looks like the following example, using a console application as a test vehicle for a TCP channel server:

using System;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
namespace Server
{
    public class ServerFunctions : MarshalByRefObject
    {
        public int ServerFunction1( . . . .
        public bool ServerFunction2( . . . .
        . . . .
    }
 
    public class ServerLauncher
    {
        static void Main(string[] args)
        {
            try
            {
                TcpServerChannel oTcpChannel = new TcpServerChannel(9000);
                ChannelServices.RegisterChannel(oTcpChannel, true);
                RemotingConfiguration.RegisterWellKnownServiceType
                (
                    typeof(ServerFunctions), "RemoteService",
                    WellKnownObjectMode.Singleton
                );
                Console.WriteLine("Type Enter to exit");
                System.Console.ReadLine();
            }
            catch (Exception oException)
            {
                Console.WriteLine("Error: " + oException.Message);
                Console.ReadLine();
            }
        }
    }
}

Be sure to add a reference to System.Runtime.Remoting.dll in a .NET Remoting project, when using ASP.NET 2.0 typically found in C:\Windows\Microsoft.NET\Framework\v2.0.50727. With code structured this way running under the Visual Studio debugger, functions in the ServerFunctions class can be debugged as usual. Corresponding client programs will acquire copies of the entire server .exe instead of just a .dll; that is the difference.

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]