| Printable Version
.Net Remoting - Part4 A Custom Proxy
By Narendra Maharaj
|
|
| RealProxy And TransparentProxy |
|
The .Net Remoting Framework uses two proxy objects to acomplish its work of making a
remote call from a client object to a remote server object. A RealProxy object
and a TransparentProxy object. The RealProxy object does to work of actually
sending messages to the remote object and receiving response messages from the remote object.
The TransparentProxy interacts with the client and does to work of intercepting the remote
method call made by the client.
|
| A Custom Proxy |
|
Why bother with a custom proxy? Just as with COM, the main reason is efficiency of marshaling.
The standard .Net proxy can be replace with a custom proxy by inheriting from the RealProxy
class. Load balancing can also be done in our custom RealProxy class, in addition, and more
importantly, a custom proxy allows us access to the messages passed to and from remote objects.
|
// The Interface
using System;
namespace CustProxyLib
{
public abstract class BaseRemoteObject : MarshalByRefObject
{
public abstract void setValue(int pValue);
public abstract int getValue();
public abstract string DoWork(string s);
}
}
|
|
This is a typical interface we have seen before implemented by a SAO.
|
// The Server
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using CustProxyLib;
namespace CustProxyServer
{
class CustProxyServer : BaseRemoteObject
{
int mValue;
public override void setValue(int pValue)
{
Console.WriteLine("CustProxyServer.setValue old={0} new={1}", mValue, pValue);
mValue = pValue;
}
public override int getValue()
{
Console.WriteLine("CustProxyServer.getValue val={0}", mValue);
return mValue;
}
public override string DoWork(string s)
{
Console.WriteLine("DoWork method invoked on server");
return "Remote greetings from - " + s;
}
public CustProxyServer()
{
Console.WriteLine("CustProxyServer.ctor - remote singleton created");
}
} // class CustProxyServer
class CustProxyServerStarter
{
[STAThread]
static void Main(string[] args)
{
TcpChannel chnl = new TcpChannel(1200);
ChannelServices.RegisterChannel(chnl);
RemotingConfiguration.RegisterWellKnownServiceType(
typeof(CustProxyServer), "CustProxyServer.tcp", WellKnownObjectMode.Singleton );
Console.WriteLine("CustProxyServer Remote Server On Port 1200 (using Tcp Protocol)...");
Console.WriteLine("Press to exit");
Console.ReadLine();
}
}
}
|
|
A simple server with a Singleton remote object, that implements our interface.
|
// The Client
using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Proxies;
using System.Runtime.Remoting.Messaging;
using CustProxyLib;
namespace CustProxyClient
{
public class CustomProxy : RealProxy
{
string _url;
string _uri;
IMessageSink _sinkChain;
public CustomProxy(Type type, string url)
: base(type)
{
_url = url;
IChannel[] registeredChannels = ChannelServices.RegisteredChannels;
foreach(IChannel chnl in registeredChannels)
{
if (chnl is IChannelSender)
{
IChannelSender chnlSender = (IChannelSender) chnl;
_sinkChain = chnlSender.CreateMessageSink(_url, null, out _uri);
if (_sinkChain != null)
break;
}
} // foreach
if (_sinkChain == null)
{
throw new Exception("No channel has been found for " + _url);
}
} // ctor
public override IMessage Invoke(IMessage msg)
{
IDictionary d = msg.Properties;
d["__Uri"] = _url;
// process the request mesage here
IMessage retMsg = _sinkChain.SyncProcessMessage(msg);
// process the return message here
return retMsg;
}
} // class CustomProxy
class CustProxyClient
{
[STAThread]
static void Main(string[] args)
{
TcpChannel chnl = new TcpChannel();
ChannelServices.RegisterChannel(chnl);
CustomProxy prx = new CustomProxy(typeof(BaseRemoteObject),
"Tcp://localhost:1200/CustProxyServer.tcp");
BaseRemoteObject brObj = (BaseRemoteObject) prx.GetTransparentProxy();
brObj.setValue(411);
Console.WriteLine("Custom Proxy Remote Server: {0} value:{1}",
brObj.DoWork("Narendra"), brObj.getValue());
}
}
}
|
|
The client is where we see a difference from the previous examples. We begin by
inheriting from RealProxy. The constructor first calls the base RealProxy which uses
the type to generate a TransparentProxy object. Then all of the registered
channels are checked and the first one that parses the url properly returns an
IMessageSink object. Next we override the RealProxy's Invoke(IMessage msg)
method and use our newly created message sink to forward the message to the remote object.
In the overidden Invoke() method we have complete access to both the request messages and
the response messages. The msg parameter is what we send to the remote object and
the return value of our call to IMessage retMsg = _sinkChain.SyncProcessMessage(msg);
is the message returned by the remote object. Below is a diagram of the server and client.
|
|
| Summary |
|
We have seen quite a few things: There are two ways to marshal a remote object. MarshalByValue
and MarshalByRefObject. There are three types of remote objects, SAO - Singleton,
SAO - SingleCall and CAO. A remote object's lifetime is completed determined by the host
application domain's lease manager object. The .Net Remoting Framework is extensible as we have
senn the the above example. However there is a lot more to this topic than what is covered here,
it is my hope that this elementary exposition will provide you with an entry to more moderate
and advanced topics.
|
| Appendix - COM |
|
Recall the classic C++ definition of the IUnknown interface:
|
class IUnknown
{
virtual HRESULT QueryInterface (REFIID riid, void** pv) = 0;
virtual ULONG AddRef() = 0;
virtual ULONG Release() = 0;
};
|
|
The programmer was responsible for object lifetime management with its attendant network
overhead. This was accomplished by reference counting using AddRef() and Release(). It
is replaced by the decoupling of remote SAO and CAO objects from the client, and left to the
Garbage Collector.
|
|