Skip to main content
Email Password   helpLost your password?

Contents

Introduction
Usage
    Configuring the MSMQ Channel     
     Server config file
     Client config file
     Queues
Concept and Design
    Synchronously Call
     Asynchronously Call
Implementation
     MSMQChannelLib
      MSMQSender
          Configuration
          CreateMessageSin
          SyncProcessMessage
          AsyncProcessMessage
      MSMQReceiver
Test
Conclusion

 

Introduction

The Remoting channels are objects in the System.Runtine.Remoting namespace that transport messages between applications across remoting boundaries such as application domains, processes and computers in the peer-to-peer manner. The channel is plug-in between the server and client side. From the abstract point of view, the channel represents a logical connectivity between two points. The point which listening inbound messages is called endpoint and it is a logical address of the remote object, knew as URI (uniform resource identifier). This connectivity is full transparent to the CLR and "understandable" for open protocol design pattern.

The Remoting namespace includes an implementation of two channels: http and tcp represented as standard channels. In this article I will show you how to build and use the custom channel using the System.Messaging namespace classes to implement the MSMQ channel for Remoting purpose. Before than we will go to its implementation details, let's start it with usage and configuration issue. I am assuming that you have a knowledge of the .Net Remoting.

Usage

Using the MSMQ custom remoting channel required to install MSMQChannelLib into the GAC. There are two classes in this library:

The Remoting object has no special requirements to handle the custom remoting channel. It's a full transparently without any knowledge who and how the messages are transferring. So, we can administratively switch the channel (endpoint) any time, based on the application environment.

The situation on the client side is slightly different. The sender needs to know an URI address (endpoint) of the Remoting object for its activation, see the following code snippet:

 // activate a remote object

 Type interfaceType = typeof(IRemInterface);
 string objectUrl = @"msmq://./reqchannel/endpoint"; 
 IRemInterface robj = (IRemInterface)Activator.GetObject(interfaceType, objectUrl);

This address can be either hard coded or retrieved from the config file. Using the config file is more flexible. That's all differences on the client side. Note that this issue is the same when the application is using two standard channels (http, tcp).

Each side of the connectivity has own custom properties to configure a channel end in the properly way.

Configuring the MSMQ Channel:

Based on the above, the server and client channels can be configured as is shown in the following pictures:

Server config file:

<configuration>
 <system.runtime.remoting>
  <application >
   <service>
    <wellknown mode="SingleCall" type="RemoteObject.RemObject, RemoteObject" objectUri="endpoint" />
   </service>
   <channels>
    <channel type="RKiss.MSMQChannelLib.MSMQReceiver, MSMQChannelLib" listener=".\ReqChannel"/>
   </channels>
  </application>
 </system.runtime.remoting>
</configuration>

The following picture shows another example of the server channel to configure two msmq channels listening on the local queue with the name ReqChannel. Their priority have a value 20. Each channel has own unique name such as msmq_1 rsp. msmq_2.

<configuration>
 <system.runtime.remoting>
  <application >
   <service>
    <wellknown mode="SingleCall" type="RemoteObject.RemObject, RemoteObject" objectUri="endpoint" />
   </service>
   <channels>
    <channel ref="msmq" name="msmq_1" listener=".\ReqChannel" priority="20"/>
    <channel ref="msmq" name="msmq_2" listener=".\ReqChannel" priority="20"/>
   </channels>
   </application>
   <channels>
    <channel id="msmq" type="RKiss.MSMQChannelLib.MSMQReceiver, MSMQChannelLib"/>
   </channels>
 </system.runtime.remoting>
</configuration>

Client config file:

The client channel is represented by the MSMQSender class located in the MSMQChannelLib asssembly. Its properties are: respond queue is ".\RspChannel" , administration queue is ".\RspChannel",  outbound message and its return message are timed to be received within 30 seconds and the channel priority is 10.

<configuration>
 <system.runtime.remoting>
  <application >
   <client>
    <wellknown type="RemoteObject.RemObject, RemoteObject" url="msmq://./ReqChannel/endpoint" />
   </client>
   <channels>
    <channel type="RKiss.MSMQChannelLib.MSMQSender, MSMQChannelLib" 
             respond=".\RspChannel" admin=".\AdminChannel" timeout="30" priority="10"/>
   </channels>
  </application>
 </system.runtime.remoting>
</configuration>

Queues:

The MSMQ Remoting channel requires an existing queue on the sender and receiver sides. The administration queue on the sender side is an option. These two queues have to be transactional for the channel using on the Enterprise Network to guarantee delivering a message (one only) to the destination queue. There is a special situation - calling a remoting OneWay attributed method. In this case, there is no return message and the call has a fire&forget design pattern.

Concept and Design

The Custom Remoting MSMQ channel concept is based on the MSMQ technology, where queues are accessible on the Enterprise Network from anywhere. The remoting channel using a message passing design pattern between a remote object and its consumer. It's based on the two parts:  Sender and Receiver with an interface contract ICannelSender resp. IChannelReceiver from the System.Runtime.Remoting.Channels namespace. Implementation of these two interfaces allows plug-in a particular channel and passing a IMessage object through. Note that IMessage object contains a communication data sent between cooperating message sinks controlled by the Runtime.Remoting.Messaging namespace classes.

The IChannelSender interface allows to call a channel synchronously or asynchronously using the delegate design pattern. Let's look at more details for both options:

Synchronously Call:

The following picture shows an IMessage object flows through the MSMQ remoting channel in the sync manner.

 

Concept of the Custom remoting MSMQ channel - sync call

 

 

Note that the Receiver can handle also one special case - calling the OneWay attributed remoting method. In this case, the return IMessage object reference is null and it is done immediately without processing the remote method body and also there is no respond message to the sender. However, the client knows (from the assembly metadata) that this message has to be handled asynchronously without any respond. It's very important to understand the behaviour of this attribute which has a fire&forget design pattern. The client in this case doesn't have any information about the remoting method processing such as an error exceptions.

 

Asynchronously Call:

The following picture shows an IMessage object flows through the MSMQ remoting channel in the async manner.

 

Concept of the Custom remoting MSMQ channel - sync call

 

To process the IMessage object through channel in the asynchronously manner is decided by the client. The ISenderChannel supports an AsyncProcessMessage function which required two arguments: IMessage and IMessageSink objects. As you can see the Sender is divided into two isolated parts and the Receiver is identically to the above Sync Call design.

 

 

Implementation

The MSMQ Custom Remoting Channel implementation using the System.Messaging namespace classes to communicate with the MSMQ,  System.Runtime.Remoting namespace classes to integrated channel into the  remoting mechanism and System.Runtime.Serialization namespace classes to serialize/de-serialize messages through the channel. All implementation is built by managed code and package into one assembly named as MSMQChannelLib.

MSMQChannelLib

The MSMQChannelLib contains a functionality of the MSMQ remoting channel for server and client side. Its assembly is required to be installed into the GAC at the server and client side. Before using the MSMQ remoting channel, the channel has to be registered either programmatically or administratively. Note that there is no requirement to register a server channel in prior to client channel registration, that's the feature of the loosely coupled connectivity of the MSMQ. However, the MSMQ remoting channel requires in prior of the registration that the Request respectively Respond queue is existed.

As I mentioned earlier this assembly contains two major classes. The following are some details how they been implemented: 

MSMQSender

This is a derived class from the IChannelSender interface from the System.Runtime.Remoting.Channels namespace. Its implementation is divided into the following parts:

Configuration:

The class has several constructors to config the sender channel programmatically and one constructor for administratively configuration using the config file, here is its code snippet:

public MSMQSender(IDictionary properties, IServerChannelSinkProvider serverSinkProvider) 
{
   string pathRQ = DEFAULT_RESPONDCHANNEL;
   string pathAQ = null; 
   int timeout = DEFAULT_TIMEOUTINSEC;
   string channelName = DEFAULT_CHANNELNAME;
   int channelPriority = DEFAULT_CHANNELPRIORITY;

   // administratively setup the config values (xxx.exe.config file)

   if(properties.Contains("name")) 
      channelName = properties["name"].ToString();
   if(properties.Contains("priority")) 
      channelPriority = Convert.ToInt32(properties["priority"]);
   if(properties.Contains("respond")) 
      pathRQ = properties["respond"].ToString();
   if(properties.Contains("admin")) 
      pathAQ = properties["admin"].ToString();
   if(properties.Contains("timeout")) 
      timeout = Convert.ToInt32(properties["timeout"]);

   Init(channelName, pathRQ, pathAQ, timeout, channelPriority);

   Trace.WriteLine(string.Format("Client-config: name={0}, respQ={1}, adminQ={2}, timeout={3}, priority={4}", 
                   m_ChannelName, pathRQ, pathAQ, timeout, m_ChannelPriority));
}

This constructor is invoked by the following function to pass all custom channel properties specified in the config file via the IDictionary interface:

   RemotingConfiguration.Configure(@"..\..\Server.exe.config");

That's all of the configuration process for remoting channel. It's very straightforward and easy implementation.

CreateMessageSink:

This is an implementation of the IChannelSender interface contract. When consumer of the remote object calling the Activator class, as it is shown in the following code snippet,

 // activate a remote object

 Type interfaceType = typeof(IRemInterface);
 string objectUrl = @"msmq://./reqchannel/endpoint"; 
 IRemInterface robj = (IRemInterface)Activator.GetObject(interfaceType, objectUrl);

the call is forward to the following function of the MSMQSender class

// IChannelSender (activation)

public virtual IMessageSink CreateMessageSink(String url, Object data, out string objectURI)
{
   objectURI = null;
   string[] s = url.Split(new Char[]{'/'});

   if(s.Length < PATH_MINNUMOFFIELDS || m_ChannelName + ":" != s[0])
      return null; // this is not correct channel


   string outpath = "";
   for(int ii = 2; ii < s.Length-1; ii++) 
   {
      outpath += s[ii]; 
      outpath += "\\"; 
   }
   outpath = outpath.TrimEnd(new Char[]{'\\'});
   objectURI = s[s.Length-1];

   MSMQMessageSink msgsink = new MSMQMessageSink(this, objectURI, outpath);
   Trace.WriteLine(string.Format("Client-CreateMessageSink: url = {0}, sink = {1}", url, msgsink.GetHashCode()));
   return msgsink; 
}

to check if the specified url can be handled by this channel. If the channel name is not matching with the url field address, the null is returned. The walking process is repeated through all registered channels until a successful match is found, otherwise the error exception is thrown. In the successful case,  the MSMQMessageSink class is instated and its reference is returned to the caller.

MSMQMessageSink:

The MSMQMessageSink is derived class from the IMessageSink and IDictionary interfaces. When a method call is made on the proxy, the remoting infrastructure passing IMessage object to this class using one of the following ways:

1. SyncProcessMessage

// IMessageSink (MethodCall)

public virtual IMessage SyncProcessMessage(IMessage msgReq)
{ 
   IMessage msgRsp = null;
   Message outMsg = null;
   MessageQueueTransaction mqtx = new MessageQueueTransaction();

   try 
   {
      msgReq.Properties[OBJECTURI] = m_ObjectUri; // work around!

      // send a remoting message 

      mqtx.Begin();
      outMsg = new Message(msgReq, new BinaryMessageFormatter()); // create a message

      outMsg.ResponseQueue = m_parent.ResponseQueue; // response queue

      outMsg.TimeToBeReceived = m_parent.ResponseTimeOut; // timeout to pick-up a message

      outMsg.AcknowledgeType = AcknowledgeTypes.NegativeReceive; // notify negative receive on the client/server side

      outMsg.AdministrationQueue = m_parent.AdminQueue; // admin queue for a time-expired messages

      string label = msgReq.Properties["__TypeName"] + "." + msgReq.Properties["__MethodName"];
      m_OutQueue.Send(outMsg, label, mqtx); // transactional message 

      mqtx.Commit();
      Trace.WriteLine(string.Format("Client-Sync:PRE-CALL, msgId={0}", outMsg.Id));

      // Wait for a Return Message

      Thread.Sleep(0);
      msgRsp = ReceiveReturnMessage(outMsg.Id);
      Trace.WriteLine(string.Format("Client-Sync:POST-CALL, msgId={0}", outMsg.Id));
   }
   catch (MessageQueueException ex) 
   {
      Trace.WriteLine(string.Format("Client:SyncProcessMessage error = {0}, msgId = {1}", ex.Message, outMsg.Id));
   }
   catch(Exception ex) 
   {
      Trace.WriteLine(string.Format("Client:SyncProcessMessage error = {0}, msgId = {1}", ex.Message, outMsg.Id));
   }
   finally
   {
      if(mqtx.Status == MessageQueueTransactionStatus.Pending) 
      { 
         mqtx.Abort();
         Trace.WriteLine(string.Format("Client:SyncProcessMessage Aborted, msgId = {0}", outMsg.Id));
      }
      m_OutQueue.Close();
   }

   return msgRsp;
}

This is a synchronously invoking a remote method with a waiting for its return message within a specified time. As you can see the above code snippet, the outbound message is created and configured for its respond from the endpoint and then sent to the transactional queue. After this, the helper function ReceiveReturnMessage is called to wait for a message with a specified correlation id on the Respond queue. This function returning a reference to IMessage object, which it is returned back to the remoting infrastructure as a ReturnMessage. Note that caller is blocked during the process of the waiting for the respond message.

If the client wants to be pre-emptive with the remoting object, the process of the invoking a remote method needs to be implemented by BeginInvoke/EndInvoke design technique. In this case the following method is called:

2. AsyncProcessMessage

This function has a fire&forget design pattern implemented in two ways: either sending an outbound message without waiting for its respond (OneWay option) or delegating this call to the worker thread. In the both situation, the call is immediately return back to the remoting infrastructure. The following code snippet shows that:

public virtual IMessageCtrl AsyncProcessMessage(IMessage msgReq, IMessageSink replySink)
{
   IMessageCtrl imc = null;
   
   if(replySink == null) // OneWayAttribute

   { 
      MessageQueueTransaction mqtx = new MessageQueueTransaction();

      try 
      {
         mqtx.Begin();
         msgReq.Properties[OBJECTURI] = m_ObjectUri; // work around!

         string label = msgReq.Properties["__TypeName"] + "." + msgReq.Properties["__MethodName"];
         m_OutQueue.Send(msgReq, label, mqtx); // transacional message

         mqtx.Commit();
         Trace.WriteLine("Client-[OneWay]Async:CALL");
      }
      catch(Exception ex) 
      {
         Trace.WriteLine(string.Format("Client:AsyncProcessMessage error = {0}", ex.Message));
      }
      finally 
      {
         if(mqtx.Status == MessageQueueTransactionStatus.Pending) 
         { 
            mqtx.Abort();
         }
         m_OutQueue.Close();
      }
   }
   else
   {
      // spawn thread (delegate work)

      delegateAsyncWorker daw = new delegateAsyncWorker(handlerAsyncWorker);
      daw.BeginInvoke(msgReq, replySink, null, null);
   }

   return imc;
}

and here is a background process of the asynchronously call. This thread actually call remote method synchronously and after that it is using a replySink to update AsyncResult object state included setting its ManualResetEvent object. After this work the worker thread is going to be terminated itself.

// handler of the AsyncProcessMessage worker

private void handlerAsyncWorker(IMessage msgReq, IMessageSink replySink) 
{
   AsyncResult ar = replySink.NextSink as AsyncResult;
   // call Remote Method and wait for its Return Value 

   IMessage msgRsp = SyncProcessMessage(msgReq);
   // update AsyncResult state (_replyMsg, IsCompleted, waitstate, etc.)

   replySink.SyncProcessMessage(msgRsp);
}

MSMQReceiver

This is a derived class from the IChannelReceiver interface from the System.Runtime.Remoting.Channels namespace. The class has several constructors to config the receiver channel programmatically and one constructor for administratively configuration using the config file. Their design implementation is similarly to the MSMQSender.

The implementation of the MSMQReceiver class is simpler than MSMQSender. The listener run an endless loop in the background thread which is blocked by m_InQueue.Receive() function. When message arrived into the queue, this function will retrieve this message and delegate it to the worker thread in the fire&forget fashion using the BeginInvoke design pattern. The above is shown in the following code snippet:

// Listener

private void Run()
{
   try 
   {
      Thread.CurrentThread.IsBackground = true;

      while(true) 
      {
         // Wait for a client's call

         Message msg = m_InQueue.Receive(); // local queue!

         delegateDispatchMessage ddm = new delegateDispatchMessage(DispatchMessage);
         ddm.BeginInvoke(msg, null, null);
      }
   }
   catch(Exception ex) 
   {
      Trace.WriteLine(string.Format("RemoteObject-MSMQListener error = {0}", ex.Message));
   }
}

The actually work is done in the following background worker thread:

// Listener worker

public override void DispatchMessage(Message msg)
{
   //Message msg = null;

   MessageQueueTransaction mqtx = new MessageQueueTransaction();

   try 
   {
      // message from the client

      IMessage msgReq = msg.Body as IMessage;
      msgReq.Properties["__Uri"] = msgReq.Properties[OBJECTURI]; // work around!!!

      Trace.WriteLine(string.Format("RemoteObject:PRE-CALL, msgId={0}", msg.Id));

      // Dispatch message to the remoting object

      IMessage msgRsp = ChannelServices.SyncDispatchMessage(msgReq);
      if(msgRsp != null) 
      {
         // update this msg with the return value and send it back to the caller

            mqtx.Begin();
            msg.BodyStream.Close(); // clean-up

            msg.Body = msgRsp; // serialize IMessage

             msg.CorrelationId = msg.Id; // cookie

            msg.ResponseQueue.Send(msg, "RET: " + msg.Label, mqtx); // send message

            mqtx.Commit();
            msg.ResponseQueue.Close();
            Trace.WriteLine(string.Format("RemoteObject:POST-CALL, msgId={0}", msg.CorrelationId));
      }
      else
            Trace.WriteLine(string.Format("RemoteObject-[OneWay]: msgId = {0}", msg.Id)); // dump MessageId

   }
   catch(MessageQueueException ex) 
   {
      Trace.WriteLine(string.Format("RemoteObject error = {0}, msgId = {1}", ex.Message, msg.Id));
   }
   catch(Exception ex) 
   {
      Trace.WriteLine(string.Format("RemoteObject error = {0}, msgId = {1}", ex.Message, msg.Id));
   }
   finally
   {
      if(mqtx.Status == MessageQueueTransactionStatus.Pending) 
      { 
         mqtx.Abort();
      }
   }
}// DispatchMessage

The primary goal of the above function is to dispatch an arrived IMessage object to the remoting infrastructure in the synchronously manner. The "dispatcher" will return back a return message represented by IMessage object.  If the return value is null than nothing to do and thread will be exited, that's the situation of the OneWay attributed remote method. In the other hand, the IMessage object is serialized into the message body and send it back to the Respond queue. Note that setting a message CorrelationId by its Id is very important for the Sender side - this is a callback cookie, which the Sender waiting for it. 

Test

Testing the MSMQ Remoting Channel (MSMQChannelLib) can be done replacing your current channel by the MSMQ channel or using the included package of the following assembly:

The Remoting object is very simply class using the Interface contract to make a loosely coupled client/server design.

Remoting Object

using RemoteInterface; // interface abstract definition


namespace RemoteObject
{
   public class RemObject : MarshalByRefObject, IRemInterface 
   {
      public RemObject()
      {
      }

      public string SayHello(string msg)
      {
         Console.WriteLine("RemObject.SayHello({0})", msg);
         return "Hello" + " " + msg;
      }

      [OneWay]
      public void OneWay(string msg)
      {
         //Thread.Sleep(3000);

         Console.WriteLine("RemObject.OneWay({0})", msg);
      }
   }
}
 The server and client are also very straightforward and simply. Before starting the test the following assemblies have to be installed in the GAC:

also you must create manually the following MSMQ queues:

Note that this test can be run also using only one machine as a server and client.

After all the above checks, start the server host program and then client program. The client program will ask you to write a number of the test loops, just write 10 and press Enter. You will see all remoting processes how fast running on the server and client console programs. Note that client is running test for each remote method synchronously and asynchronously. After all these test loops, press Enter and the client console will display a test result. It should by 100 for both of them.

You can play more test such as open more server host programs or more client programs and also you can change a channel configuration in the their config files.

Conclusion

In this article has been described how to design, implement and plug-in the MSMQ into the remoting infrastructure. Beside the standard remoting channel such as http and tcp, the MSMQ remoting channel allows to call a remoting object in the loosely coupled design pattern, which is very important in the mobile application. Design and implementation of the Custom Remoting channel using .Net Framework classes is simple and straightforward for any kind of physical channel such can be as serial RS232, USB, X25, modem, etc.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
Generalproblem:for Deserialize Pin
xiaoliangbo
18:57 10 Aug '09  
GeneralReceiver (Server) question Pin
Madmaximus
6:31 13 Nov '06  
QuestionHelp required on remote objects in C# Pin
kschakravarthy
21:33 9 Oct '05  
AnswerRe: Help required on remote objects in C# Pin
Anonymous
4:55 10 Oct '05  
Questionremoting as peer to peer application Pin
shadheel
22:27 1 Oct '05  
QuestionMSMQ with Public Queue Pin
scienty77
5:01 29 Sep '05  
GeneralThe method was called with a Message of an unexpected type Pin
majeti
23:21 13 Sep '05  
GeneralThe method was called with a Message of an unexpected type Pin
cheelam_mze
3:51 11 Jun '05  
GeneralRe: The method was called with a Message of an unexpected type Pin
jesapp
5:31 20 Jul '05  
GeneralRe: The method was called with a Message of an unexpected type Pin
Madmaximus
11:52 28 Dec '05  
GeneralRe: The method was called with a Message of an unexpected type Pin
erojasoviedo
11:28 28 Sep '06  
GeneralRe: The method was called with a Message of an unexpected type Pin
Pangag
4:19 29 Aug '07  
GeneralNon-Windows Client Pin
yofnik
3:46 22 Mar '05  
General.NET Remoting Channels (Custom) Pin
Aisha Ikram
2:48 21 Apr '04  
GeneralFile or assembly name RemoteObject, or one of its dependencies, was not found. Pin
Earl Damron
14:59 15 Mar '04  
GeneralRe: File or assembly name RemoteObject, or one of its dependencies, was not found. Pin
nls
16:38 15 Mar '04  
GeneralRe: File or assembly name RemoteObject, or one of its dependencies, was not found. Pin
majeti
0:55 14 Sep '05  
GeneralTimeout exception not correct Pin
reginald_blue
13:31 9 Nov '03  
GeneralUnsupportedOperation Error on Win2003 Pin
ankors
2:19 28 Oct '03  
Generalmultiple recipients and callbacks Pin
Amangie
12:42 27 Oct '03  
GeneralRe: multiple recipients and callbacks Pin
Roman Kiss
18:24 27 Oct '03  
Generalhow to send and receive from msmq ? Pin
r5500
20:46 1 Oct '03  
GeneralMSMQ and Offline server Pin
Gery Dorazio
9:46 21 Sep '03  
GeneralNon-transactional modification ? Pin
Anonymous
9:30 2 Jul '03  
GeneralRe: Non-transactional modification ? Pin
Roman Kiss
11:00 2 Jul '03  


Last Updated 10 Dec 2001 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009