Looking at the Five Elements of Remoting

The .Net Remoting architecture, as you can see in Figure 7-1, is based on five core types of objects:

  • Proxies: These objects masquerade as remote objects and forward calls.

  • Messages: Message objects contain the necessary data to execute a remote method call.

  • Message sinks: These objects allow custom processing of messages during are mote invocation.

  • Formatters: These objects are message sinks as well and will serialize a message to a transfer format like SOAP.

  • Transport channels: Message sinks yet again, these objects will transfer the serialized message to a remote process, for example, via HTTP.

click to expand
Figure 7-1: Simplified version of the .NET Remoting architecture

A Bit About Proxies

Instead of dealing with "real" object references (memory references, pointers, and so on), when using remote objects, the client application can only perform methods on object proxies. These proxies masquerade, and therefore provide the same interface, as the target object. Instead of executing any method on their own, the proxies forward each method call to the .NET Remoting Framework as a Message object.

This message then passes through the sinks shown previously, until it is finally handled by the server, where it passes though another set of message sinks until the call is placed on the "real" destination object. The server then creates a return message that will be passed back to the proxy. The proxy handles the return message and converts it to the eventual out/ref parameters and the method's return value, which will be passed back to the client application.

Creating Proxies

When using the new operator or calling Activator.GetObject() to acquire a reference to a remote object, the .NET Remoting framework generates two proxy objects. The first is an instance of the generic TransparentProxy (from System.Runtime.Remoting.Proxies). This is the object that will be returned from the new operator for a remote object.

Whenever you call a method on the reference to a remote object, you will in reality call it on this TransparentProxy. This proxy holds a reference to a RemotingProxy, which is a descendent of the abstract RealProxy class.

During the creation stage, references to the client-side message sink chains are acquired using the sink providers passed during channel creation (or the default, if no custom providers have been specified). These references are stored in the Identity object contained in the RealProxy.

After using the new operator or calling GetObject(), as shown in the following example, the variable obj will point to the TransparentProxy (see Figure 7-2).

click to expand
Figure 7-2: Proxies with identity

 HttpChannel channel = new HttpChannel(); ChannelServices.RegisterChannel(channel); SomeClass obj = (SomeClass) Activator.GetObject(       type of(SomeClass),       "http://localhost:1234/SomeSAO.soap"); 

Creating Messages

When a call is placed on a remote object reference, the TransparentProxy creates a MessageData object and passes this to the RealProxy's PrivateInvoke() method. The RealProxy in turn generates a new Message object and calls InitFields(), passing the MessageData as a parameter. The Message object will now populate its properties by resolving the pointers inside the MessageData object.

For synchronous calls, the RealProxy places a chain of calls on itself, including Invoke(), InternalInvoke(), and CallProcessMessage(). The last one will look up the contained Identity object's sink chain and call SyncProcessMessage() on the first IMessageSink.

When the processing (including server-side handling) has completed, the call to the this method will return an IMessage object containing the response message. The RealProxy will call its own HandleReturnMessage() method, which checks for out/ref parameters and will call PropagateOutParameters() on the Message object.

You can see a part of this client-side process when using the default HTTP channel, shown in Figure 7-3. If the client would use the TCP channel instead, the channel would consist of a BinaryClientFormatterSink and a TcpClientTransport sink.

click to expand
Figure 7-3: Client-side synchronous message handling (partial)

Returning Values

After this handling of the response, the RealProxy will return from its PrivateInvoke() method, and the IMessage is passed back to the TransparentProxy. Now a lot of "magic" happens behind the scenes: the TransparentProxy will take the return values and out parameters from the Message object and return them to the calling application in the conventional, stack-based method return fashion of the CLR. From the perspective of the client application, it will look as if a normal method call has just returned.

A Bit About the ObjRef Object

When working with CAOs, the client needs the possibility to identify distinct object instances. When passing references to CAOs from one process to another, this identity information has to "travel" with the call. This information is stored in the serializable ObjRef object.

When instantiating a CAO (either by using a custom factory SAO as shown in Chapter 3 or by using the default RemoteActivation.rem SAO that is provided by the framework when the CAO is registered at the server side), a serialized ObjRef will be returned by the server.

This ObjRef is taken as a base to initialize the Identity object, and a reference to it will also be kept by the Identity. The ObjRef stores the unique URL to the CAO. This URL is based on a GUID and looks like the following:


Additionally, the ObjRef object stores the server's base URL, which has also been returned by the server.


This behavior is very different from that of SAOs, where the URL to the server is specified at the client. With CAOs, only the URL needed for the creation of the CAO is known to the client. The real connection endpoint URL for the CAO will be returned when creating the object. This can also mean that a host that's behind a firewall might return its private IP address and will therefore render the CAO unusable. To prevent this behavior, make sure to use either the machineName or bindTo attribute in the channel's configuration section as shown in Chapter 4.

You can check the ObjRef (or any other properties of the proxies) in a sample project simply by setting a breakpoint after the line where you acquire the remote reference of a CAO (for example, SomeObj obj1 = new SomeObj() when using configuration files). You can then open the Locals window and browse to the obj1's properties as show in Figure 7-4.

click to expand
Figure 7-4: Browsing to the ObjRef's properties in the Locals window

You'll find the ObjRef shown in Figure 7-5 at obj1/_TransparentProxy/_rp/_identity/_objRef.

click to expand
Figure 7-5: Locating the ObjRef in the Locals window

Understanding the Role of Messages

A message is basically just a dictionary object hidden behind the IMessage interface. Even though every message is based on this interface, the .NET Framework defines several special types thereof. You'll come across ConstructionCall and MethodCall messages (plus their respective return messages). The main difference between these message types is a predefinition of several entries in the internal dictionary.

While traveling through the chain of sinks, the message passes at least two important points: a formatter and a transport channel. The formatter is a special kind of sink that encodes the internal dictionary into some sort of wire protocol such as SOAP or a binary representation.

The transport channel will transfer a serialized message from one process to another. At the destination, the message's dictionary is restored from the wire protocol by a server-side formatter. After this, it passes through several serverside MessageSinks until it reaches the dispatcher. The dispatcher converts the message into a "real" stack-based method call that will be executed upon the target object. After execution, a return message is generated for most call types (excluding one-way calls) and passed back through the various sinks and channels until it reaches the client-side proxy, where it will be converted to the respective return value or exception.

What's in a Message?

There are several kinds of messages, and each of them is represented by a distinct class, depending on what kind of call it stands for. This object implements the IDictionary interface to provide key/value-based access to its properties.

A partial definition of MethodCall is shown here:

 public class System.Runtime.Remoting.Messaging.MethodCall: {    // only properties are shown    public int ArgCount { virtual get; }    public object[] Args { virtual get; }    public bool HasVarArgs { virtual get; }    public int InArgCount { virtual get; }    public object[] InArgs { virtual get; }    public LogicalCallContext LogicalCallContext { virtual get; }    public MethodBase MethodBase { virtual get; }    public string MethodName { virtual get; }    public object MethodSignature { virtual get; }    public IDictionary Properties { virtual get; }    public string TypeName { virtual get; }    public string Uri { virtual get; set; } } 

These values can be accessed in two ways. The first is by directly referencing the properties from the message object, as in methodname = msg.MethodName. The second way is to access the properties using the IDictionary interface with one of the predefined keys shown in the table that follows.

When doing this, a wrapper object (for example a MCMDictionary for MethodCallMessages) will be generated. This wrapper has a reference to the original message so that it can resolve a call to its dictionary values by providing the data from the underlying Message object's properties. Here you will see the dictionary keys and corresponding properties for a sample method call message:




















General.BaseRemoteObject, General









The second kind of message, used during the instantiation of CAOs, is the ConstructionCall. This object extends MethodCall and provides the following additional properties:

 public class System.Runtime.Remoting.Messaging.ConstructionCall : {    // only properties are shown    public Type ActivationType { virtual get; }    public string ActivationTypeName { virtual get; }    public IActivator Activator { virtual get; virtual set; }    public object[] CallSiteActivationAttributes { virtual get; }    public IList ContextProperties { virtual get; } } 

Examining Message Sinks

The transfer of a message from a client application to a server-side object is done by so-called message sinks. A sink will basically receive a message from another object, apply its own processing, and delegate any additional work to the next sink in a chain.

There are three basic interfaces for message sinks: IMessageSink, IClientChannelSink, and IServerChannelSink. As you can see in the following interface description, IMessageSink defines two methods for processing a message and a property getter for acquiring the reference for the next sink in the chain.

 public interface IMessageSink {     IMessageSink NextSink { get; }     IMessageCtrl AsyncProcessMessage(IMessage msg,                                             IMessageSink replySink);     IMessage SyncProcessMessage(IMessage msg); } 

Whenever an IMessageSink receives a message using either SyncProcessMessage() or AsyncProcessMessage(), it may first check whether it can handle this message. If it's able to do so, it will apply its own processing and afterwards pass the message on to the IMessageSink referenced in its NextSink property.

At some point in the chain, the message will reach a formatter (which is also an IMessageSink) that will serialize the message to a defined format and pass it on to a secondary chain of IClientChannelSink objects.


Formatters implement IClientFormatterSink by convention. This interface is a combination of IMessageSink and IClientChannelSink.

 public interface IClientChannelSink {     // Properties     IClientChannelSink NextChannelSink { get; }     // Methods     void AsyncProcessRequest(IClientChannelSinkStack sinkStack,                                   IMessage msg,                                   ITransportHeaders headers,                                   Stream stream);    void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack,                                   object state,                                   ITransportHeaders headers,                                   Stream stream);    Stream GetRequestStream(IMessage msg,                                ITransportHeaders headers);    void ProcessMessage(IMessage msg,                            ITransportHeaders requestHeaders,                            Stream requestStream,                            ref ITransportHeaders responseHeaders,                            ref Stream responseStream); } 

The main difference between IMessageSink and IClientChannelSink is that the former can access and change the original dictionary, independent of any serialization format, whereas the latter has access to the serialized message as a stream.

After processing the message, the IClientChannelSink also passes it on to the next sink in its chain until it reaches a transport channel like HttpClientTransportSink (which also implements IClientChannelSink) for the default HTTP channel.

Serialization through Formatters

A Message object needs to be serialized into a stream before it can be transferred to a remote process, a task which is performed by a formatter. The .NET Remoting Framework provides you with two default formatters, the SoapFormatter and the BinaryFormatter, which can both be used via HTTP or TCP connections.


In the samples that follow, you get a chance to take a look at the SoapFormatter, but the same information applies to BinaryFormatter (or any custom formatter) as well.

After the message completes the preprocessing stage by passing through the chain of IMessageSink objects, it will reach the formatter via the SyncProcessMessage() method.

On the client side, the SoapClientFormatterSink passes the IMessage on to its SerializeMessage() method. This function sets up the TransportHeaders and asks its NextSink (which will be the respective IClientChannelSink—that is, the HttpClientTransportSink) for the request stream onto which it should write the serialized data. If the request stream is not yet available, it will create a new ChunkedMemoryStream that will later be passed to the channel sink.

The real serialization is started from CoreChannel.SerializeSoapMessage(), which creates a SoapFormatter (from the System.Runtime.Serialization.Formatters.Soap namespace) and calls its Serialize() method.

You can see the SOAP output of the formatter for a sample call to obj.setValue(42) in the following excerpt. Remember that this is only the serialized form of the request—it is not yet transfer dependent (it does not contain any HTTP headers, for example).

 <SOAP-ENV:Envelope  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xmlns:xsd="http://www.w3.org/2001/XMLSchema"  xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"  xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"  SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"  xmlns:i2="http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObj *** ect/General">    <SOAP-ENV:Body>      <i2:setValue >         <newval>42</newval>      </i2:setValue>    </SOAP-ENV:Body> </SOAP-ENV:Envelope> 

Moving Messages through Transport Channels

After the last IClientChannelSink (which can be either the formatter or custom channel sink) has been called, it forwards the message, stream, and headers to the ProcessMessage() method of the associated transfer channel. In addition to the stream generated by the formatter, this function needs an ITransportHeaders object, which has been populated by the formatter as well, as a parameter.

The transport sink's responsibility is to convert these headers into a protocoldependent format—for example, into HTTP headers. It will then open a connection to the server (or check if it's is already open, for TCP channels or HTTP 1.1 keep alive connections) and send the headers and the stream's content over this connection.

Following the previous example, the HTTP headers for the SOAP remoting call will look like this:

 POST /MyRemoteObject.soap HTTP/1.1 User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 5.0.2195.0; MS .NET Remoting; MS .NET CLR 1.0.2914.16 ) SOAPAction: "http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General# setValue" Content-Type: text/xml; charset="utf-8" Content-Length: 510 Expect: 100-continue Connection: Keep-Alive Host: localhost 

This leads to the following complete HTTP request for the setValue(int) method of a sample remote object:

 POST /MyRemoteObject.soap HTTP/1.1 User-Agent: Mozilla/4.0+(compatible; MSIE 6.0; Windows 5.0.2195.0; MS .NET Remoting; MS .NET CLR 1.0.2914.16 ) SOAPAction: "http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General# setValue" Content-Type: text/xml; charset="utf-8" Content-Length: 510 Expect: 100-continue Connection: Keep-Alive Host: localhost <SOAP-ENV:Envelope    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xmlns:xsd="http://www.w3.org/2001/XMLSchema"    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"    xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"    SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"    xmlns:i2=       "http://schemas.microsoft.com/clr/nsassem/General.BaseRemoteObject/General">    <SOAP-ENV:Body>      <i2:setValue >          <newval>42</newval>      </i2:setValue>    </SOAP-ENV:Body> </SOAP-ENV:Envelope> 

I highlighted the important parts in this request so that you can see the values that have been taken from the message object's dictionary.

Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net