Sinks and Sink Chains

This topic is specific to a legacy technology that is retained for backward compatibility with existing applications and is not recommended for new development. Distributed applications should now be developed using the Windows Communication Foundation (WCF).

Clients make method calls on remote objects by sending messages to the remote application domain. This is accomplished by a set of channel objects. The client application domain contains a client channel and the remote application domain contains a remote channel. Each channel is composed of a series of channel sinks that are linked together in a chain. The following illustration shows the structure of a basic channel sink chain.

Basic Channel Sink Chain

Basic Channel Sink Chain

Channels send each message along a chain of channel sink objects prior to sending or after receiving a message. This sink chain contains sinks required for basic channel functionality, such as formatter, transport, or stackbuilder sinks, but you can customize the channel sink chain to perform special tasks with a message or a stream. Each channel sink implements either IClientChannelSink or IServerChannelSinkfrlrfSystemRuntimeRemotingChannelsIServerChannelSinkClassTopic. The first channel sink on the client must also implement IMessageSinkfrlrfSystemRuntimeRemotingMessagingIMessageSinkClassTopic. It typically implements IClientFormatterSink (which inherits from both IMessageSink, IChannelSinkBase, and IClientChannelSink) and is called a formatter sink because it transforms the incoming message into a stream (an IMessagefrlrfSystemRuntimeRemotingMessagingIMessageClassTopic object).

The channel sink chain processes any message that is sent to or from an application domain. A channel sink has access to the message being processed and subsequent processing uses the message that is returned to the system after processing. This is a natural place to implement a logging service or any sort of filter.

Each channel sink processes the stream and then passes the stream to the next channel sink, which means that sinks before or after a specific sink should know what to do with the stream passed to them.

NoteNote

Message sinks must not throw exceptions. One way a message sink can control this is by wrapping method code in try-catch blocks.

Channel sink providers (objects that implement the IClientChannelSinkProviderfrlrfSystemRuntimeRemotingChannelsIClientChannelSinkProviderClassTopic, IClientFormatterSinkProviderfrlrfSystemRuntimeRemotingChannelsIClientFormatterSinkProviderClassTopic, or IServerChannelSinkProvider interface) are responsible for creating the channel sinks that process .NET remoting messages. When a remote type is activated, the channel sink provider is retrieved from the channel and the CreateSink method is called on the sink provider to retrieve the first channel sink from the chain.

Channel sinks are responsible for transporting messages between the client and the server. Channel sinks are also linked together in a chain. When the CreateSink method is called on a sink provider, it should do the following:

  • Create a channel sink.

  • Call CreateSink on the next sink provider in the chain.

  • Ensure that the next sink and the current one are linked together.

  • Return its sink to the caller.

Channel sinks are responsible for forwarding all calls made on them to the next sink in the chain and should provide a mechanism for storing a reference to the next sink.

Channel sinks have great flexibility in what they send down the sink chain. For example, security sinks that negotiate authentication before sending the actual serialized original message can hold onto the complete channel message, replace the content stream with their own content, and send it down the sink chain and on to the remote application domain. On the return journey, the security sink can intercept the reply message, creating a conversation with the corresponding security sinks in the remote application domain. Once an agreement is reached, the originating security sink can send the original content stream on to the remote application domain.

Message Processing in the Channel Sink Chain

Once the .NET remoting system locates a channel that can process the message, the channel passes the message to the formatter channel sink by calling SyncProcessMessage (or AsyncProcessMessagefrlrfSystemRuntimeRemotingMessagingIMessageSinkClassAsyncProcessMessageTopic). The formatter sink creates the transport header array and calls GetRequestStream on the next sink. This call is forwarded down the sink chain, and any sink can create a request stream that is passed back to the formatter sink. If GetRequestStream returns a null reference (Nothing in Visual Basic), the formatter sink creates its own sink to use for serialization. Once this call returns, the message is serialized and the appropriate message processing method is called on the first channel sink in the sink chain.

Sinks cannot write data into the stream but can read from the stream or pass a new stream along where required. Sinks can also add headers to the header array (if they have not previously called GetRequestStream on the next sink) and add themselves to the sink stack before forwarding the call to the next sink. (The sync stack is used to allow asynchronous calls to call back to the caller when they are completed.) When the call reaches the transport sink at the end of the chain, the transport sink sends the headers and serialized message over the channel to the server where the entire process is reversed. The transport sink (on the server) retrieves the headers and serialized message from the server side of the stream and forwards these through the sink chain until the formatter sink is reached. The formatter sink deserializes the message and forwards it to the .NET remoting system where the message is turned back into a method call and is invoked on the server object.

Creating Channel Sink Chains

To create a new channel sink, you must implement and configure the .NET remoting system to recognize an IServerChannelSinkProvider or IClientChannelSinkProvider implementation, which can create your custom IClientChannelSink or IServerChannelSink implementation or retrieve the next sink in the chain. You can use the BaseChannelSinkWithProperties abstract class to help implement your custom channel sinks.

Building a Channel Sink Provider

Applications can provide server or client channel sink providers as parameters when constructing a channel. Channel sink providers should be stored in a chain and it is the responsibility of the developer to chain all channel sink providers together before passing the outer one to the channel constructor. The channel sink provider implements a Next property for this purpose. The following code example illustrates how to build a client channel sink provider. A complete example is available at Remoting Example: Channel Sink Provider.

private Function CreateDefaultClientProviderChain() As IClientChannelSinkProvider
   Dim chain As New FirstClientFormatterSinkProvider            
   Dim sink As IClientChannelSinkProvider
   sink = chain
   sink.Next = New SecondClientFormatterSinkProvider
   sink = sink.Next
   return chain
End Function 

private IClientChannelSinkProvider CreateDefaultClientProviderChain(){
   IClientChannelSinkProvider chain = new FirstClientFormatterSinkProvider();            
   IClientChannelSinkProvider sink = chain;
   sink.Next = new SecondClientFormatterSinkProvider();
   sink = sink.Next;
   return chain;
} 
NoteNote

When multiple channel sink providers are provided in a configuration file, the .NET remoting system chains them together in the order in which they are found in the configuration file. The channel sink providers are created when the channel is created during the ConfigurefrlrfSystemRuntimeRemotingRemotingConfigurationClassConfigureTopic call.

Formatter Sinks

Formatter sinks serialize the channel message into the message stream as an object that implements IMessage. Some formatter sink implementations use the system-provided formatter types (BinaryFormatterfrlrfSystemRuntimeSerializationFormattersBinaryBinaryFormatterClassTopic and SoapFormatterfrlrfSystemRuntimeSerializationFormattersSoapSoapFormatterClassTopic). Other implementations can use their own means to transform the channel message into the stream.

The function of the formatter sink is to generate the necessary headers and serialize the message to the stream. After the formatter sink, the message is forwarded to all sinks in the sink chain through the SyncProcessMessage or AsyncProcessMessagefrlrfSystemRuntimeRemotingMessagingIMessageSinkClassAsyncProcessMessageTopic calls. At this stage the message has already been serialized and cannot be modified.

NoteNote

Sinks that must create or modify the message itself must be placed in the sink chain prior to the formatter. This is easily achieved by implementing IClientFormatterSink, thereby telling the system that it has a reference to the formatter sink. The real formatter sink can then be placed later in the sink chain.

On the return journey, the formatter sink transforms the message stream back into the channel message object (return message). The first sink on the client must implement the IClientFormatterSink interface. When CreateSink returns to the channel, the reference returned is cast to an IClientFormatterSink type so the SyncProcessMessage method can be called. Remember IClientFormatterSink is derived from IMessageSink. If the cast fails, the system raises an exception.

Custom Channel Sinks

On the client, custom channel sinks are inserted into the chain of objects between the formatter sink and the last transport sink. Inserting a custom channel sink in the client or server channel enables you to process the IMessage at one of the following points:

  • During the process by which a call represented as a message is converted into a stream and sent over the wire.

  • During the process by which a stream is taken off the wire and sent to the StackBuilderSink object (the last message sink before the remote object on the server) or the proxy object (on the client).

Custom sinks can read or write data (depending if the call is outgoing or incoming) to the stream and add additional information to the headers where desired. At this stage, the message has already been serialized by the formatter and cannot be modified. When the message call is forwarded to the transport sink at the end of the chain, the transport sink writes the headers to the stream and forwards the stream to the transport sink on the server using the transport protocol dictated by the channel.

Transport Sinks

The transport sink is the last sink in the chain on the client, and the first sink in the chain on the server. Besides transporting the serialized message, the transport sink is also responsible for sending the headers to the server and retrieving the headers and the stream when the call returns from the server. These sinks are built into the channel and cannot be extended.

Replacing the Default Formatter

Because a channel is an abstract networking mechanism, you can configure the .NET remoting system to combine a system-implemented channel with any formatter you choose. You can do this using the channel constructor that takes an IDictionary implementation of channel properties, a formatter on the server, and a formatter on the client. You can also specify the formatter in a configuration file. The following example instructs the .NET remoting configuration system to create an HttpChannel but use the BinaryClientFormatterSink on the client.

<configuration>
   <system.runtime.remoting>
      <application>
         <channels>
            <channel ref="http">
               <clientProviders>
                  <formatter ref="binary"/>
               </clientProviders>
         <channels>
      </application>
   </system.runtime.remoting>
</configuration> 

The following code does the same thing programmatically, assuming a remote interface type IService **** that implements GetServerString and GetServerTime:

Imports System
Imports System.Collections
Imports System.Runtime.Remoting
Imports System.Runtime.Remoting.Channels
Imports System.Runtime.Remoting.Channels.Http

Public Class ClientProcess
  <MTAThread()> _
  Public Shared Sub Main()
      
    ' Note that any name/value pairs of configuration attributes can be 
    ' placed in this dictionary (the configuration system calls this same 
    ' constructor).
    Dim properties As New Hashtable()
    properties("name") = "HttpBinary"
     
    ChannelServices.RegisterChannel(New HttpChannel(properties, New BinaryClientFormatterSinkProvider(), Nothing))
    ' The last parameter above (Nothing) is the server sink provider chain 
    ' to obtain the default behavior (which includes SOAP and 
    ' binary formatters on the server side).
    Dim service As IService = CType(Activator.GetObject(GetType(IService), "http://computer:8080/SAService"), IService)
      
    Console.WriteLine("Server string is: " + service.GetServerString())
    Console.WriteLine("Server time is: " + service.GetServerTime())
  End Sub
   
End Class 

using System;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;

public class ClientProcess{

  public static void Main(string[] Args){
        
    // Note that any name/value pairs of configuration attributes can be 
    // placed in this dictionary (the configuration system calls this 
    // same HttpChannel constructor).
    IDictionary properties = new Hashtable();
    properties["name"] = "HttpBinary";

    // The last parameter below is the server sink provider chain 
    // to obtain the default behavior (which includes SOAP and binary 
    // formatters) on the server side.
    ChannelServices.RegisterChannel(new HttpChannel(properties, new BinaryClientFormatterSinkProvider(), null));

    IService service = (IService)Activator.GetObject(typeof(IService),"http://computer:8080/SAService");
        
    Console.WriteLine("Server string is: " + service.GetServerString());
    Console.WriteLine("Server time is: " + service.GetServerTime());      
  }
}

For a complete example of this channel and formatter combination hosted in Internet Information Services (IIS), see Remoting Example: Hosting in Internet Information Services (IIS).

To change this client to use a TcpChannel object with the SoapClientFormatterSinkfrlrfSystemRuntimeRemotingChannelsSoapClientFormatterSinkClassTopic object, you must change only the namespaces and the RegisterChannel call, as shown in the following code:

ChannelServices.RegisterChannel(New TcpChannel(properties, New SoapClientFormatterSinkProvider(), Nothing))

ChannelServices.RegisterChannel(new TcpChannel(properties, new SoapClientFormatterSinkProvider(), null));

See Also

Concepts

Hosting Remote Objects in Internet Information Services (IIS)
Remoting Example: Hosting in Internet Information Services (IIS)

Other Resources

Advanced Remoting