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
Table 7-1 introduces some channel terminology that will be necessary for understanding channels.
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. |
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.Runtime.Remoting.Channels.IChannelSender, and System.Runtime.Remoting.Channels.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.
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.
Channel properties can be set in two ways. The first way is programmatically. 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.
As we stated earlier, HttpChannel inherits from IChannelSender. IChannelSender provides the interface for client-side interaction with the channel. As you can see in Table 7-5, IChannelSender has only a single member.
Member | Member Type | Description |
---|---|---|
CreateMessageSink | Method | Returns a channel message sink |
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 _channelData 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.
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.IServerChannelSink. Table 7-6 presents the public members of this interface.
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.
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 information 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 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.
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.
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 IClientChannelSink |
The property Next always returns null because HttpClientTransportSinkProvider is the last provider in the chain. CreateSink returns a newly created 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.
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.