Introduction
Synchronizing MQ GETs and PUTs with C#
Legacy MQ code is written in C/C++. In a C++ program, you would link MQM.LIB and call its methods to do a GET/ PUT. The techniques of synchronization between a simple GET REQUEST/ PUT RESPONSE processor program and another simple PUT REQUEST/ GET RESPONSE client program are age old. But what .NET MQ developers would die for is a simple step by step guide on how to implement those well known techniques in C#. Well, IBM has provided their .NET equivalent library of MQM: AMQMDNET.DLL. This article will show you how to use this library to write simple MQ programs in C#.NET. I am thankful to another CodeProject article by John Concannon which you can find here. I have used a lot of his code - as such his article is enough to understand the synchronization part of it. What I have added is the concept of having "synchronization" under the umbrella of a single program. The sample application spawns a user thread that behaves as the server. Then, it behaves as the client. With a few mouse clicks, you can control the way the client and the server interact. Synchronization will be more visible and controlled.
Pre-requisites
If you have not installed WebSphere MQ, you are probably not going to need this article. I have worked with IBM WebSphere 5.3. IBM also provides the ".NET interoperability platform" along with 5.3. That, of course, needs to be installed - but you don't have to worry about that if you have WebSphere 5.3 or higher installed. Next, you will need AMQMDNET.DLL. If you don't find it, I have included it in the demo project zip file. Well, once you are ready with all the software you need, create your MQ Queue Manager and at least two queues. One will be the request queue and the other, the response queue. Note down the names of the queue manager and the queues. When the demo application is run, its UI, by default will contain the names that I had used during development. What you can do is, you can modify the code (the form) to put in your own values, so that you don't need to type them again.
MQ service must be running - (that goes without saying) for this piece of code to work.
Using the code
Well, the best way to understand how to use this code is to understand what the demo application does. You have two distinct group boxes - one for GET REQUEST PUT BACK REPLY (= GRPBR) and the other for PUT REQUEST GET BACK REPLY (= PRGBR). The former is the server and the latter the client. For meaningful functioning, you have to start the server first - so (after you have downloaded the demo program and kept the EXE in the same folder with amqmdnet.dll, which accompanies the EXE in the zip) go ahead and click the GET button. If you have typed in the correct queue manager and the request queue name, the GET button click will spawn a thread which is the "server" thread. It will start polling the request Queue for an incoming message. Keeping the "wait infinitely" box checked before clicking GET will ensure that the server runs infinitely, so that you can relax and trigger the client. Note that had I not done this GET operation in a user thread, the application would have hung, and you would not have been able to interact with the GUI any more to test the client.
So, now that somebody is listening for an incoming message - let us PUT a message. The PRGBR group box contains the PUT button. Type in the correct queue manager and queue names, and type in some request message text, and click PUT. Immediately, the message will be picked up by the server thread. The thread will exit, and the message will be displayed in the GRPBR "Request Text" box. The server, before terminating, will reply back with some message (the text of which you can supply in the UI), and the client will get it back. But before the client picks up the reply, it will ask you - "do you want me to poll for a reply"? I added this message just in case you are testing the client without spawning the server, so that you can click CANCEL in that case to avoid a wait you know will be futile.
The dialog box has instructions at its bottom pane on how to test this application in two ways - simple and complex. I just described the complex method above. The CS file MQRCText.CS was borrowed from John Concannon - you need it to show/log meaningful errors.
Here is the client processing code, abbreviated for pasting here with the catch
blocks cut short (PRGBR PUT button click): The running comments identify the "steps" clearly:
try
{
mqQMgr = new MQQueueManager( strQueueManagerName );
}
catch( MQException mqe )
{
string strError = mqrcText.getMQRCText( mqe.Reason );
MessageBox.Show( "Error trying to create Queue " +
"Manager Object. Error: " + mqe.Message +
", Details: " + strError );
return;
}
try
{
requestQueue = mqQMgr.AccessQueue( strRequestQueueName,
MQC.MQOO_OUTPUT
+ MQC.MQOO_FAIL_IF_QUIESCING );
}
catch( MQException mqe )
{
}
try
{
responseQueue = mqQMgr.AccessQueue( strResponseQueueName,
MQC.MQOO_INPUT_AS_Q_DEF
+ MQC.MQOO_FAIL_IF_QUIESCING );
}
catch( MQException mqe )
{
}
try
{
requestMessage = new MQMessage();
requestMessage.WriteString( strRequestText );
requestMessage.Format = MQC.MQFMT_STRING;
requestMessage.MessageType = MQC.MQMT_REQUEST;
requestMessage.Report = MQC.MQRO_COPY_MSG_ID_TO_CORREL_ID;
requestMessage.ReplyToQueueName = strResponseQueueName;
requestMessage.ReplyToQueueManagerName = strQueueManagerName;
requestQueue.Put(requestMessage);
if( requestQueue.OpenStatus )
requestQueue.Close();
if( MessageBox.Show("Request Message PUT successfully.\nOK: " +
"wait for RESPONSE, Cancel: skip waiting", "Do you want to " +
"wait for response?", MessageBoxButtons.OKCancel ) ==
DialogResult.Cancel)
{
if( responseQueue.OpenStatus )
responseQueue.Close();
if( mqQMgr.ConnectionStatus )
mqQMgr.Disconnect();
return;
}
}
catch( MQException mqe )
{
}
try
{
responseMessage = new MQMessage();
responseMessage.CorrelationId = requestMessage.MessageId;
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.Options = MQC.MQGMO_WAIT;
if( nud_PRGBR_WaitMS.Enabled == true )
gmo.WaitInterval = ( int ) nud_PRGBR_WaitMS.Value;
else
gmo.WaitInterval = MQC.MQWI_UNLIMITED;
gmo.MatchOptions = MQC.MQMO_MATCH_CORREL_ID;
responseQueue.Get(responseMessage, gmo);
tb_PRGBR_RESPONSE_TEXT.Text =
responseMessage.ReadString(responseMessage.MessageLength);
}
catch( MQException mqe )
{
}
if( responseQueue.OpenStatus )
responseQueue.Close();
if( mqQMgr.ConnectionStatus )
mqQMgr.Disconnect();
return;
Here is the server processing code, abbreviated again (GRPBR GET button click, executed in the user thread): The running comments identify the "steps" clearly:
try
{
mqQMgr = new MQQueueManager( strQueueManagerName );
}
catch( MQException mqe )
{
}
try
{
requestQueue = mqQMgr.AccessQueue( strRequestQueueName,
MQC.MQOO_INPUT_AS_Q_DEF
+ MQC.MQOO_FAIL_IF_QUIESCING );
}
catch( MQException mqe )
{
}
try
{
requestMessage = new MQMessage();
MQGetMessageOptions gmo = new MQGetMessageOptions();
gmo.Options = MQC.MQGMO_WAIT;
if( nud_GRPBR_WaitMS.Enabled == false )
gmo.WaitInterval = MQC.MQWI_UNLIMITED;
else
gmo.WaitInterval = ( int ) nud_GRPBR_WaitMS.Value;
gmo.MatchOptions = MQC.MQMO_NONE;
requestQueue.Get(requestMessage, gmo);
tb_GRPBR_REQUEST_TEXT.Text =
requestMessage.ReadString(requestMessage.MessageLength);
}
catch( MQException mqe )
{
}
try
{
responseQueue =
mqQMgr.AccessQueue(requestMessage.ReplyToQueueName,
MQC.MQOO_OUTPUT, requestMessage.ReplyToQueueManagerName,
null, null);
}
catch( MQException mqe )
{
}
try
{
responseMessage = new MQMessage();
MQPutMessageOptions pmo = new MQPutMessageOptions();
pmo.Options = MQC.MQPMO_NONE;
if ((requestMessage.Report & MQC.MQRO_PASS_MSG_ID) ==
MQC.MQRO_PASS_MSG_ID)
responseMessage.MessageId = requestMessage.CorrelationId;
else
pmo.Options = MQC.MQPMO_NEW_MSG_ID;
if ((requestMessage.Report & MQC.MQRO_PASS_CORREL_ID) ==
MQC.MQRO_PASS_CORREL_ID)
responseMessage.CorrelationId = requestMessage.CorrelationId;
else
responseMessage.CorrelationId = requestMessage.MessageId;
responseMessage.MessageType = MQC.MQMT_REPLY;
responseMessage.WriteString( strResponseText );
responseQueue.Put(responseMessage);
if( responseQueue.OpenStatus )
responseQueue.Close();
if( requestQueue.OpenStatus )
requestQueue.Close();
if( mqQMgr.ConnectionStatus )
mqQMgr.Disconnect();
btn_GRPBR_ABORTWAIT.Enabled = false;
return;
}
catch( MQException mqe )
{
}
Points of interest
This sample is definitely not thread safe. Imagine a client program that has multiple threads in it. If the threads PUT their requests and try to GUT the response from the same queue at the same time, then you have to do time slicing on your GETs. Calling a GET with a wait time of 10 seconds (worse infinite) will not work - say the server replied to one of the requests, but did not reply to the other. But the thread for which there was no reply is waiting with a GET with a wait time of say 30 seconds. It will wait for 30 seconds, while the other thread will do nothing, but had the other thread called the GET and not this one, it would have got the reply immediately, as it was lying there. Obviously, this assumes that you have called lock
around your GET call. So calling GET with a big wait time can degrade the performance in a multi threaded environment. In that case you have to write more manipulative code - and that is called "time slicing" (Actually I call it time slicing, I don't know whether that is an iconized way of calling it).
I am currently writing a C# DLL that provides such time slicing functionalities. Any help is welcome!
History
- V 1.0 - A simple sample. The targeted V 1.1 is a time slicer!