Finally, after so many
definitions and abstractions, it's time to put it all to use and see how
to build a server, a host app domain to host the server object, and a
client application to consume the remote object. Both the host and the
client application need to indicate to .NET how they intend to use
remoting. The host needs to register with .NET the channels and formats
on which it's willing to accept remote calls, the remote types it's
willing to export, their activation modes, and their URIs (if
applicable). The client application needs to register with .NET the
types it wants to access remotely, and any channels to be used for
callbacks. There are two ways to achieve all this: programmatically and
administratively. If you use programmatic configuration, you gain
maximum flexibility because at any time you can change the activation
modes, object locations, and channels used. Both the client and the host
can use programmatic configuration. If you use administrative
configuration, you save your remoting settings in a configuration file.
Both the client and the server can use administrative configuration. You
can also mix and match (i.e., have some settings in the configuration
files and programmatically configure others), although normally you use
one or the other of these methods, not both at the same time.
Administrative configuration lets you change the settings and affect the
way your distributed application behaves, even after deployment; it's
the preferred way to handle remoting in most cases. This section
demonstrates both techniques using the same sample application. I will
explain programmatic configuration first and, armed with the
understanding of the basic steps, will then examine the administrative
configuration settings.
1. Programmatic Channel Registration
A remoting channel is any component that implements the IChannel interface, defined in the System.Runtime.Remoting.Channels
namespace. You rarely need to interact with a channel object
directly—all you have to do is register them. Out of the box, .NET
provides three implementations of the IChannel interface: the TcpChannel, HttpChannel, and IpcChannel classes, defined in the System.Runtime.Remoting.Channels.Tcp, System.Runtime.Remoting.Channels.Http, and System.Runtime.Remoting.Channels.Ipc namespaces, respectively. The host application needs to register which channels it wishes to use, using the static method RegisterChannel( ) of the ChannelServices class:
public sealed class ChannelServices
{
public static void RegisterChannel(IChannel channel);
//Other methods
}
Typically, the host will put the channel registration code in its Main( )
method, but it can register the channels anywhere else, as long as the
registration takes place before remote calls are issued. Note that you
can register the same channel type only once per app domain, unless you
explicitly assign it a different name, as described later.
1.1. Host channel registration
The host must register
at least one channel if it wants to export objects. To register a TCP
or HTTP channel, the host first creates a new channel object, providing
as a construction parameter the port number associated with this
channel. Next, the host registers the new channel. For example:
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
//Registering TCP channel
IChannel channel = new TcpChannel(8005);
ChannelServices.RegisterChannel(channel);
When a new remote call
is accepted, the channel grabs a thread from the thread pool and lets
it execute the call, while the channel continues to monitor the port.
This way, .NET can serve incoming calls as soon as they come off the
channel. Note that the number of concurrent calls .NET remoting can
service is subject to the thread-pool limitation. Once the pool is
exhausted, new requests are queued until requests in progress are
complete.
To register an IPC channel, the host provides the IpcChannel constructor with the pipe's name:
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
//Registering IPC channel
IChannel ipcChannel = new IpcChannel("MyHost");
ChannelServices.RegisterChannel(ipcChannel);
The named pipe is a global
resource on the host machine, and therefore it must be unique
machine-wide. No other host on the same machine can open a named pipe
with the same name; doing so will yield a RemotingException.
The host can register multiple channels, like so:
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Ipc;
//Registering TCP channel
IChannel tcpChannel = new TcpChannel(8005);
ChannelServices.RegisterChannel(tcpChannel);
//Registering http channel
IChannel httpChannel = new HttpChannel(8006);
ChannelServices.RegisterChannel (httpChannel);
//Registering IPC channel
IChannel ipcChannel = new IpcChannel("MyHost");
ChannelServices.RegisterChannel(ipcChannel);
When the host
instantiates a channel, .NET creates a background thread to open a
socket and listen to activation requests on the port. As a result, you
can run blocking operations after creating and registering a channel,
because you won't affect the thread monitoring the channel. For example,
this is valid host-side registration code:
static void Main( )
{
//Registering TCP channel
IChannel channel = new TcpChannel(8005);
ChannelServices.RegisterChannel(channel);
Thread.Sleep(Timeout.Infinite);
}
An inherent limitation
of network programming is that on a given machine you can open a given
port only once. Consequently, you can't open multiple channels on the
same port on a given machine. For example, you can't register channels
this way:
//You can't register multiple channels on the same port
IChannel tcpChannel = new TcpChannel(8005);
ChannelServices.RegisterChannel(tcpChannel);
IChannel httpChannel = new HttpChannel(8005);
ChannelServices.RegisterChannel(httpChannel);
Registering multiple
channels targeting the same port number causes an exception to be thrown
at runtime. In addition, you can register a channel type only once,
even if you use different ports:
//You can only register a channel once, so this will not work:
IChannel tcpChannel1 = new TcpChannel(8005);
ChannelServices.RegisterChannel(tcpChannel1);
IChannel tcpChannel2 = new TcpChannel(8007);
ChannelServices.RegisterChannel(tcpChannel2);//Throws RemotingException
The same is true for the IPC channel—the host can register only a single IPC channel per app domain.
When the host application
shuts down, .NET automatically frees the port (or the named pipe) so
other hosts on the machine can use it. However, it's customary that as
soon as you no longer need the channels, you unregister them explicitly
using the static method UnregisterChannel( ) of the ChannelServices class:
IChannel channel = new TcpChannel(8005);
ChannelServices.RegisterChannel(channel);
/* Accept remote calls here */
//When done—unregister channel(s):
ChannelServices.UnregisterChannel(channel);
1.2. Channels and formats
The default constructors shown so far automatically select the appropriate default formatter. By default, the TcpChannel and IpcChannel classes use the binary formatter to format messages between the client and the host. The HttpChannel class uses the SOAP formatter by default. However, as stated previously, you can combine any channel with any format. The channel classes provide the following constructors:
public class TcpChannel : <base types>
{
public TcpChannel(IDictionary properties,
IClientChannelSinkProvider clientSinkProvider,
IServerChannelSinkProvider serverSinkProvider);
/* Other constructors and methods */
}
public class HttpChannel : <base types>
{
public HttpChannel(IDictionary properties,
IClientChannelSinkProvider clientSinkProvider,
IServerChannelSinkProvider serverSinkProvider);
/* Other constructors and methods */
}
public class IpcChannel : <base types>
{
public IpcChannel(IDictionary properties,
IClientChannelSinkProvider clientSinkProvider,
IServerChannelSinkProvider serverSinkProvider);
/* Other constructors and methods */
}
These constructors
accept a collection of key/value pairs and two sink interfaces. The
collection is a dictionary of predetermined channel-configuration
properties, such as the new channel's name and the port number. The two
sink interfaces are where you can provide a formatter instead of
accepting the default. The clientSinkProvider parameter registers a channel on the client's side (when client-side registration takes place for callbacks); the serverSinkProvider parameter registers a channel on the host's side. The available formatters for the host are SoapServerFormatterSinkProvider and BinaryServerFormatterSinkProvider, implementing the IServerChannelSinkProvider interface. The available formatters are SoapClientFormatterSinkProvider and BinaryClientFormatterSinkProvider, implementing the IClientChannelSinkProvider
interface. The details of these interfaces and format-providing classes
are immaterial; the important thing is that you can use one to
explicitly force a message format.
Refer to the MSDN Library for more information on the configuration parameters and the way they affect the channels. |
|
Here is how to register a SOAP formatter using a TCP channel on the host side:
IServerChannelSinkProvider formatter;
formatter = new SoapServerFormatterSinkProvider( );
IDictionary channelProperties = new Hashtable( );
channelProperties["name"] = "MyServerTCPChannel";
channelProperties["port"] = 8005;
IChannel channel = new TcpChannel(channelProperties,null,formatter);
ChannelServices.RegisterChannel(channel);
Note that the
second construction parameter is ignored. When doing the same on the
client side, you need not provide a port number, and you provide the
formatter as the second (instead of the third) parameter:
IClientChannelSinkProvider formatter;
formatter = new SoapClientFormatterSinkProvider( );
IDictionary channelProperties = new Hashtable( );
channelProperties["name"] = "MyClientTCPChannel";
IChannel channel = new TcpChannel(channelProperties,formatter,null);
ChannelServices.RegisterChannel(channel);