Implementing the Server Channel

As with the client channel, I show you how the server channel will be used before diving into the code. Basically, it looks exactly like the SMTPClientChannel did:

 <channel       name="smtpserver"       type="SMTPChannel.SMTPServerChannel, SMTPChannel"       senderEmail="server_1@localhost"       smtpServer="localhost"       pop3Server="localhost"       pop3User="server_1"       pop3Password="server_1"       pop3PollInterval="1" > 

The parameters for this channel are shown in Table 10-3.

Table 10-3: Parameters for SMTPServerChannel




Unique name for this channel.


The value for the e-mail's From: header. The server will reply to the address specified here.


Your outgoing e-mail server's name.


Your incoming mail server's name.


The POP3 user account that is assigned to this client application.


The password for the application's POP3 account.


Interval in seconds at which the framework will check for new mail at the server.

The basic difference between the SMTPClientChannel and the SMTPServerChannel is that the latter registers itself with the POP3PollManager as a server. This means that the POP3Polling instance will constantly check for new e-mails.

The server-side channel has to implement IChannelReceiver, which in turn inherits from IChannel again. These interfaces are shown in Listing 10-5.

Listing 10-5: IChannel and IChannelReceiver

start example
 public interface IChannel {     string ChannelName { get; }     int ChannelPriority { get; }     string Parse(string url, ref String objectURI); } public interface IChannelReceiver: IChannel {     object ChannelData { get; }     string[] GetUrlsForUri(string objectURI);     void StartListening(object data);     void StopListening(object data); } 
end example

The implementation of SMTPServerChannel itself is quite straightforward. Its constructor checks for the attributes specified in the configuration file and assigns them to local member variables. It also creates a ChannelDataStore object, which is needed for CAOs to communicate back with the server (that is, when creating a CAO using this channel, the server returns the base URL contained in this ChannelDataStore object).

It then creates the sink chain and adds the SMTPServerTransportSink on top of the chain. This is different from the client-side channel, where the constructor only creates a chain of sink providers. This is because on the server-side there is only a single sink chain per channel, whereas the client creates a distinct sink chain for each remote object. Finally the constructor calls StartListening() to enable the reception of incoming requests:

 using System; using System.Collections; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; namespace SMTPChannel {    public class SMTPServerChannel: BaseChannelWithProperties,       IChannelReceiver,       IChannel    {       private String _myAddress;       private String _name;       private String _smtpServer;       private String _pop3Server;       private String _pop3Username;       private String _pop3Password;       private int _pop3Pollingtime;       private SMTPServerTransportSink _transportSink;       private IServerChannelSinkProvider _sinkProvider;       private IDictionary _properties;       private ChannelDataStore _channelData;       public SMTPServerChannel(IDictionary properties,              IServerChannelSinkProvider serverSinkProvider)      {          _sinkProvider = serverSinkProvider;          _properties = properties;          _myAddress = (String) _properties["senderEmail"];          _name = (String) _properties["name"];          _pop3Server = (String) _properties["pop3Server"];          _smtpServer = (String) _properties["smtpServer"];          _pop3Username = (String) _properties["pop3User"];          _pop3Password = (String) _properties["pop3Password"];          _pop3Pollingtime =             Convert.ToInt32((String) _properties["pop3PollInterval"]);          // needed for CAOs!          String[] urls = { this.GetURLBase() };          _channelData = new ChannelDataStore(urls);          // collect channel data from all providers          IServerChannelSinkProvider provider = _sinkProvider;          while (provider != null)          {             provider.GetChannelData(_channelData);             provider = provider.Next;          }          // create the sink chain          IServerChannelSink snk =             ChannelServices.CreateServerChannelSinkChain(_sinkProvider,this);          // add the SMTPServerTransportSink as a first element to the chain          _transportSink = new SMTPServerTransportSink(snk, _smtpServer,             _myAddress);          // start to listen          this.StartListening(null);       } 

The constructor calls GetURLBase(), which provides a way for this channel to return its base URL:

 private String GetURLBase() {    return "smtp:" + _myAddress; } 

You also have to implement IChannel's methods and properties: Parse(), ChannelName, and ChannelPriority. The implementation itself looks exactly the same as it did for the client-side channel:

 public string Parse(string url, out string objectURI) {    String email;    SMTPHelper.parseURL(url, out email, out objectURI);    if (email == null || email=="" || objectURI == null || objectURI =="")    {        return null;    }    else    {        return "smtp:" + email;    } } public string ChannelName {    get    {        return _name;    } } public int ChannelPriority {    get    {        return 0;    } } 

The single most important method of a server-side channel is StartListening(). Only after it is called will the server be able to receive requests and to handle them.

In the SMTPServerChannel, this method registers its connection as a server with the POP3PollManager. It next registers the server-side transport sink and its e-mail address with the SMTPHelper. This last step will enable the helper to dispatch requests based on the destination e-mail address:

 public void StartListening(object data) {     // register the POP3 account for polling     POP3PollManager.RegisterPolling(_pop3Server,_pop3Username,        _pop3Password,_pop3Pollingtime,true);     // register the e-mail address as a server     SMTPHelper.RegisterServer(_transportSink,_myAddress); } public void StopListening(object data) {     // Not needed ;-) } 

To enable CAOs to work correctly, you must implement the method GetUrlsForUri() and the property ChannelData. The first allows the framework to convert a given object's URI into a complete URL (including the protocol-specific part, such as The second returns the channel data object that is used by the framework to provide the complete URL for a client-activated object:

 public string[] GetUrlsForUri(string objectURI) {    String[] urls;    urls = new String[1];    if (!(objectURI.StartsWith("/")))        objectURI = "/" + objectURI;    urls[0] = this.GetURLBase() + objectURI;    return urls; } public object ChannelData {    get    {        return _channelData;    } } 

Creating the Server's Sink

A server-side transport sink has to implement IServerChannelSink, which in turn extends IChannelSinkBase. You might already know the interfaces shown in Listing 10-6 from Chapter 9, where they were used to extend the remoting infrastructure.

Listing 10-6: IChannelSinkBase and IServerChannelSink

start example
 public interface IChannelSinkBase {     IDictionary Properties { get; } } public interface IServerChannelSink : IChannelSinkBase {     IServerChannelSink NextChannelSink { get; }     ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,             IMessage requestMsg, ITransportHeaders requestHeaders,             Stream requestStream, ref IMessage responseMsg,             ref ITransportHeaders responseHeaders, ref Stream responseStream);     void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack,             object state, IMessage msg, ITransportHeaders headers,             Stream stream);     Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack,             object state, IMessage msg, ITransportHeaders headers); } 
end example

The implementation of SMTPServerTransportSink is a little bit different from classic channel sinks. First and foremost, it is not created by a sink provider but instead directly by the channel that passes the reference to the next sink, the SMTP server's address and the server's own address to the newly created SMTPServerTransportSink.

Additionally, you'll need a private class to hold state information about the origin of the request and its message ID to process the asynchronous replies:

 using System; using System.IO; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting; using System.Runtime.Remoting.Messaging; namespace SMTPChannel {     public class SMTPServerTransportSink: IServerChannelSink     {         // will be used as a state object for the async reply         private class SMTPState         {            internal String ID;            internal String responseAddress;         }         private String _smtpServer;         private String _myAddress;         private IServerChannelSink _nextSink;         public SMTPServerTransportSink(IServerChannelSink nextSink,               String smtpServer, String myAddress)         {            _nextSink = nextSink;            _smtpServer =smtpServer;            _myAddress = myAddress;         } 

One of the main differences between a server-side transport sink and a "conventional" channel sink is that the latter receives its parameters via the ProcessMessage() method. The transport sink instead does not define a specific way to receive the incoming request from the underlying transport mechanisms.

In the case of the SMTPServerTransportSink, it receives a POP3Msg object from the SMTPHelper and processes it in HandleIncomingMessage(). It first splits the e-mail message into a Stream object and an ITransportHeaders object. It then creates a new state object of type SMTPState and populates its properties from the e-mail's values.

Next, it creates a ServerChannelSinkStack and pushes itself and the newly created state object onto it before handing over the processing to the next sink in its chain. When this method is finished, it returns a ServerProcessing value. This indicates whether the message has been handled synchronously, asynchronously, or as a one-way message.

The SMTPServerTransportSink now behaves accordingly. If the request has been handled synchronously, it generates and sends a response message. For asynchronous calls, it waits for the framework to call its AsyncProcessResponse() method. For one-way calls, it does nothing at all:

 public void HandleIncomingMessage(POP3Msg popmsg) {    Stream requestStream;    ITransportHeaders requestHeaders;    String ID;    // split the message in Stream and ITransportHeaders    SMTPHelper.ProcessMessage(popmsg,out requestHeaders,       out requestStream, out ID);    // create a new sink stack    ServerChannelSinkStack stack = new ServerChannelSinkStack();    // create a new state object and populate it    SMTPState state = new SMTPState();    state.ID = ID;    state.responseAddress = SMTPHelper.GetCleanAddress(popmsg.From );    // push this sink onto the stack    stack.Push(this,state);    IMessage responseMsg;    Stream responseStream;    ITransportHeaders responseHeaders;    // forward the call to the next sink    ServerProcessing proc = _nextSink.ProcessMessage(stack,null,requestHeaders,              requestStream, out responseMsg, out responseHeaders,              out responseStream);    // check the return value.    switch (proc)    {        // this message has been handled synchronously        case ServerProcessing.Complete:           // send a response message           SMTPHelper.SendResponseMessage(_myAddress,              state.responseAddress,_smtpServer,responseHeaders,              responseStream,state.ID);           break;        // this message has been handled asynchronously        case ServerProcessing.Async:            // nothing needs to be done yet            break;        // it's been a one way message        case ServerProcessing.OneWay:           // nothing needs to be done yet           break;    } }

AsyncProcessResponse() is called when the framework has completed the execution of an underlying asynchronous method. The SMTPServerTransportSink in this case generates a response message and sends it to the client:

 public void AsyncProcessResponse(    IServerResponseChannelSinkStack sinkStack, object state,    IMessage msg, ITransportHeaders headers, System.IO.Stream stream) {    // fetch the state object    SMTPState smtpstate = (SMTPState) state;    // send the response e-mail    SMTPHelper.SendResponseMessage(_myAddress,       smtpstate.responseAddress,_smtpServer,headers,       stream,smtpstate.ID); }

What's still left in SMTPServerTransportSink is the implementation of the other mandatory methods and properties defined in IServerChannelSink. Most of them will not be called for a transport sink and will therefore only return null or throw an exception:

 public IServerChannelSink NextChannelSink {    get    {        return _nextSink;    } } public System.Collections.IDictionary Properties {    get    {        // not needed        return null;    } } public ServerProcessing ProcessMessage(    IServerChannelSinkStack sinkStack, IMessage requestMsg,    ITransportHeaders requestHeaders, Stream requestStream,    out IMessage responseMsg, out ITransportHeaders responseHeaders,    out Stream responseStream) {    // will never be called for a server side transport sink    throw new NotSupportedException(); } public Stream GetResponseStream(    IServerResponseChannelSinkStack sinkStack, object state,    IMessage msg, ITransportHeaders headers) {    // it's not possible to directly access the stream    return null; } 

Great! You have now finished implementing your own transport channel!

Advanced  .NET Remoting C# Edition
Advanced .NET Remoting (C# Edition)
ISBN: 1590590252
EAN: 2147483647
Year: 2002
Pages: 91
Authors: Ingo Rammer © 2008-2017.
If you may any questions please contact us: