< The .NET Remoting Channel | Main | UDP Custom Channel C# .NET Remoting Program Example >

 


 

 

Chapter 12 Part 6:

.NET Remoting

 

 

What do we have in this chapter 12 Part 6?

  1. Implementing the Client Channel

  2. Creating a Client Sink Provider

  3. Client Sink Provider Chain

  4. Implementing the Server Channel

  5. Creating a Server Transport Sink Provider

  6. Server Sink Provider Chain

 

Implementing the Client Channel

 

The role of the client channel is to set up a chain of channel sink providers that communicate with one another to handle the client-side communications of the remoting channel, as shown in Figure 12-2. On the client channel side, the .NET remoting infrastructure hands messages to a client channel sink chain that gets returned to the remoting infrastructure using CreateMessageSink described earlier. The first sink in the chain is a formatter that serializes messages into a request stream and passes the stream down to the chain of sink providers until the message reaches the end of the chain. At the end of the chain, a transport sink is responsible for transmitting the serialized request stream to the server side of the remoting channel. This is typically done over a network. To set up a client chain of sink providers requires building your own transport sink provider that will end up at the end of a chain of client sink providers. We’ll show how to set up a chain of sink providers later in this section; for now we’ll build a client transport sink provider. Since our transport sink provider is at the end of the chain, it will handle the sending of remoting requests to the server and the receiving of responses from the server.

 

Creating a Client Sink Provider

 

Building your own client sink transport provider requires implementing the IClientChannelSinkProvider interface and applying the provider to the end of the chain of client sink providers for the remoting infrastructure. The following C# code demonstrates how to implement the client provider:

 

internal class ClientChannelSinkProvider : IClientChannelSinkProvider

{

    public IClientChannelSink CreateSink(IChannelSender channel, string url, object remoteChannelData)

    {

        return new ClientTransportSink(url);

    }

 

    public IClientChannelSinkProvider Next

    {

        get

        {

            // We are at the end of the sink chain in the client so return null.

            return null;

        }

        set

        {

            throw new NotSupportedException();

        }

    }

}

 

As shown in the code, you must implement the CreateSink method and the Next property. The Next property is supposed to return the next provider in the sink chain, but since our client transport provider is at the end of the sink chain, it should return null. CreateSink is responsible for creating a channel sink that sends client requests and receives client responses. Recall from the earlier discussion that CreateSink gets called on each of the client channel sink providers in the chain when the client remoting infrastructure calls CreateMessageSink. CreateMessageSink sets up a connection to a remoting server to access a remote class. Therefore, in the code sample for the client transport sink provider, we create a new client transport sink that receives a URL that indicates where to access a remote class. The following C# code sample shows how to implement the client channel sink when CreateSink is called. To implement the sink requires inheriting the IClientChannelSink interface. Figure 12-4 shows the abstract methods and properties that must be implemented.

 

 

Figure 12-4: IClientChannelSink abstract methods and properties

 

The following C# code sample provides a code outline for how to develop a client transport sink for the client sink provider:

 

internal class MyClientTransportSink : IClientChannelSink

{

    internal MyClientTransportSink(string Url)

    {

        // This is a good place to set up communication

        // to the server by using the Url parameter to determine where to connect.

    }

    public void AsyncProcessRequest(

        IClientChannelSinkStack SinkStack,

        IMessage Msg,

        ITransportHeaders RequestHeaders,

        System.IO.Stream RequestStream)

    {

        // Provide implementation to handle processing requests asynchronously

    }

 

    public void AsyncProcessResponse(

        IClientResponseChannelSinkStack SinkStack,

        object State,

        ITransportHeaders Headers,

        System.IO.Stream Stream)

    {

        // We are last in the chain - no need to implement

        throw new NotSupportedException();

    }

 

    public System.IO.Stream GetRequestStream(IMessage Msg, ITransportHeaders Headers)

    {

        // We don't do any serialization here.

        return null;

    }

 

    public void ProcessMessage(

        IMessage Msg,

        ITransportHeaders RequestHeaders,

        System.IO.Stream RequestStream,

        out ITransportHeaders ResponseHeaders,

        out System.IO.Stream ResponseStream)

    {

        // Get the URI from the Msg parameter to send to the server

        IMethodCallMessage MCM = (IMethodCallMessage)Msg;

        string Uri = MCM.Uri;

 

        // Send the Uri, RequestHeaders, and RequestStream to the server

        byte[ ] RequestBuffer = null;

 

        PrepareOutboundMessage(Uri,

            RequestHeaders,

            RequestStream,

            out RequestBuffer);

 

        SendMessageToServer(RequestBuffer);

 

        // Wait for the server to respond with

        // ResponseHeaders and a ResponseStream to return from this method.

        Byte[ ] ResponseBuffer = null;

        ReceiveResponseFromServer(out ResponseBuffer);

 

        PrepareInboundMessage(ResponseBuffer, out ResponseHeaders, out ResponseStream);

    }

 

    public IClientChannelSink NextChannelSink

    {

        get

        {

            return null;

        }

    }

 

    public System.Collections.IDictionary Properties

    {

        get

        {

            return null;

        }

    }

}

 

The methods that need implementation are a constructor that accepts a URL, AsyncProcessRequest, and ProcessMessage. Since the sink is intended to be the last provider of the client sink provider chain, the rest of the methods and properties return null or raise exceptions,. The sink constructor is important because it lets you set up communication with the remoting server channel. For example, if you were creating a TCP channel, you could create a socket and make a connection to the server. ProcessMessage is the core method that sends a client request message to the remoting server and awaits a response from the server. Sending a client request in ProcessMessage requires the following steps:

 

  1. Package up the URI from the Msg parameter with the RequestHeaders and the RequestStream and send the information to the server. The URI identifies the remote class instance you are trying to access on the server.
  2. Wait for the server to process the request information. Once the server has completed the request, it will respond with packaged ResponseHeaders and a ResponseStream.
  3. Unpackage the ResponseHeaders and ResponseStream from the server and return the information up the sink chain.

 

The process of packaging up request headers and streams and unpackaging response headers and streams can be handled any way you like, depending on how your client sink communicates with the server. For example, if you are trying to send requests and receive responses over a socket, you’ll want to bundle all the information as a stream of bytes and transmit the information over the socket. In the previous code example we called PrepareOutboundMessage to package up the request headers and the request stream. We called PrepareInboundMessage to unpackage response headers and a response stream received from the server side of the channel. In our discussion to follow of the server transport channel, we’ll reuse PrepareOutboundMessage and PrepareInboundMessage to handle client requests and server responses on the server.

AsyncProcessRequest is an asynchronous version of ProcessMessage. The main difference is you do not return ResponseHeaders and ResponseStreams directly from the method call. Instead, you have to call AsyncProcessResponse from the sink stack when the server returns response headers.

You have now learned how to construct a client transport sink provider. This provider must be applied to the end of a client sink provider chain that is constructed during the client initialization of the channel.

 

Client Sink Provider Chain

 

Once you have established a client sink transport provider, you can set up a chain of client sink providers that will handle:

 

  1. The client-side serialization for remoting calls
  2. Data transmission for the serialized calls using the transport sink we just constructed

 

Setting up a chain of sink providers for the channel happens when a remoting channel is created. Earlier in the chapter we explained that remoting channels are created either programmatically or by using configuration files. Our channel constructor methods called either SetupClientSinkProviders or SetupServerSinkProviders, depending on whether the channel constructor was creating the client or server side of the channel. The following C# code shows how to implement SetupClientSinkProviders to set up a chain of sink providers for the client.

 

private IClientChannelSinkProvider m_ClientSinkProvidersChain = null;

 

internal void SetupClientSinkProviders(IClientChannelSinkProvider ClientProviderChain)

{

    if (ClientProviderChain == null)

    {

        // Install at least default formatter for serialization

        m_ClientSinkProvidersChain = new BinaryClientFormatterSinkProvider();

    }

    else

    {

        // Get the provider chain from the outside

        m_ClientSinkProvidersChain = ClientProviderChain;

    }

 

    // Move to the end of the sink provider chain

    IClientChannelSinkProvider TempSinkProvider = m_ClientSinkProvidersChain;

           

    while (TempSinkProvider.Next != null)

        TempSinkProvider = TempSinkProvider.Next;

 

    // Append our new channel sink provider to the end of the chain

    TempSinkProvider.Next = new MyClientChannelSinkProvider();

}

 

When setting up a chain of client sink providers, you are responsible for setting up a formatter sink that serializes messages from the remoting infrastructure and you must place a client transport sink provider at the end of the chain. The remoting infrastructure might hand you a formatter sink from a client configuration file. If you do not receive a formatter sink provider, you must provide a default formatter. In the preceding code sample we provided the binary serialization sink formatter from the .NET Framework. You have now learned the basics for setting up a channel and implementing the client side of a custom channel. Next we’ll look at how to implement the server side of a custom channel.

 

Implementing the Server Channel

 

Implementing the server side of a custom channel is the reverse of the client channel. On the server side, the server transport sink (the lowest sink in the chain) reads requests received from the client and passes the requests up the server sink chain as a stream. The server formatter sink (the sink at the top of the chain) will deserialize the request stream and hand the request up to the server remoting infrastructure. Once the server remoting infrastructure has processed the client request, a response is usually generated and sent down a chain of server sink providers where a server transport sink at the end of the chain is responsible for sending the response message back to the client. Therefore, on the server side you must implement a server transport sink provider.

 

Creating a Server Transport Sink Provider

 

To implement the server transport sink requires inheriting the IServerChannelSink interface. Figure 12-5 shows the abstract methods and properties that must be implemented.

 

 

Figure 12-5: IServerChannelSink abstract methods and properties

 

The following C# code fragment provides a code outline for how to develop a server transport sink for the server sink provider described earlier.

 

internal class MyServerTransportSink : IServerChannelSink

{

    private IServerChannelSink m_Next;

 

    internal MyServerTransportSink(IServerChannelSink Next)

    {

        m_Next = Next;

    }

 

    public System.IO.Stream GetResponseStream(

        IServerResponseChannelSinkStack SinkStack,

        object State,

        IMessage Msg,

        ITransportHeaders Headers)

    {

        return null;

    }

 

    public ServerProcessing ProcessMessage(

        IServerChannelSinkStack SinkStack,

        IMessage RequestMsg,

        ITransportHeaders RequestHeaders,

        Stream RequestStream,

        out IMessage ResponseMsg,

        out ITransportHeaders ResponseHeaders,

        out Stream ResponseStream)

    {

        ResponseMsg = null;

        ResponseHeaders = null;

        ResponseStream = null;

 

        throw new NotSupportedException();

    }

 

    public void AsyncProcessResponse(

        IServerResponseChannelSinkStack SinkStack,

        object State,

        IMessage Msg,

        ITransportHeaders ResponseHeaders,

        Stream ResponseStream)

    {

        throw new NotSupportedException();

    }

 

    public IServerChannelSink NextChannelSink

    {

        get

        {

            return m_Next;

        }

    }

 

    public IDictionary Properties

    {

        get

        {

            return null;

        }

    }

}

 

Since a server transport sink provider is at the beginning of the receiving end of the server chain of sink providers, most of the methods and properties do not need implementation. The only method that needs implementation is NextChannelSink, which is responsible for returning the next server sink in the chain of sink providers. This method is needed when the server transport sink provider communicates up the chain of sink providers with client request messages.

At this point you might be wondering how the server provider transport sink handles servicing requests from the client channel. Recall the IChannelReceiver abstract methods discussed during creation of the channel; at that time we mentioned that you have to implement StartListening and StopListening. In the StartListening method we asynchronously (using a thread) called a method named RunServer that handles the receiving of remoting requests from a client and returns responses back to the client after the server side has processed the request. The following C# code describes how to implement a listening server:

 

public void RunServer()

{

    SetupCommunication();

    byte [ ] buffer = new byte[4096];

 

    for (;;)

    {

        // Let the server wait for a message to arrive from a client

        ReceiveMessageFromClient(out buffer);

 

        ITransportHeaders RequestHeaders;

        Stream RequestStream;

 

        PrepareInboundMessage(buffer, out RequestHeaders, out RequestStream);

        // Setup a sink stack to pass to Process message in the next sink

        ServerChannelSinkStack SinkStack = new ServerChannelSinkStack();

 

        SinkStack.Push(this, null);

        // Setup the response to hand back to the client

        IMessage ResponseMessage;

        ITransportHeaders ResponseHeaders;

        Stream ResponseStream;                   

        // Call the upstream sinks process message

        ServerProcessing Processing = this.NextChannelSink.ProcessMessage(

                SinkStack,

                null,

                RequestHeaders,

                RequestStream,

                out ResponseMessage,

                out ResponseHeaders,

                out ResponseStream); 

 

        // handle response

        switch (Processing)

        {

            case ServerProcessing.Complete:

                // Call completed synchronously send the response immediately

                SinkStack.Pop(this);

                // Prepare response to send back to client

                byte [ ] SendBuffer;

                Utility.PrepareOutboundMessage("", ResponseHeaders, ResponseStream, out SendBuffer);

                SendResponseToClient(SendBuffer);

                break;

 

            case ServerProcessing.OneWay:

                break;

                       

            case ServerProcessing.Async:

                SinkStack.StoreAndDispatch(this, null);

                break;

        }

    }

}

 

The listening server is designed to wait for a client request to arrive. When it does, it will unpackage the RequestHeaders and RequestStream and send the information up a chain of server sink providers by calling ProcessMessage from the next sink provider on the chain. When ProcessMessage completes, the server has to determine if it needs to send back a response based on the return state of ProcessMessage. ProcessMessage can return one of three states: Complete, Async, and OneWay. If ProcessMessage returns Complete, a response must be sent back to the client. A response is not needed in the OneWay and Async state. Sending a response requires packaging up the ResponseHeaders and the ResponseStream and sending the response back to the client.

In the preceding sample, request headers and streams from the client were unpackaged using a custom PrepareInboundMessage method. Response headers and streams were packaged up to send to the client using a custom method called PrepareOutboundMessage. Earlier in the chapter, PrepareOutboundMessage was used to send requests from the client transport sink to the listening server and PrepareInboundMessage was used to receive responses from the listening server. These methods are designed to work together to prepare client requests and server responses for data transmission between the client and server side of the channel. It does not really matter how they are implemented as long as they are capable of packaging and unpackaging ITransportHeaders on a Stream.

 

Server Sink Provider Chain

 

Once you have established a server sink provider, you can set up a chain of server sink providers to handle:

 

  1. The receiving of request messages
  2. The server-side serialization of the remoting calls

 

A chain of server sink providers must be set up whenever the server channel is created directly by an application or is created by the remoting infrastructure from a configuration file. The following C# code shows how to set up sink providers for the server.

 

internal void SetupServerSinkProviders(IServerChannelSinkProvider InputSinkProvider)

{

    string MachineName = Dns.GetHostName();

 

    m_ChannelDataStore = new ChannelDataStore(null);

    m_ChannelDataStore.ChannelUris = new string[1];

    m_ChannelDataStore.ChannelUris[0] = m_ChannelName +

        "://" + MachineName + ":" +

        m_ChannelPort.ToString();

 

    IServerChannelSinkProvider ServerSinkProvidersChain;

 

    // Create a default sink provider if one was not passed in

    if (InputSinkProvider == null)

    {

        ServerSinkProvidersChain = new BinaryServerFormatterSinkProvider();

    }

    else

    {

        ServerSinkProvidersChain = InputSinkProvider;

    }

    // Collect the rest of the channel data:

    IServerChannelSinkProvider provider = ServerSinkProvidersChain;

    while(provider != null)

    {

        provider.GetChannelData(m_ChannelDataStore);

        provider = provider.Next;

    }

    // Create a chain of sink providers

    IServerChannelSink next = ChannelServices.CreateServerChannelSinkChain(ServerSinkProvidersChain, this);

    // Put the transport sink at the receiving end of the chain.

    m_transportSink = new UDPServerTransportSink(next);

}

 

When setting up a chain of server sink providers, you are responsible for setting up a formatter sink that deserializes messages sent from the client. The remoting infrastructure might hand you a formatter sink from a client configuration file. If you do not receive a formatter sink provider then you must provide a default formatter. Note that the formatter should match the formatter of the client sink; otherwise, deserialization of client messages will not work. Once the formatter sink provider is established, you’ll need to place the server transport sink provider at the receiving end of the chain. After the server chain is established, the listening server can send request messages from the client up the server sink provider chain to access remote classes. You now know the basics for developing your own custom remoting channel. The following C# program example shows a custom channel sample named UDPChannel that uses the principles described here and enables the .NET remoting infrastructure to communicate over UDP.

 

 

 


< The .NET Remoting Channel | Main | UDP Custom Channel C# .NET Remoting Program Example >