Crossing Application Boundaries
Earlier in this chapter, we mentioned that the divisions between application domains and contexts form .NET Remoting boundaries. The .NET Remoting infrastructure largely consists of facilities that handle the details of enabling objects to interact across these boundaries. Having defined some basic concepts in the previous sections, we can look at the overall sequence of events that occurs when a client of a remote object activates the object and then calls a method on that object.
Marshaling Remote Object References via an ObjRef
We mentioned earlier that objects in one .NET Remoting subdivision can t directly access instances of marshal-by-reference types in another .NET Remoting subdivision. So how does .NET Remoting enable objects to communicate across .NET Remoting boundaries? In simple terms, the client uses a proxy object to interact with the remote object by using some means of interprocess communication. We ll look at proxies in more detail shortly, but before we do, we ll discuss how the .NET Remoting infrastructure marshals a reference to a marshal-by-reference object from one .NET Remoting subdivision to another.
There are at least three cases in which a reference to a marshal-by-reference object might need to cross a .NET Remoting boundary:
Passing the marshal-by-reference object in a function argument
Returning the marshal-by-reference object from a function
Creating a client-activated marshal-by-reference object
In these cases, the .NET Remoting infrastructure employs the services of the System.Runtime.Remoting.ObjRef type. Marshaling is the process of transferring an object reference from one .NET Remoting subdivision to another. To marshal a reference to a marshal-by-reference type from one .NET Remoting subdivision to another, the .NET Remoting infrastructure performs the following tasks:
Creates an ObjRef instance that fully describes the type of the marshal-by-reference object
Serializes the ObjRef into a bit stream
Transfers the serialized ObjRef to the target .NET Remoting subdivision
After receiving the serialized ObjRef, the Remoting infrastructure operating in the target .NET Remoting subdivision performs the following tasks:
Deserializes the serialized ObjRef representation into an ObjRef instance
Unmarshals the ObjRef instance into a proxy object instance that the client can use to access the remote object
To achieve the functionality just described, the ObjRef type is serializable and encapsulates several vital pieces of information necessary for the .NET Remoting infrastructure to instantiate a proxy object in the client application domain.
When the .NET Remoting infrastructure activates an instance of a marshal-by-reference object within an application, it assigns it a Uniform Resource Identifier that the client uses in all subsequent requests on that object reference. For server-activated types, the Uniform Resource Identifier corresponds to the published well-known endpoint configured by the host application. For client-activated types, the .NET Remoting infrastructure generates a Globally Unique Identifier (GUID) for the URI and maps it to the remote object instance.
Metadata is the DNA of .NET. No, we re not talking about Distributed Network Applications; we re talking about the basic building blocks of the common language runtime. The ObjRef contains type information, or metadata, that describes the marshal-by-reference type. The type information consists of the marshal-by-reference object s fully qualified type name; the name of the assembly containing the type s implementation; and the assembly version, culture, and public key token information. The .NET Remoting infrastructure also serializes this type information for each type in the derivation hierarchy, along with any interfaces that the marshal-by-reference type implements, but the infrastructure doesn t serialize the type s implementation.
We can draw a subtle yet important conclusion from the type information conveyed in the ObjRef instance: because the ObjRef conveys information that describes a type s containing assembly and derivation hierarchy but fails to convey the type s implementation, the receiving application domain must have access to the assembly defining the type s implementation. This requirement has many implications for how you deploy your remote object, which we ll examine in Chapter 3.
Along with the URI and type information, the ObjRef carries information that informs the receiving .NET Remoting subdivision how it can access the remote object. .NET Remoting uses channels to convey the serialized ObjRef instance, as well as other information, across .NET Remoting boundaries. We ll examine channels shortly, but for now, it s enough to know that the ObjRef conveys two sets of channel information:
Information identifying the context, application domain, and process containing the object being marshaled
Information identifying the transport type (for example, HTTP), IP address, and port to which requests should be addressed
Clients Communicate with Remote Objects via Proxies
As we mentioned earlier, after the ObjRef arrives in the client .NET Remoting subdivision, the .NET Remoting infrastructure deserializes it into an ObjRef instance and unmarshals the ObjRef instance into a proxy object. The client uses the proxy object to interact with the remote object represented by the ObjRef. We ll discuss proxies in detail in Chapter 5, Messages and Proxies. For now, we want to limit this discussion to the conceptual aspects of proxies to help you better understand their role in .NET Remoting.
Figure 2-8 shows the relationship between a client object and the two types of proxies: transparent and real. The .NET Remoting infrastructure utilizes these two proxy types to achieve seamless interaction between the client and the remote object.
Figure 2-8. The .NET Remoting infrastructure utilizes two kinds of proxies to enable clients to interact with the remote object: transparent and real.
The transparent proxy is the one that the client directly accesses. When the .NET Remoting infrastructure unmarshals an ObjRef into a proxy, it generates on the fly a TransparentProxy instance that has an interface identical to the interface of the remote object. The client has no idea it s interacting with anything other than the actual remote object s type. The .NET Remoting infrastructure defines and implements TransparentProxy internally as the System.Runtime.Remoting.Proxies.__TransparentProxy type.
When a client makes a method call on the transparent proxy, the proxy simply converts the method call into a message object, which we ll discuss shortly. The transparent proxy then forwards the message to the second proxy type, RealProxy.
The real proxy is the workhorse that takes the message created by the transparent proxy and sends it to the .NET Remoting infrastructure for eventual delivery to the remote object.
The System.Runtime.Remoting.Proxies.RealProxy type is an abstract class; therefore, you can t create instances of it directly. This class is the base class for all proxy types that plug into the .NET Remoting infrastructure. In fact, the .NET Remoting infrastructure defines a RemotingProxy class that extends RealProxy. The infrastructure uses the RemotingProxy class to handle the role of RealProxy, but you can derive your own custom proxy type from RealProxy and use it in place of the one provided by the runtime. We ll demonstrate how to define and use a custom proxy in Chapter 5.
Messages Form the Basis of Remoting
Let s briefly digress from .NET Remoting to consider what happens when we make a method call in a nonremote object-oriented environment. Logically speaking, when you make a method call on an object, you re signaling the object to perform some function. In a way, you re sending the object a message composed of values passed as arguments to that method. The address of the method s entry point is the destination address for the message. At a very low level, the caller pushes the method arguments onto the stack, along with the address to which execution should return when the method completes. Then the caller calls the method by setting the application s instruction pointer to the method s entry point. Because the caller and the method agree on a calling convention, the method knows how to obtain its arguments from the stack in the correct order. In reality, the stack assumes the role of a communications transport layer between method calls, conveying function arguments and return results between the caller and the callee.
Encapsulating the information about the method call in a message object abstracts and models the method-call-as-message concept in an object-oriented way. The message object conveys the method name, arguments, and other information about the method call from the caller to the callee. .NET Remoting uses such a scheme to enable distributed objects to interact with one another. Message objects encapsulate all method calls, input arguments, constructor calls, method return values, output arguments, exceptions, and so on.
.NET Remoting message object types implement the System.Run time.Remoting.Messages.IMessage interface and are serializable. IMessage defines a single property member of type IDictionary named Properties. The dictionary holds named properties and values that describe various aspects of the called method. The dictionary typically contains information such as the URI of the remote object, the name of the method to invoke, and any method parameters. The .NET Remoting infrastructure serializes the values in the dictionary when it transfers the message across a .NET Remoting boundary. The .NET Remoting infrastructure derives several kinds of message types from IMessage. We ll look at these types and messages in more detail in Chapter 5, Messages and Proxies.
Remember that only instances of serializable types can cross .NET Remoting boundaries. Keep in mind that the .NET Remoting infrastructure will serialize the message object to transfer it across the .NET Remoting boundary. This means that any object placed in the message object s Properties dictionary must be serializable if you want it to flow across the .NET Remoting boundary with the message.
Channels Transport Messages Across Remoting Boundaries
.NET Remoting transports serialized message objects across .NET Remoting boundaries through channels. Channel objects on either side of the boundary provide a highly extensible communications transport mechanism that potentially can support a wide variety of protocols and wire formats. The .NET Remoting infrastructure provides two types of channels you can use to provide a transport mechanism for your distributed applications: TCP and HTTP. If these channels are inadequate for your transport requirements, you can create your own transport and plug it into the .NET Remoting infrastructure. We ll look at customizing and plugging into the channel architecture in Chapter 7, Channels and Channel Sinks.
For maximum efficiency, the .NET Remoting infrastructure provides a socket-based transport that utilizes the TCP protocol for transporting the serialized message stream across .NET Remoting boundaries. The TcpChannel type defined in the System.Runtime.Remoting.Channels.Tcp namespace implements the IChannel, IChannelReceiver, and IChannelSender interfaces. This means that TcpChannel supports both sending and receiving data across .NET Remoting boundaries. The TcpChannel type serializes message objects by using a binary wire format by default. The following code snippet configures an application domain with an instance of the TcpChannel type that listens for incoming connections on port 2000:
using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Tcp; TcpChannel c = new TcpChannel( 2000 ); ChannelServices.Register(c);
For maximum interoperability, the .NET Remoting infrastructure provides a transport that utilizes the HTTP protocol for transporting the serialized message stream across the Internet and through firewalls. The HttpChannel type defined in the System.Runtime.Remoting.Channels.Http namespace implements the HTTP transport functionality. Like the TcpChannel type, HttpChannel can send and receive data across .NET Remoting boundaries. The HttpChannel type serializes message objects by using a SOAP wire format by default. The following code snippet configures an application domain with an instance of the HttpChannel type that listens for incoming connections on port 80:
using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; HttpChannel c = new HttpChannel( 80 ); ChannelServices.Register(c);
Channel Sink Chains Can Act on Messages
The .NET Remoting architecture is highly flexible because it possesses a clear separation of object responsibilities. The channel architecture provides flexibility by employing a series of channel sink objects linked together into a sink chain. Each channel sink in the chain has a clearly defined role in the processing of the message. In general, each channel sink performs the following tasks:
Accepts the message and a stream from the previous sink in the chain
Performs some action based on the message or stream
Passes the message and stream to the next sink in the chain
At a minimum, channels transport the serialized messages across .NET Remoting boundaries by using two channel sink objects. Figure 2-9 shows the client-side channel architecture.
Figure 2-9. Client-side channel architecture
In Figure 2-9, the client object makes calls on a transparent proxy, which in turn converts the method call into a message object and passes that object to the RealProxy actually a RemotingProxy derived from RealProxy. The RemotingProxy passes the message object to a set of specialized sink chains within the context (not shown in Figure 2-9), which we ll discuss in detail in Chapter 6, Message Sinks and Contexts. The message object makes its way through the context sink chains until it reaches the first sink in the channel s sink chain: a formatter sink, which is responsible for serializing the message object to a byte stream by using a particular wire format. The formatter sink then passes the stream to the next sink in the chain for further processing. The last sink in the channel sink chain is responsible for transporting the stream over the wire by using a specific transport protocol.
Formatter Sinks Serialize Message Objects to a Stream
.NET Remoting provides two types of formatter sinks for serializing messages: BinaryFormatter and SoapFormatter. The type you choose largely depends on the type of network environment connecting your distributed objects. Because of the pluggable nature of the .NET Remoting architecture, you can create your own formatter sinks and plug them into the .NET Remoting infrastructure. This flexibility enables the infrastructure to support a potentially wide variety of wire formats. We ll look at creating a custom formatter in Chapter 8, Formatters. For now, let s take a quick look at what .NET Remoting provides out of the box.
For network transports that allow you to send and receive binary data (such as TCP/IP), you can use the BinaryFormatter type defined in the System.Runtime.Serialization.Formatters.Binary namespace. As its name suggests, BinaryFormatter serializes message objects to a stream in a binary format. This can be the most efficient and compact way of representing a message object for transport over the wire.
Some network transports don t allow you to send and receive binary data. These transports force applications to convert all binary data into an ASCII text representation before sending it over the wire. In such situations or for maximum interoperability, .NET Remoting provides the SoapFormatter type in the System.Runtime.Serialization.Formatters.Soap namespace. SoapFormatter serializes messages to a stream by using a SOAP representation of the message. We ll discuss SOAP in more detail in Chapter 4, SOAP and Message Flows.
Transport Sinks Interface with the Wire
The transport sink knows how to transfer data between itself and its counterpart across the .NET Remoting boundary by using a specific transport protocol. For example, HttpChannel uses a transport sink capable of sending and receiving HTTP requests and responses to transport the serialized message stream data from one .NET Remoting subdivision to another.
A transport sink terminates the client-side channel sink chain. When this sink receives the message stream, it first writes transport protocol header information to the wire and then copies the message stream to the wire, which transports the stream across the .NET Remoting boundary to the server-side .NET Remoting subdivision.
Figure 2-10 shows the server-side channel architecture. As you can see, it s largely the same as the client-side channel architecture.
Figure 2-10. Server-side channel architecture
In Figure 2-10, the first sink on the server-side channel sink chain that the serialized message stream encounters is a transport sink that reads the transport protocol headers and the serialized message data from the stream. After pulling this data off the wire, the transport sink passes this information to the next sink in the server-side sink chain. Sinks in the chain perform their processing and pass the resulting message stream and headers up the channel sink chain until they reach the formatter sink. The formatter sink deserializes the message stream and headers into an IMessage object and passes the message object to the .NET Remoting infrastructure s StackBuilderSink, which actually makes the method call on the remote object. When the method call returns, the StackBuilderSink packages the return result and any output arguments into a message object of type System.Runtime.Remoting.Messaging.ReturnMessage, which the StackBuilderSink then passes back down the sink chain for eventual delivery to the proxy in the caller s .NET Remoting subdivision.