The .NET remoting architecture is a modular and extensible architecture. As shown in Figure 1,
the basic building blocks on the client side are proxies, formatters,
and transport channels. On the host side, the building blocks are
transport channels, formatters, and call dispatchers. In addition, .NET
provides a way to uniquely locate and identify remote objects. This
section provides an overview of the remoting architecture's building
blocks and how they interact with each other.
1. Client-Side Processing
The client never
interacts with a remote object directly. Instead, it interacts with a
proxy, which provides the exact same public entry points as the remote
object. It's the proxy's job to allow the client to make a method call
or access a property on it, and then to marshal that call to the actual
object. Every proxy is bound to at most one object, although multiple
proxies can access a single object. The proxy also knows where the
object is. When the client makes a call on the proxy (Step 1 in Figure 10-9),
the proxy takes the parameters to the call (the stack frame), creates a
message object, and asks a formatter object to process the message
(Step 2 in Figure 10-9). The formatter serializes the message object and passes it to a channel object, to transport to the remote object (Step 3 in Figure 10-9).
While all this is happening, the proxy blocks the client, waiting for
the call to return. Once the call returns from the channel, the
formatter deserializes the returned message and returns it to the proxy.
The proxy places the output parameters and the returned value on the
client's call stack (just like the real object does for a direct call),
and finally returns control to the client.
The proxy has actually two parts to it (see Figure 2). The first is called a transparent
proxy. The transparent proxy, implemented by the sealed private class TransparentProxy,
exposes the same entry points (as well as base type and interfaces) as
the actual object. The transparent proxy converts the stack frame to a
message and then passes the message to the real proxy. The real
proxy knows how to connect to the remote object and forward the message to it. The real proxy is a class derived from the RealProxy
abstract class; it has nothing to do with the actual remote object
type. By default, .NET provides a concrete subclass (the internal class RemotingProxy).
The advantage of breaking the proxy into two parts is that it allows
you to use the .NET-provided transparent proxy while providing your own
custom real proxy.
2. Server-Side Processing
Once the server-side channel receives the method, it forwards the message to a formatter (Step 4 in Figure 10-9). The formatter deserializes the message and passes it to the stack builder (Step 5 in Figure 1). The stack builder reads the message and calls the object based on the method and its parameters in the message (Step 6 in Figure 10-9).
The object itself is never aware that a remote client is accessing it,
because as far as it's concerned, the client is the stack builder. Once
the call returns to the stack builder, it returns a reply message to the
server-side formatter. The formatter serializes the message and returns
it to the channel to transport to the client.
3. Formatters
Because the proxy and
the stack builder serialize and deserialize messages, all they need to
do is take advantage of the serialization mechanism.
Out of the box, .NET provides a SOAP formatter and a binary formatter.
The binary formatter requires much less processing time to serialize and
deserialize than the SOAP formatter, so in intense calling patterns the
binary formatter gets better performance. This is because it takes more
time to compose and parse a SOAP message, as opposed to the binary
format, which is practically used as-is. In addition, the message in
binary format has a smaller payload and reduces overall network latency.
However, SOAP is the format of choice for going through a firewall when
HTTP is used
as the transport protocol (although you can use a binary format, too).
If there is no interoperability need, or no firewall is present between
the host and the client, you should use a binary format for performance
reasons.
4. Transport Channels
Once the message (either
from the proxy to the stack builder or vice versa) is serialized, what
protocol transports the message to the other side? Out of the box, .NET
provides three transport protocols for remote calls: TCP, HTTP, and IPC.
These are called transport
channels. TCP and HTTP were available with .NET 1.0, and .NET 2.0 introduced the IPC channel. IPC stands for Inter-Process Communication
and is based on named pipes. The main advantage of IPC is that because
you can use it only for cross-process calls on the same machine, hosts
that expose only IPC channels are inherently more secure than hosts that
expose TCP or HTTP channels.
It's important
to state that the question of what transport protocol to use is
independent of the question of what format is used to serialize the
message. You can use the SOAP or binary formats over either TCP, HTTP,
or IPC. However, if you choose the default transport channel
configuration, when you select TCP or IPC, .NET uses a binary format;
when you select HTTP, .NET uses the SOAP format. This policy makes
sense, because if there is no firewall between the client and the host, a
binary protocol (TCP or IPC) with a binary format yields the best
performance. If a firewall is present, a text-based protocol (HTTP) with
the SOAP format is required to go through the firewall.
Both the host and the client app domains need to indicate to .NET which channels they intend to use. This is called channel
registration.
The host app domain needs to register the channels through which it's
willing to accept calls from remote clients. It can register any one
channel, or all of them. The client needs to register the channels on
which it wishes to accept callbacks (discussed later). The client can
register one channel or multiple channels. If you have access to other
custom channels, you can use them on both sides.
At first glance, .NET
remoting over HTTP using SOAP sounds just like a web service. Although
web services also use HTTP and SOAP, web services and remoting serve
different purposes. Remoting can be used only when both the client and
the server are implemented using .NET. Web services, on the other hand,
are platform-agnostic and can connect any platform to any other. The
trade-off is, of course, in type expressiveness and activation models.
With remoting, you can use any serializable or marshal by
reference-derived class. With web services, you are limited to types
that can be expressed with SOAP and WSDL,
and there is no easy way to pass an object reference. Another
difference is that you use web services over the Internet, whereas you
use remoting when both ends are in the same protected and secure LAN. In
addition, you can use remoting with TCP or other channels, but web
services are usually limited to HTTP. |
In the case of a remote call
across two app domains in the same physical process, .NET uses the same
architecture as with a call across processes or across machines.
However, .NET doesn't use the network-oriented channels or IPC, because
doing so would be a waste of resources and would incur a performance
penalty. Instead, for this case .NET automatically uses a dedicated channel called CrossAppDomainChannel.
This channel is internal to the remoting infrastructure assembly and
isn't available to you. Because both client and server share the same
physical process, the CrossAppDomainChannel channel uses the client's thread to invoke the call on the object, and the thread pool isn't involved.
5. Object Locations and Identity
Every remote object is associated with a uniform resource locator
(URL). The URL provides the location of the remote object, and it must
be mapped to an actual location in which a host app domain is listening
for remote activation requests. The URL has the following structure:
<protocol>://<host identifier>:<port number>
The URL tells .NET where
and how to connect with a remote object; that is, what protocol to use
to transport the call, to what host (which typically means which
machine), and, in the case of TCP and HTTP, on which port of the host
machine to try to connect. For example, here is a possible URL:
tcp://localhost:8005
This URL instructs .NET to connect to a host on the local machine on port 8005, and to use TCP for the transport protocol.
The following URL instructs .NET to use HTTP for the transport protocol and to try to connect to port 8006 on the local machine:
http://localhost:8006
When using IPC, there is no need to specify a port number. All the URL needs to contain is the pipe's name:
ipc://MyHost
A URL can also optionally contain an application name section:
<protocol>://<machine name>:<port number>[/<application name>]
For example:
http://localhost:8006/MyApp
If a client wants to use a
client-activated remote object, the information in the URL is
sufficient for .NET to connect to the remote host, create an object on
the remote machine, and marshal a reference back to the client. As a
result, a URL is all that is required to identify a remote
client-activated object.
The situation is
different for server-activated objects. When the client tries to connect
to a server-activated object, it must provide the server with
additional information identifying which well-known object it wants to
activate. For example, the host could have a number of singleton objects
of the same type, servicing different clients. That additional
identification information is in the form of a uniform resource identifier (URI). The URI is appended to the activation URL, like so:
<URL>/<URI>
Here are a few examples:
tcp://localhost:8005/MyRemoteServer
http://localhost:8006/MyRemoteServer
ipc://MyHost/MyRemoteServer
The URI can be any
string, as long as it's unique in the scope of the host app domain. The
host is responsible for registering with .NET the well-known objects
it's willing to export, and the URIs have to match to those supplied by
the clients. Note that the client supplies the URI, but it's the host
who decides whether the client gets a well-known singleton object
identified by the URI or a single-call object, which is actually not a
well-known instance at all (nonetheless, both server-activation types
are called well-known objects).
Whenever you
marshal a remote object reference across an app domain boundary, the
reference carries with it the location of the object (in the form of a
URL). This is required so that .NET will know where to hook up the
proxy. The URL also enables .NET to correctly resolve object references
when clients pass them around. Imagine a client in App Domain A that has
a proxy referencing an object in App Domain B. When that client passes a
reference to the proxy to another client in App Domain C, the client in
App Domain C gets a reference to the object in App Domain B, and its
proxy will point directly at the object, not at the proxy in App Domain
A.
6. Error Handling
When a client has a direct
reference to an object, exceptions thrown by the object wind their way
up the call stack. The client can then catch the exceptions and handle
them, or let them propagate up the call chain. With remote objects, the
client has a direct reference only to a proxy, and the object is called
on a different stack frame. If a remote object throws an exception, .NET
catches that exception, serializes it, and sends it back to the proxy.
The proxy then re-throws the exception on the client's side. The
resulting programming model, as far as the client is concerned, is very
similar to that of handling errors with local objects in the same app
domain as the client.
Almost every point in the .NET remoting
architecture is extensible, and you can replace core building blocks
with your own or intercept the remote calls in various stages. .NET lets
you provide custom formatters and transport channels as well as your
own implementation of proxies, which allows you to intervene in proxy
creation, marshaling, and object binding. You can provide special hooks
to monitor the system behavior or add security or proprietary logging,
and you can do all that without having the client or the server do
anything different.