Microsoft .NET Remoting (Pro-Developer) - page 36

Summary

Message sinks and contexts are key elements of the .NET Remoting infrastructure. As we saw in this chapter, message sinks allow you to intercept .NET Remoting messages at various points in both the client and server contexts. As we demonstrated, contexts allow the developer to define various services available to all objects executing within them. Examples of such services include message tracing and exception logging. Because the envoy sink chain executes in the client context, it’s the perfect choice for validating method parameters prior to transmission. The .NET Remoting architecture allows for further customization via channels and channel sinks, which are the subject of the next chapter.

Chapter 7

Channels and Channel Sinks

In this chapter, we’ll continue showing you the various customizable features of .NET Remoting. To get a more detailed understanding of the channel architecture, we’ll examine HttpChannel in depth. The next step will be to create a custom channel by using our own transport mechanism. Finally, we’ll build a custom channel sink.

The .NET Framework has two types of channels: HttpChannel and TcpChannel. Although the overall structure of these two channels is very similar, they differ in the transport they use to transmit messages. Although HTTP and TCP will fulfill most transport needs, occasional problems that require a different transport will occur.

For example, you might need to access remote objects from a wireless device that supports the Wireless Application Protocol (WAP). To solve this problem, you’d create a custom channel that accepts incoming messages via WAP. When we look at the structure of channels later in the chapter, you’ll see that once a message is reconstituted into the proper format, the channel framework is oblivious to the manner in which the message was received.

How Channels Are Constructed

To make it easier to understand the overall channel architecture when we create a custom channel, we’ll show you the HTTP channel architecture. We benefit in several ways by discussing the construction of an existing channel. First, we get a more in-depth understanding of how the HTTP channel works. Second, we can introduce concepts at a higher level, making it easier to understand the overall channel architecture when we create a custom channel. We’ll discuss the features of the HTTP channel that are relevant to creating our custom channel. The HTTP channel consists of six main classes that are relevant to us:

  • HttpChannel

  • HttpServerChannel

  • HttpServerTransportSink

  • HttpClientChannel

  • HttpClientTransportSinkProvider

  • HttpClientTransportSink

Channel Terminology

Table 7-1 introduces some channel terminology that will be necessary for understanding channels.

Table 7-1. Channel Terminology

Term

Description

Object URI

An object URI identifies a particular well-known object registered on the server.

Channel URI

A channel URI is a string that specifies the connection information to the server.

Server-activated URL

A server-activated URL is a unique string that the client uses to connect to the correct object on the server. In the URL http://localhost:4000/SomeObjectUri, SomeObjectUri is an object URI and http://localhost:4000 is a channel URI.

Client-activated URL

A client-activated URL is a string that the client uses to connect to the correct object on the server. When using client-activated objects, you don’t need to use a unique URL because the .NET Remoting infrastructure will generate one.

HttpChannel

The HttpChannel does little work. Its purpose is to wrap the functionality that’s implemented in HttpServerChannel and HttpClientChannel into a single interface. Most of the methods in HttpChannel simply call their counterparts in either HttpServerChannel or HttpClientChannel. HttpChannel implements the interfaces System.Runtime.Remoting.Channels.IChannel, System.Run­time.Remoting.Channels.IChannelSender, and System.Runtime.Remoting.Chan­nels.IChannelReceiver. Although IChannel is a required interface for channels, IChannelSender and IChannelReceiver aren’t required in all cases. If you just want a channel that receives messages, you must implement the IChannelReceiver; likewise, if your channel will just send messages, you need to implement only the IChannelSender interface. It’s not necessary to inherit directly from the IChannel interface because both IChannelSender and IChannelReceiver handle this for you. Table 7-2 shows the interface for IChannel.

Table 7-2. Members of System.Runtime.Remoting.Channels.IChannel

Member

Member Type

Description

ChannelName

Property

Returns the name of the channel.

ChannelPriority

Property

Returns the priority of the channel. The default priority is 1.

Parse

Method

Extracts the channel URI and object URI from the URL.

ChannelName will generally be the name of the transport. For example, HttpChannel returns the lowercase string http for its name. The ChannelPriority property allows you to control the order in which channels attempt to connect to the remote object. For instance, if you have two channels registered on the server with different priorities and both channels registered on the client, the remoting infrastructure will select the channel with the higher priority. The ChannelName and ChannelPriority properties are read-only. HttpChannel has three constructors.

public HttpChannel();
public HttpChannel( int );
public HttpChannel( IDictionary, 
                    IClientChannelSinkProvider, 
                    IServerChannelSinkProvider );

The first constructor initializes the channel for both sending and receiving of messages, whereas the second constructor initializes the channel for receiving messages. The parameter for the second constructor sets the port on which the server will listen. The last constructor is a little more interesting. All channels that plan to use configuration files must implement a constructor with this signature. The .NET Remoting runtime throws an exception if this constructor isn’t present. Because of the number of optional parameters that can be set for an HttpChannel, it wouldn’t be practical to provide a constructor for all the combinations. To overcome this problem, channels use the IDictionary parameter to pass configuration information to the constructor. Table 7-3 lists the properties available for the HttpChannel.

Table 7-3. HttpChannel Configuration Properties

Name

Applies To

Description

name

Server and client

Used when you register multiple instances of HttpChannel because each channel must have a unique name.

priority

Server and client

Sets the channel priority.

clientConnectionLimit

Client

Sets the number of clients that can simultaneously be connected to the server. The default value is 2.

proxyName

Client

Allows setting of the proxy computer name.

proxyPort

Client

Allows setting of the proxy port.

port

Server

Sets the listening port.

suppressChannelData

Server

When this property is set, the ChannelData property will return null.

useIpAddress

Server

Specifies whether the channel should use the IP address. If this value is false, the channel will use the machine name retrieved from the static method Dns.GetHostName.

bindTo

Server

Allows you to specify the IP address of a network interface card that the server should bind to. This is particularly useful when you have multiple network interface cards in a machine.

machineName

Server

Allows you to override the machine name.

Channel properties can be set in two ways. The first way is program­matically. In the following snippet, we set the port and assign a new IP address to bindTo:

IDictionary ChannelProperties = new Hashtable();
ChannelProperties ["port"] = 4001;
ChannelProperties ["bindTo"] = "192.168.0.1";
HttpChannel channel = new HttpChannel( props, 
                                       null, 
                                       null );

The second way to set properties is in your configuration file:

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
         
      </service>
      <channels>
        <channel ref="http" port="4001" bindTo="192.268.0.1"/>
      </channels>
    </application>
  </system.runtime.remoting>
</configuration>

The final two parameters of the constructor, IClientChannelSinkProvider and IServerChannelSinkProvider, allow you to specify a different formatter provider. The default formatter providers for HttpChannel are SoapClientFormatterSinkProvider and SoapServerFormatterSinkProvider.

We stated earlier that HttpChannel implements IChannelReceiver. Any channel that plans to receive messages must implement IChannelReceiver. Table 7-4 shows the members of IChannelReceiver. We’ll cover these members in detail momentarily.

Table 7-4. Members of System.Runtime.Remoting.Channels.IChannelReceiver

Member

Member Type

Description

ChannelData

Property

Used to store channel information on an instance basis

GetUrlsForUri

Method

Returns an array of URLs that are used to uniquely identify remote objects

StartListening

Method

Tells the server channel to listen for incoming messages

StopListening

Method

Tells the server to stop listening for incoming messages

As we stated earlier, HttpChannel inherits from IChannelSender. IChannel­Sender provides the interface for client-side interaction with the channel. As you can see in Table 7-5, IChannelSender has only a single member.

Table 7-5. Member of System.Runtime.Remoting.Channels.IChannelSender

Member

Member Type

Description

CreateMessageSink

Method

Returns a channel message sink

HttpServerChannel

HttpServerChannel handles the server-side functionality for receiving messages. During construction, the HttpChannel class creates an instance of HttpServerChannel. As with HttpChannel, HttpServerChannel has a constructor that takes an IDictionary object as its first parameter:

public HttpServerChannel( IDictionary properties, 
                          IServerChannelSinkProvider provider );

HttpChannel removes client-side properties before passing the IDictionary object to HttpServerChannel constructor. At the time of construction, HttpServerChannel performs the following operations:

  • Extracts properties and sets corresponding member variables

  • Initializes channel data

  • Extracts channel data from the server sink providers

  • Creates the server channel sink chain

We’ll address each of these items in detail in the “Creating the Custom Channel FileChannel” section later in the chapter.

As stated earlier, HttpServerChannel handles the receiving of request messages; therefore, it must derive from IChannelReceiver. Let’s take a closer look at the members in HttpServerChannel that implement the IChannelReceiver interface. The ChannelData property returns an object of type ChannelDataStore. The ChannelDataStore object is a private member variable named _channelData. The main purpose of ChannelDataStore is to store channel URIs that map to the channel. The channel URI for the HttpChannel is http://<machine_name>:<port>. The member function GetUrlsForUri uses _channel­Data to help generate its return value. GetUrlsForUri appends the object URI to the channel URI and returns the value in the 0 index of the string array.

The most interesting method in this group is StartListening. The responsibility of StartListening is to start a background thread that listens on a port for incoming messages. StartListening would look similar to the following pseudocode:

public void StartListening( Object data )
{
    ThreadStart ListeningThreadStart =
      new ThreadStart( this.Listen );
    _listenerThread = new Thread( ListeningThreadStart );
    _listenerThread.IsBackground = true;
    _listenerThread.Start();
}

In this snippet, _listenerThread is a member variable of type Thread. The Listen method sits in a loop, waiting for incoming messages. When Listen receives a message, it dispatches the message by calling the method ServiceRequest in the class HttpServerTransportSink. The Listen method then returns to a waiting state.

HttpServerTransportSink

The main responsibility of HttpServerTransportSink is to process request ­messages. HttpServerTransportSink, as with all server-side channel sinks, must derive from the interface System.Runtime.Remoting.Channels.IServerChannel­Sink. Table 7-6 presents the public members of this interface.

Table 7-6. Members of System.Runtime.Remoting.Channels.IServerChannelSink

Member

Member Type

Description

NextChannelSink

Property

Returns a reference to the next channel sink in the chain.

AsyncProcessResponse

Method

Returns the response payload from the process asynchronous message.

GetResponseStream

Method

Builds a Stream object that contains the IMessage object and any needed key/value pairs from the ITransportHeaders object.

ProcessMessage

Method

Used by the sink chain to handle incoming messages. Because the transport sink is the first sink in the chain, it doesn’t need to perform any work.

During construction, HttpServerTransportSink receives a reference to the next sink in the sink chain. HttpServerTransportSink holds the reference in the member variable _nextSink. It’s the responsibility of each sink in the chain to hold a reference to the next sink. Upon message receipt, each sink passes the request message down the sink chain by calling _nextSink.ProcessMessage. The return value for ProcessMessage is an enumeration of type ServerProcessing. Table 7-7 lists the enumeration values for ServerProcessing.

Table 7-7. System.Runtime.Remoting.Channels. ServerProcessing Enumeration Values

Value

Description

Complete

The request message was processed synchronously.

Async

The request message was dispatched asynchronously. Response data must be stored for later dispatching.

OneWay

The request message was dispatched and no response is permitted.

The request message contains two key items, an ITransportHeaders and a Stream. ITransportHeaders is a dictionary that allows key/value pairs of inform­ation to pass between the client and server. The .NET Framework provides a class named System.Runtime.Remoting.Channels.CommonTransportKeys that defines string keys for common data found in an ITransportHeaders object. CommonTransportKeys contains three public string fields:

  • ConnectionId

  • IPAddress

  • RequestUri

The Stream object contains the serialized .NET Remoting message—for example, a ConstructionCallMessage or a MethodCallMessage.

HttpClientChannel

HttpClientChannel handles the sending of request messages to the server. As with HttpServerChannel, HttpChannel creates an instance of HttpClientChannel in its constructor. HttpClientChannel has a multitude of constructors, but the most configurable one is this:

public FileClientChannel( IDictionary properties, 
                          IClientChannelSinkProvider sinkProvider );

This constructor allows the client properties discussed in Table 7-3 to be set in the channel. It also allows you to specify an alternate sink provider. In a moment, we’ll take a closer look at sink providers.

HttpClientChannel has one main purpose, to build an IMessageSink object. CreateMessageSink builds and returns the IMessageSink object. Recall that CreateMessageSink is a member of the IChannelSender interface. In the “Creating Custom Channels” section later in this chapter, we’ll look at the details of CreateMessageSink, but for now it’s sufficient to say that it returns an HttpClientTransportSink. CreateMessageSink doesn’t directly create the new transport sink. It uses the class HttpClientTransportSinkProvider.

HttpClientTransportSinkProvider

The sole responsibility of HttpClientTransportSinkProvider is to create an instance of the HttpClientTransportSink. As with all client sink providers, HttpClientTransportSinkProvider derives from the interface IClientChannelSinkProvider. Table 7-8 shows the members of System.Runtime.Remoting.Channels.IClientChannelSinkProvider.

Table 7-8. Members of System.Runtime.Remoting.Channels. IClientChannelSinkProvider

Member

Member Type

Description

Next

Property

Gets or sets the next IClientChannelSinkProvider in the chain

CreateSink

Method

Creates and returns a new object that derives from the IClient­ChannelSink

The property Next always returns null because HttpClientTransportSinkProvider is the last provider in the chain. CreateSink returns a newly created HttpClientTransportSink.

HttpClientTransportSink

IClientChannelSink is the class that dispatches messages to the server. It implements the interface System.Runtime.Remoting.Channels.IClientChannelSink. Table 7-9 shows the members of this interface.

Table 7-9. Members of System.Runtime.Remoting.Channels.IClientChannelSink

Member

Member Type

Description

NextChannelSink

Property

Holds a reference to the next sink in the chain

AsyncProcessRequest

Method

Handles processing of asynchronous method calls

AsyncProcessResponse

Method

Handles asynchronous method calls that are returning from the server

GetRequestStream

Method

Builds a Stream object that contains the IMessage object and any needed key/value pairs from the ITransportHeaders object

ProcessMessage

Method

Handles processing of synchronous method calls

Because HttpClientTransportSink is the last sink in the chain, the only two methods that provide functionality are ProcessMessage and AsyncProcessRequest. The job of both ProcessMessage and AsyncProcessRequest is to package up the message and send it. The main difference between the two is that ProcessMessage waits for a response from the server, whereas AsyncProcessRequest sets up a callback method to watch for the return message from the server while the main thread of execution continues.

In this section, we took a high-level look at how channels are constructed. HttpChannel was our model for this discussion. Next we’ll use this knowledge to build a custom channel.