Remoting Internals

In this section, we look at the internals of remoting. You can skip this section and revisit it later if you wish to.

Recall from the earlier section that when an object is passed from the original context or AppDomain to the client's context or AppDomain, the runtime transparently creates a proxy object in the client's context and returns to the client a reference to the proxy. When the client makes method calls on the proxy, the runtime takes care of packaging the data as a message and sending it over the transport channel to the server.

For the sake of our discussion in this section, an object refers to either a context-bound object or a context-agile object that will be remoted to a different AppDomain. Such an object always requires marshaling.

The overall structure of how the message flows from the client to the server is shown in Figure 6.9.

Figure 6.9. Remote method call flow.


Don't get daunted by the complexity of Figure 6.9. By the time you are done with this section, you won't have any problems drawing this figure out and impressing your officemates.

As messages form the basis of the flow, let's examine them first.

Messages

When a method call is made on the proxy, the runtime packages the necessary data such as name of the method, method arguments, and so on, into a message. On return, the runtime packages the return values into a message. A message is simply composed of properties, each of which is uniquely identified by a name.

A message is represented by a standard interface, IMessage. This interface is further classified as IMethodCallMessage (to represent the method call) and IMethodReturnMessage (to represent the return value).

Here is the definition of interface IMessage:

interface IMessage {
     IDictionary Properties {get; }
}

The property Properties returns a dictionary that represents a collection of properties. Some basic message properties are listed in Table 6.1.

Table 6.1. Basic Message Properties
NameDescription
__MethodNameName of the remote method being called
__MethodSignatureParameter types and return value type of the method
__TypeNameComplete type name of the remote object
__ArgsActual arguments being passed to the method
__CallContextThe logical call context of the method
__ReturnHolds the return value of the method

Project Utilities on the companion Web site defines a method MyRemoteUtils.DumpMessage that you may find useful. This method can be used to dump the contents of a message to the console.

Message Sinks

One of the goals of the .NET remoting architecture was to provide an extensible mechanism so that developers can intercept various stages of the message pipeline and monitor or massage the message according to their needs. This interception capability is provided in the form of an interface IMessageSink. You create your message sink class that implements IMessageSink and insert it into the pipeline. More than one message sink can be inserted into the pipeline as a singly linked chain.

Here is the definition of the IMessageSink interface:

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

The NextSink method returns the handle to the next sink in the chain. A typical implementation of a message sink stores this handle as a member field.

When a synchronous method call is made on the proxy, the runtime calls SyncProcessMessage on the first message sink in the chain. This gives the sink a chance to inspect or modify the message. A typical implementation would eventually forward the message to the next sink in the chain. However, a sink may choose to manufacture a return value and short-circuit the method call.

When an asynchronous method call is made on the proxy, the runtime calls AsyncProcessMessage on the first message sink in the chain. Parameter replySink is used to post the reply of the message. The return value, IMessageCtrl, provides a way to cancel the asynchronous call that is in progress. A typical implementation just forwards the incoming message to the next sink in the chain.

Here is a vanilla implementation of IMessageSink. The only extra functionality this code provides is to display the synchronous method call message and the return value to the console:

// File UtilitiesMyUtils.cs

[Serializable]
public class MyMessageSink : IMessageSink {
     private IMessageSink m_nextSink;

     public MyMessageSink(IMessageSink nextSink) {
       m_nextSink = nextSink;
     }

     public IMessageSink NextSink { get { return m_nextSink; } }

     public IMessage SyncProcessMessage(IMessage msg) {
       MyRemoteUtils.DumpMessage(msg);
       IMessage retmsg = m_nextSink.SyncProcessMessage(msg);
       MyRemoteUtils.DumpMessage(retmsg);
       return retmsg;
     }

     public IMessageCtrl AsyncProcessMessage(
       IMessage msg, IMessageSink replySink) {
       return m_nextSink.AsyncProcessMessage(msg, replySink);
     }
}

Note the [Serializable] attribute on the message sink. This ensures that the sink runs in the context it is intended for, even if it initially gets created in a different context.

Always Use [Serializable] on Message Sinks

Without this attribute on the message sink, the sink methods may execute in the wrong context.


As we go through the rest of this section, we will discover the stages in the pipeline where an IMessageSink-based sink can be used.

Contexts

A .NET object lives in a context, which stores objects that have context attributes compatible with it.

A context is represented by a class Context. Here are some useful methods available in this class:

public class Context {
     public virtual int ContextID {get;}
     public virtual IContextProperty[] ContextProperties {get;}
     public virtual IContextProperty GetProperty(string name);
     ...
}

The property ContextID returns an identifier for the context that is unique within the context's AppDomain.

A context stores one or more properties. A context property is represented by an interface, IContextProperty, and is uniquely identified by a name within the context. A context property can be accessed by its unique name by calling GetProperty on the context. All the context properties can be obtained by calling ContextProperties.

A context also stores zero or more message sinks. In this case, the message sinks are referred to as the context sinks.

When a context-bound class is instantiated, the runtime queries each of its context attributes for compatibility with the current context. If any of the attributes are not compatible, the runtime creates a compatible context and houses the object in the new context.

Context Attributes

Context attributes serve three purposes:

  1. They inform the runtime if a context-bound object is compatible with the specified context.

  2. They contribute properties to the new context. These properties can be programmatically accessed at runtime from within a type's methods.

  3. They contribute zero or more message sinks.

A context attribute is defined by a class that inherits from the class System.Attribute and implements an interface IContextAttribute (namespace System.Runtime.Remoting.Contexts).

Let's develop a simple context attribute that contributes the name of the developer as a context property. Here is an example of its usage:

[Developer("Jay")]
public class Foo : ContextBoundObject {
     ...
}

The .NET Framework provides a class, ContextAttribute, that simplifies developing context attributes. The following code excerpt shows an implementation that inherits from this class:

// Project ContextProperties/DeveloperAttribute

[AttributeUsage(AttributeTargets.Class)]
public class DeveloperAttribute : ContextAttribute {
     private String m_Name;
     public DeveloperAttribute(String name):base("MyDevAttr") {
       m_Name = name;
     }
     public override void GetPropertiesForNewContext(
         IConstructionCallMessage ctor) {
       DeveloperProperty dev = new DeveloperProperty(m_Name);
       ctor.ContextProperties.Add(dev);
     }

     public override bool IsContextOK(Context ctx,
          IConstructionCallMessage ctorMsg) {
       DeveloperProperty prop =
          ctx.GetProperty(DeveloperProperty.KEY) as
            DeveloperProperty;
       if (null == prop) {
         return false;
       }
       bool contextOK = (this.m_Name == prop.Developer);
       return contextOK;
     }
}

Here is what happens when an instance of class Foo is instantiated. The runtime instantiates DeveloperAttribute and calls IsContextOK to check if the caller's context is compatible with the attribute. If the method returns false, the runtime obtains the context property by calling GetPropertiesForNewContext, creates a new context, and places the property into the new context.

The logic in IsContextOK ensures that no two developers can share the same context. Don't try this at work. Creating a context for each developer may not be a good idea for real applications.

Note that the context attribute object can add as many properties as desired by calling ContextProperties.Add repeatedly.

The context attribute object is not needed again until a new context-bound object is instantiated.

Let's now examine our context property class, DeveloperProperty.

Context Properties

A context property is defined by a class that implements IContextProperty. Here is its definition and description:

interface IContextProperty {
     // The runtime uses the property name to
     // uniquely identify the property
     public String Name { get; }

     // The runtime creates a new context and calls this
     // method to check if the new context is compatible
     public bool IsNewContextOK(Context ctx);
     // The runtime calls this to indicate that no more
     // properties can be added to the context
     public void Freeze(Context ctx);
}

Here is some of the relevant code from our implementation:

// Project ContextProperties/DeveloperAttribute

[Serializable]
public class DeveloperProperty : IContextProperty {
     public static readonly String KEY = "Developer";
     private String m_Name; // Store the developer's name

     public String Name {
       get {return KEY;} // return the identifier
     }

     public String Developer {
       get {return m_Name; }
     }

     public bool IsNewContextOK(Context ctx) {
       // We will force a new context for each developer.
       DeveloperProperty prop =
         ctx.GetProperty(KEY) as DeveloperProperty;
       if (null == prop) {
         return false;
       }
       bool contextOK = (Developer == prop.Developer);
       return contextOK;
     }
     ...
}

The code is self-explanatory. Just note that a property class must be marked as [Serializable]. The importance of this will become evident when we discuss context sinks.

We are finished creating the context attribute and the context property. It is now time for us to apply it on a class. The following code demonstrates how to apply DeveloperAttribute on a context-bound object and how the name of the developer can be obtained within the context during runtime.

// Project ContextProperties/Foo

[Developer("Pradeep")]
public class Foo : ContextBoundObject {

     public int Add(int x, int y) {
       DeveloperProperty prop =
       Thread.CurrentContext.GetProperty(DeveloperProperty.KEY)
            as DeveloperProperty;
       throw new Exception("Contact " + prop.Developer);
       return (x+y);
     }
}

Straightforward, isn't it?

It is interesting to note that if you apply more than one Developer attribute on the same class, the runtime picks just one attribute and simply ignores the rest.

Context Sinks

Context sinks provide an interception point in the communication. They allow you to hook the messages as they enter and leave contexts. There are four kinds of context sinks, each of which serves a different purpose. A server context sink is used to install a context-wide interceptor for calls entering the server context. This sink is represented by the interface IContributeServerContextSink. A client context sink is used to install a context-wide interceptor for calls leaving the server context. This sink is represented by the interface IContributeClientContextSink. An object sink is used to install a per-object interceptor in the server context. This sink is represented by the interface IContributeObjectSink. An envoy sink is used to install a per-object interceptor in the client context. This sink is represented by the interface IContributeEnvoySink.

Note that the envoy sink is the only one that is installed in the client context. All others are meant to be installed in the server context.

From an implementation perspective, all the context sink interfaces are similar. All of these interfaces have just one method of the following type:

IMessageSink GetXxxSink(IMessageSink nextSink);

The method name is different for each interface. For example, IContributeEnvoySink has the method GetEnvoySink. However, all of them essentially return a message sink when queried for.

A context sink interface is implemented on the context property object. Recall that the runtime obtains one or more context property objects from the context attribute by calling GetPropertiesForNewContext. Later, when the first out-of-context call comes in, the runtime checks if the context property object supports any of the context sink interfaces. For each interface that is found, the runtime calls an appropriate GetXxxSink method on the context property object, obtains the message sink, and inserts it in the appropriate sink chain.

Here is a code excerpt that defines an envoy sink on a context property. The complete source can be found on the companion Web site.

// Project EnvoySink/MyTraceSink

[Serializable]
public class MyTraceProperty :
     IContextProperty, IContributeEnvoySink
{
     ...
     public IMessageSink GetEnvoySink(
       MarshalByRefObject mbro, IMessageSink nextSink)
     {
       return new MyMessageSink(nextSink);
     }
}

The first parameter to GetEnvoySink represents the object for which the interceptor is being installed.

Note that the runtime mandates that a context property be marked as [Serializable]. Without this, the remoting layer throws a SerializationException.

Call Contexts

While we are on the subject of contexts, .NET provides another type of context that you should be aware of, the call context. The call context represents the logical thread of the execution code path. Objects can be added to the call context as it travels down and back up the execution code path, and examined by various objects along the path. This makes it possible for either the developers or the runtime to pass extra information about a specific execution code path.

The call context is encapsulated in the CallContext class (namespace System.Runtime.Remoting.Messaging). All the methods in this class are static and operate on the call context of the current thread. The static method SetData can be used to store an object in a named data slot, whereas the static method GetData can be used to retrieve an object by the name of the data slot. Another static method, FreeNamedDataSlot, can be used to empty a named data slot.

It is interesting to note that although any object can be stored in the call context, only those objects that implement a standard interface ILogicalThreadAffinative can propagate to a different AppDomain. Internally, when a call is made on an object from a different AppDomain, the runtime generates an instance of the class LogicalCallContext and sends it along with the remote call. Objects that do not support ILogicalThreadAffinative are not transmitted in LogicalCallContext.

The SDK includes a sample that shows how extra information can be passed in the call context.

Transparent and Real Proxies

There are two proxy objects in the remoting infrastructure that work together to make calls from a client to a remote server. The proxy object that the client object deals with is actually an instance of a runtime class called TransparentProxy. The majority of the work is done by a helper object of type RealProxy. When the client makes a method call, the TransparentProxy checks the call against its type information, bundles the call parameters into a message object, and hands it over to the RealProxy object. The RealProxy object is then responsible for forwarding the message across the configured channel.

For every TransparentProxy object, there is a corresponding RealProxy object. You can obtain the RealProxy object from the TransparentProxy object by calling a static method, RemotingServices.GetRealProxy. Likewise, you can obtain the TransparentProxy from the RealProxy by calling the method GetTransparentProxy on it.

Casting a Transparent Proxy

Consider the following line of code:

Foo f = (Foo) Activator.GetObject(...);

You know that Activator.GetObject eventually returns a TransparentProxy object to the caller. Here is what happens when you cast the TransparentProxy object to the type Foo.

A RealProxy object implements a standard interface, IRemotingTypeInfo. When the caller casts the TransparentProxy object to a specific type, the TransparentProxy object calls the IRemotingTypeInfo.CanCastTo method on the RealProxy object to check if the cast is supported. If the method returns true, the cast is accepted.

You can implement CanCastTo on your own RealProxy derived class and support casting the TransparentProxy object to any other unrelated type. The SDK includes an example that demonstrates this.


In general, the proxy object pair is created when:

  1. A client activates an MBR object that is not compatible with the current AppDomain or context.

  2. An MBR object is passed as a method parameter or a return value from one context to another.

Object References

When an MBR object is passed from its context or AppDomain to another context or AppDomain, the runtime needs to transfer the whereabouts of the MBR object to the other context so that remote method calls can be made on the MBR object. The runtime packages the whereabouts of the MBR object in an object of type ObjRef. The ObjRef contains the information that describes the type of the object being marshaled, the URI of the specific object, and information on how to reach the application domain or context where the object lives, such as channel information, and so on.

When the MBR object is marshaled, the runtime creates an ObjRef object representing the MBR object, serializes the ObjRef object, and transfers it to the remote AppDomain, possibly in another process or computer. In the target domain, the information is deserialized and an ObjRef is created. The runtime then proceeds to create a RealProxy object, passing in ObjRef as a parameter. The RealProxy object then creates a TransparentProxy object and returns it to the client.

Things are slightly different for a server-activated object. Although the proxy pair gets created when Activator.GetObject is called, the remote object, and hence its ObjRef, are not created until a method call is made on the object. On the first method call, the remote object is created, its ObjRef is obtained, and it is passed back to the RealProxy in the client's AppDomain.

Custom Proxies

The remoting architecture had a purpose in separating the proxy into a TransparentProxy object and a RealProxy object. Whereas the TransparentProxy is an internal class that cannot be extended, the RealProxy class can be extended. This is useful for many purposes:

  1. To trace the method calls being made on the proxy object.

  2. To perhaps handle some method calls locally, thus saving network round trips.

  3. To support casting a transparent proxy to another type.

  4. To perform load balancing (depending on the load on the servers, routing the call to an appropriate server).

The SDK includes a few samples that show how to extend the RealProxy class to create your own custom proxy. I have also included a sample (Project CustomProxy) on the companion Web site that demonstrates how a custom proxy can be associated with a given context-bound type such that instantiating the type automatically creates the custom proxy for the type. For those interested, this is achieved by means of extending an attribute called ProxyAttribute. This sample is based on a sample posted on www.gotdotnet.com, which, by the way, is a good source of developer information on .NET technology.

Channels

Channels are objects responsible for transporting messages across remoting boundaries. When a method call is made, the client channel is responsible for serializing the message object (using a formatter) into a stream and transporting it. The server channel is responsible for receiving the stream data and transforming it back to a message object that can then be forwarded to the context sink. On return, the process is reversed. The server channel serializes the data (using a formatter) and sends it back to the client channel, where it is transformed back to a message object.

Some commonly used channel classes in .NET are TcpChannel and HttpChannel.

Channels must expose the IChannel interface. Client channels must expose IChannelSender, a derivation of IChannel. Server channels must expose IChannelReceiver, another derivation of IChannel. The TcpChannel and HttpChannel classes implement both these interfaces so that they can be used to send and receive information.

Channels can be registered in two ways, either by calling the familiar ChannelServices.RegisterChannel method or by loading them from the remoting configuration file.

Information in the remoting configuration file can be specified in two ways. You can either declare a channel template, and then reference this channel in your application, or specify all channel information directly in your application. Channel settings must be defined under the configurationsystem.runtime.remotingchannels path.

The global configuration file Machine.Config defines some templates, such as tcp and http, that should be sufficient for most applications. Within your application file, you can refer to these templates using the ref attribute and adding any additional information such as the port number, and so on. An example is shown next. Here, the channel template referred to is tcp and additional information is the port number 8085:

<configuration>
  <system.runtime.remoting>
    <application name="MyRemotingHost">
      <channels>
        <channel port="8085" ref="tcp" />
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

A channel is uniquely identified by a name within the AppDomain in which it is registered. The default name for TcpChannel is tcp and for HttpChannel is http. If you wish to register more than one instance of the same type of channel, you need to provide a different name for the channel during registration. Both classes take an overloaded constructor that takes a parameter of type IDictionary. You can create a dictionary object, set the name, port, and any other information (e.g., proxy server to use for HTTP requests) and pass this object to the constructor. This is illustrated in the following code excerpt:

// Project Channels/MultipleChannels

public static void Main() {
     IDictionary prop = new Hashtable();
     prop["name"] = "MySecondChannel";
     prop["port"] = "8087";
     HttpChannel channel2 = new HttpChannel(prop, null, null);
     ChannelServices.RegisterChannel(channel2);
     ...
}

The project also shows how to enumerate through the registered channels in an application.

Formatter Sinks

Formatter sinks serialize the message object into a message stream and vice versa. Some standard formatter sinks are BinaryClientFormatterSink and SoapClientFormatterSink on the client side and BinaryServerFormatterSink and SoapServerFormatterSink on the client side. The binary formatter sinks internally use BinaryFormatter and the SOAP formatter sinks internally use SoapFormatter.

A client formatter sink is typically the first sink in the client channel, although, as we will see shortly, it is possible to insert additional message sinks before the formatter sink. Likewise, a server formatter sink is typically the first sink in the server channel.

The function of a formatter is to generate the necessary headers and serialize the message object to the stream. After the formatter, the message object is still forwarded to each of the sinks in the sink chain. However, as the message has already been serialized, changing the message does not change the information in the stream.

By default, the TCP channel is set to use the binary formatter and the HTTP channel is set to use the SOAP formatter. However, you can change this behavior for your application by associating a formatter with a specific channel. The simplest way to do this is to reference the formatter templates that are defined in Machine.Config. The following configuration, for example, associates a binary formatter with the HTTP channel:

<application name="MyRemotingHost">
  <channels>
    <channel ref="http" port="8090" name="MyBinaryHttp">
      <clientProviders>
        <formatter ref="binary" />
      </clientProviders>
      <serverProviders>
        <formatter ref="binary" />
      </serverProviders>
     </channel>
  </channels>
</application>

A formatter can also be associated with a channel programmatically at runtime. The overloaded constructors for TcpChannel and HttpChannel take additional parameters just for this purpose.

Although the standard formatter sinks provided should suffice for most applications, it is also possible to provide your own formatter sink. Look into the SDK documentation for IClientFormatterSinkProvider and IServerFormatterSinkProvider.

Channel Sinks

After transforming the message object to the message stream, the formatter forwards the message object and the stream object to the first channel sink in the channel.

A channel has at least one channel sink, the transport sink. The transport sink is the last sink in the client sink chain and the first sink in the server sink chain. These sinks are built into the channel and cannot be extended.

It is possible to create one or more custom channel sinks and insert them into the channel sink chain, which is useful if you wish to inspect or modify the message stream.

To create a client channel sink, you need to implement the interface IClientChannelSink; to create a server channel sink, you need to implement IServerChannelSink.

In addition to implementing a channel sink, you also need to implement a provider for the channel sink. When creating the sink chain, the runtime queries each channel sink provider to obtain the respective channel sink object.

A client channel sink provider implements IClientChannelSinkProvider and a server channel sink provider implements IServerSinkProvider.

Let's create a client channel sink provider. However, we will not create a full-fledged client channel sink. The system provides a way to create a message sink as a client channel sink. This message sink can then be placed before the formatter sink in the channel.

Here is the partial code for the client channel sink provider. The complete source can be found on the companion Web site:

// Project MessageSink/MyMessageSink

public class MyClientSinkProvider: IClientChannelSinkProvider {
     private IClientChannelSinkProvider
       m_nextChnlSinkProvider = null;

     public MyClientSinkProvider () { }

     public MyClientSinkProvider(
       IDictionary properties, ICollection providerData) { }

     public IClientChannelSink CreateSink(
       IChannelSender channel,
       string url, object remoteChannelData) {
       return new MyMsgSink(
          m_nextChnlSinkProvider.CreateSink(
            channel,url,remoteChannelData));
     }

     public IClientChannelSinkProvider Next {
       get { return m_nextChnlSinkProvider; }
       set { m_nextChnlSinkProvider = value; }
     }
}

The channel sink providers themselves form a chain. The runtime sets the Next property on each provider to define the next provider.

The runtime then calls CreateSink on the first provider in the chain, passing in some channel-specific information. This method is responsible for creating its sink as well as the next sink, and chaining them together. The method then returns its sink.

Here is a code excerpt for MyMsgSink:

// Project MessageSink/MyMessageSink

public class MyMsgSink :
       BaseChannelObjectWithProperties,
       IMessageSink, IClientChannelSink
{
     private IMessageSink m_nextMsgSink = null;
     private IClientChannelSink m_nextChnlSink = null;

     public MyMsgSink(object nextSink) {
       m_nextMsgSink = nextSink as IMessageSink;
       m_nextChnlSink = nextSink as IClientChannelSink;
     }

     // IMessageSink methods (already familiar)
     public IMessage SyncProcessMessage (...) {...}
     public IMessageCtrl AsyncProcessMessage(...) {...}
     public IMessageSink NextSink { get {...} }

     // IClientChannelSink Methods
     public IClientChannelSink NextChannelSink {
     get { return m_nextChnlSink; }
     }

     // These methods just throw an exception
     public void AsyncProcessRequest(...) {...}
     public void AsyncProcessResponse(...) {...}
     public Stream GetRequestStream(...) {...}
     public void ProcessMessage(...) {...}
}

Our client channel sink implements IClientChannelSink and IMessageSink. Of course, any of the IClientChannelSink methods are not to be used, so their implementation simply throws an exception.

MyMsgSink is derived from BaseChannelObjectWithProperties. This standard class provides a base implementation of a channel object.

Now the only thing left to do is to place the provider information in the application configuration file. This is done using the <provider> tag, as shown here:

<channels>
  <channel ref="http">
    <clientProviders>
      <provider type="MyClientSinkProvider, MyMessageSink" />
      <formatter ref="soap" />
    </clientProviders>
  </channel>
</channels>

To make a client channel sink act as a message sink, the key is to place the provider information before the formatter information.

Intraprocess Communication

Have you been wondering what channel is used for cross-context or cross-AppDomain communication within the same process? Clearly, using a TCP channel or an HTTP channel in this scenario may be overkill.

Your guess is right. For intraprocess communication, the runtime uses a combination of the private classes CrossContextChannel or CrossAppDomainChannel, as appropriate. Both classes can be found in MSCorLib.dll under the namespace System.Runtime.Remoting.Channels.


The Complete Picture

In this section, we covered a number of advanced topics. Let's put all them together and see if Figure 6.9 makes sense now.

When an MBR object is marshaled from its AppDomain or context to another AppDomain or context:

  1. The runtime creates a RealProxy object corresponding to the MBR object.

  2. The RealProxy object in turn creates a TransparentProxy object.

  3. The TransparentProxy object returns it to the caller.

When a client makes a synchronous method call on the proxy, the TransparentProxy object reads the parameters from the stack, builds an IMessage object, and hands it over to the RealProxy object.

The RealProxy object forwards the message to the first context envoy sink in the envoy sink chain. The envoy sink in turn forwards it to the next envoy sink in the chain. The last sink in the chain forwards the message to the message sink chain in the channel.

The last sink in the message sink chain is the formatter sink. This sink converts the message object to a message stream and forwards it to the first item in the client channel sink chain. The original message object is also passed along, but at this point it can only be inspected.

The last sink in the client channel sink is the transport sink. This sink takes the message stream, adds the necessary headers, and then writes this stream out to the wire.

On the server side, the server transport sink reads requests off the wire and passes the request stream to the server channel sink chain. The server formatter sink at the end of this chain deserializes the request into a message object and forwards it to the server context sinks.

The last sink in the complete chain is the stack builder sink. This sink takes the message object, recreates the stack, and invokes the actual method on the MBR object.

On return, the return value is converted to a message object and then to a message stream in the server channel and is sent to the client channel. The client channel reconstructs the message stream and forwards it to the client channel sink. Eventually, the return value comes back to the client object.

A similar, more complex, process takes place for asynchronous method calls. The extra complexity arises from the fact that a sink has to keep track of the response message.

At this point, it is worth mentioning an optimization technique that the .NET remoting architecture provides. If a method is not expected to return any value to the caller, it can be attributed with OneWayAttribute (namespace System.Runtime.Remoting.Messaging), as illustrated here:

// Project OneWayMethod/Demo

public class Foo : MarshalByRefObject {

     [OneWay]
     public void DoSomething() {
       ...
     }
}

When a method is marked as one way, the client channel does not have to wait for a response from the server. Therefore, when a client makes a call on a one-way method, the call returns immediately to the caller. The server, however, continues to execute the call.

Ideally, a method marked with OneWayAttribute must have a void return value and must take only [in] type method parameters. However, the .NET remoting mechanism lets you annotate any method with this attribute. This includes methods that have a non-void return value or [out] type parameters. The server-side StackBuilder simply discards the return values. If the executing method throws any exception, this too is caught by the StackBuilder and simply discarded.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.107.149