Copyright © 2000, Bits & Bytes Programming, Inc., ALL RIGHTS RESERVED

Table of Contents

Application to Application Communication............................................................ 2

Features of MQSeries.......................................................................................... 3

A Better Data Queue?...................................................................................... 3

MQSeries Infrastructure...................................................................................... 4

Configuration Objects...................................................................................... 4

Command and Control..................................................................................... 5

MQSeries Programming....................................................................................... 6

MQSeries API Calls............................................................................................. 7

MQSeries API Parameters.................................................................................... 8

MQSeries Data Structures................................................................................... 9

Working with the data structures...................................................................... 9

Module CMQG................................................................................................ 10

MQSeries Sample Programs............................................................................... 11

MQSeries Sample: NT Server to AS/400............................................................. 12

The RPG Server Program................................................................................ 13

D-specs...................................................................................................... 13

Customer data structure.............................................................................. 13

MQSeries data structures............................................................................. 13

Calls to MQSeries functions - MQCONN.......................................................... 13

Calls to MQSeries functions - MQOPEN.......................................................... 14

Calls to MQSeries functions - MQGET............................................................ 14

Calls to MQSeries functions - MQPUT............................................................. 15

The Visual Basic Client Program...................................................................... 15

Conclusion.................................................................................................... 16

References.................................................................................................... 16

 


Application to Application Communication

When you design systems that will be hosted entirely on one AS/400 system, the right tools are usually evident: DB2 UDB for AS/400, compiled languages such as ILE RPG, ILE COBOL or Java, and DDS for the user interface. When an application is split across systems, the options become more complicated. If your application uses multiple AS/400 systems, you might use remote data queues, distributed data management (DDM), or low-level communications programming, such as APPC or sockets.

Application design and implementation is especially challenging when you work with different platforms. For example, many companies use Windows NT Server for various applications and need access to the AS/400 database. In other cases, a Unix or AIX machine might be used as a Web server, with a requirement to work with AS/400 systems on the backend. When you cross the platform boundary, your choices become more difficult. Vendor provided solutions are usually satisfactory for only one side of the application. As a system designer and implementer, you have to locate or create the middleware that will let the two platforms work effectively with each other.

IBM provides an effective and comprehensive offering called IBM MQSeries (Message Queuing Series). The “series” part refers to the implementation of IBM MQSeries products on over 30 different platforms, many of which are not IBM products. For example, IBM MQSeries software is available for the entire IBM product line of servers, in addition to other servers such as Microsoft Windows NT Server, Sun Microsystems Solaris, and HP-UX.

Part of the reason why IBM MQSeries is enjoying a surge in popularity and interest is because of the e-business driven nature of new applications. In the past, it was possible for a company to deploy many different types of servers, which only needed to communicate with each other occasionally. With many e-business applications, the requirement now is for systems to communicate in real-time or near real-time, automatically. Without a solution like IBM MQSeries, you would have to design and implement a protocol on separate platforms that would let you send requests and process responses from other systems. As anyone who has deployed production-level communication applications can attest, developing those types of systems is not trivial. More importantly, time and resources that are expended to develop and test low-level communications code can be better used for the application itself.

Features of MQSeries

Fundamentally, IBM MQSeries is a messaging system. That means that you identify a message sender and message receiver, and create the content of your message. When you give the message to IBM MQSeries, it is responsible for delivering the message to the receiver and reporting the success or failure of the delivery to your application. If the delivery fails, IBM MQSeries provides you with enough information to determine the cause of the failure.

Unlike communications programs, IBM MQSeries is by design an asynchronous message delivery system. That means that it is not necessary for the receiver application to be active when the sender application sends the message. IBM MQSeries uses message queues to implement its asynchronous delivery capability. When the sender application sends a message, it is placed into a specified message queue. The message is actually delivered when the receiver application requests either the next or a specific message from the message queue.

A Better Data Queue?

If you have worked with AS/400 data queue objects, you will notice many similarities between data queues and IBM MQSeries message queues. In fact, the underlying concepts are quite similar. If you have implemented data queue applications, it would be relatively trivial to convert the application to use IBM MQSeries.

The question is, why would you use IBM MQSeries, which is a chargeable AS/400 Licensed Program Product (LPP), when data queues is available with OS/400? The primary reason is because data queues is strictly an AS/400 system construct. Although data queue support is provided to Windows clients in IBM’s Client Access series of products, there is no equivalent on other platforms.

A more important reason is that IBM MQSeries provides more structure to the actual handling of the message. Although it is up to you to determine the message content (as with data queues), IBM MQSeries provides assurance of delivery and traceability of the movement of the message. If you use data queues, you would need to provide that functionality yourself, through additional programming.


MQSeries Infrastructure

IBM MQSeries is available on the AS/400 system as LPP IBM MQSeries for AS/400, Version 5.1 (5733-A38). After installing the product, you need to perform several configuration steps. Those steps must be coordinated with the remote system you are using, since there are several objects to be configured that must be coordinated between the two systems. After installing and configuring, you can develop application programs using the IBM MQSeries API calls. You control IBM MQSeries on the AS/400 system either through IBM MQSeries commands or CL commands.

Configuration Objects

IBM MQSeries has three main configuration objects that you must be familiar with:


Command and Control

Although IBM MQSeries provides a command interface that is used to start, end and control IBM MQSeries objects, you will probably use the AS/400 CL commands provided with the product. You can perform all of the required control functions using the CL commands, which provide a familiar interface for AS/400 programmers and operators.

The IBM MQSeries command interface is common to all platforms that host IBM MQSeries, and provides what amounts to a PC or Unix-like command line series of commands. If you will be working with IBM MQSeries on different platforms, it may be useful to become familiar with the IBM MQSeries command interface. However, you will probably find that the different platforms provide an alternative to the IBM MQSeries command interface, just as the AS/400 CL commands are the preferred alternative on the AS/400 system. For example, the Windows NT Server version of IBM MQSeries provides an easy to use GUI tool for configuring and controlling the IBM MQSeries objects on that platform.

 


MQSeries Programming

The concepts of MQSeries programming are similar, no matter which platform you are programming on. The general flow of an MQSeries application is as follows:

 


MQSeries API Calls

 


MQSeries API Parameters

For most of the MQSeries calls, you will supply parameters in the following order:

·        OK – the call completed successfully.

·        Warning – the call completed, but with warnings. The results may or may not be usable.

·        Failed – the call did not complete successfully.

 


MQSeries Data Structures

The MQSeries data structures are used to provide lists of parameter values to the MQSeries calls. An MQSeries call may use one or more of the MQSeries data structures. The data structures are probably the most important part of MQSeries programming to become familiar with, since the values of the subfields are used to control MQSeries calls and to extract information about the call after it completes. Table 1 shows some of the MQSeries data structures that you will commonly use.

Data Structure

Description

Used with

MQGMO

Get message options

MQGET

MQMD

Message descriptor

MQGET, MQPUT, MQPUT1

MQOD

Object descriptor

MQOPEN, MQPUT1

MQPMO

Put message options

MQPUT, MQPUT1

Table 1: Some of the MQSeries data structures and the calls they are used you.

Working with the data structures

You will usually use /COPY statements to include standard MQSeries data structures and named constants. The actual copy members are in QMQM/QRPGLESRC. The MQSeries data structure copy modules are somewhat unusual, in that they do not include the “ds” statement that defines the start of the data structure. IBM omitted that statement so that you can assign your own data structure name.


Module CMQG

The MQ Constants module CMQG is one of the most important modules to become familiar with. Although you may be tempted to just define your own MQSeries constants, rather than include the module (which is over 1200 lines long and contains hundreds of definitions), you will find it to be advantageous to simply include the module “as-is”. As you start to refine and embellish an MQSeries program, you will be using more of the constants.

IBM uses the old RPG convention of limiting names to six characters or less, even though ILE RPG allows very long field names. For example, the MQSeries constant used in other languages to indicate that a get call is to wait for an unlimited amount of time is MQWI_UNLIMITED, whereas the RPG version is WIULIM. Although it may be possible to parse the meaning of the RPG constant with some practice, the bigger problem is that there is a greater chance of variable name collisions, especially if you are still using short names in your code. Although it would probably take several days to complete, you might find it worthwhile to create a CMQG_LONG module with the long constant names if you will be doing a lot of MQSeries programming with RPG.


MQSeries Sample Programs

IBM provides over a dozen sample RPG programs to demonstrate how to use MQSeries API calls. The sample programs are located in QMQMSAMP/QRPGLESRC, which is installed when you install the MQSeries LPP on your AS/400.

There are two sets of samples provided:

 


MQSeries Sample: NT Server to AS/400

To demonstrate the programming used with MQSeries, I created a simple client/server program, using an ILE RPG program on the AS/400 and a Visual Basic program on Windows NT Server. To test the program, I created the form shown in Figure 1. Upon starting the Visual Basic program, I can enter a customer number and click the Get Customer Data command button. That sends a formatted message to the AS/400 system, using MQSeries. The AS/400 RPG program receives the incoming customer number, retrieves customer data from the database, formats a response message, and uses MQSeries to send the response back to the Visual Basic program. Upon receiving the response message, the Visual Basic program extracts customer data from the message and formats the data for display, as shown in Figure 1.

MQ01.BMP

Figure 1: The MQSeries ActiveX tester, showing results returned from the AS/400.

It is unlikely that you will use the ActiveX control for a program like this on a Windows NT Server platform. More likely, you will want to use the ActiveX control to get data from the AS/400 for use in other applications, such as an Active Server Page, which returns a response to a Web browser. Nevertheless, you will find it to be tremendously helpful to create “scaffolding” applications like this to test and debug the MQSeries flow before embedding the code in an ASP application. ASP applications are typically rather difficult to debug, whereas you can use the Visual Basic development environment itself to run and test the scaffolding application.

The database file used is the famous QCUSTCDT file, which is a sample customer master file in library QIWS. For the tests, I created a logical file using the CUSNUM field in QCUSTCDT.

The RPG Server Program

The complete RPG server program is shown in Figure 2. The following sections point out some of the main constructs you need to be aware of.

D-specs

In the D-specs that define variables, the buffer field is defined as a zoned decimal field, length 6, based on the ptrBuffer field. This field is used to receive the customer number entered on the Visual Basic test form (see Figure 1). The reason why this is a based field is because the MQSeries get call uses a pointer to point to the buffer (area of storage) where you want the data retrieved from the get to be available. By using a pointer, you can easily retrieve messages of varying lengths. Before issuing the get call, you indicate the length of the buffer that you are allocating for the call and the location of the buffer. The get call retrieves the message data and makes it available to you in the variable that is pointed to by the basing pointer. In this example, the basing pointer points to a simple single field variable. For more involved messages, the basing pointer could be associated with a data structure that provides a template to parse the message content.

The numeric fields in the D-specs are all defined as Integer 10 fields. This is the standard numeric field format used in MQSeries across all platforms. The fields are used as parameters on the various MQSeries API calls.

Customer data structure

The customer data structure is used to define the actual layout of the message that will be sent from the RPG program to the requester. It is absolutely imperative that you have byte-by-byte agreement between the layout in the client and the server program.

MQSeries data structures

The /COPY statements are used to include standard MQSeries data structures and named constants. The actual copy members are in QMQM/QRPGLESRC. The “ds” statements immediately before the /COPY statements are used to assign a name to the data structures.

Calls to MQSeries functions - MQCONN

The C-specs are a sequence of calls to the MQSeries functions. The first call to MQCONN is used to connect to the queue manager, which on my AS/400 system is named QM400. Note that the call uses four parameters, the first being the queue manager name, followed by return parameters of the handle to the connection (an identifier to the connection), a completion code and reason code. In MQSeries, completion codes can be OK, WARN or FAIL. If more information is needed to determine the status of the call, the reason code is used. One nice thing about MQSeries is that the reason codes are the same across all platforms, so as you become familiar with various problems and failures on one platform, you can take that same knowledge to the next platform. Although the RPG sample does not include any error handling following the call, you will need to include error handling in production programs. For example, if the queue manager has not been started (with the Start Message Queue Manager, STRMQM command), the call to MQCONN will fail. In a production program, you will want to check for a WARN or FAIL condition after the call, and use the reason code to determine the exact cause of failure. If you have a FAIL condition, it does not make any sense to continue with the other calls, since you will not have a valid connection to the queue manager.

Calls to MQSeries functions - MQOPEN

The two MQOPEN calls are used to open the connection to the input queue (NT.To.AS400) and the output queue (AS400.To.NT). The field ODON is defined in the MQOD (Object Descriptor) data structure, and is used to identify the queue to be opened. The queueOptions field is a numeric field that is the sum of various options that you want applied to the queue. For example, the input queue uses the options OOINPQ (open as an input queue) and OOFIQ (fail if quiescing, i.e., fail if MQSeries is being brought down). The values OOINPQ and OOFIQ are defined as named constants in the CMQG include module.

As with the MQCONN call, MQOPEN returns a handle (to the queue) and the completion code and reason code. Assuming that the three calls (MQCONN and two MQOPEN) complete successfully, the program is now ready to receive and send messages using MQSeries.

Calls to MQSeries functions - MQGET

In the MQGET section, I start by setting up the fields used for the buffer. The buffer length is passed to MQGET to tell MQSeries how much storage is set aside for its use to write retrieved message data into. If you don’t set aside enough storage (in other words, the buffer is too short), MQSeries returns a reason code that indicates that not all of the message data is available in the buffer. In that case, you can issue the MQGET again to retrieve the rest of the buffer. Obviously, it is simpler to just retrieve the entire buffer in one call, but in some cases, you may not be able to predict how big a buffer you will need in advance. In this case, I am expecting a buffer that is six bytes long (the length of the customer number), so I set the buffer length then allocate that many bytes. The buffer is located at the address pointed to by the ptrBuffer field, which is the basing pointer for the field buffer. Although this effort may seem unnecessary for a simple field, you do need to use this technique, since the MQGET call requires a pointer so that it knows where in storage it is to copy the retrieved message to.

The GMOPT and GMWI fields are part of the MQGMO (Get Message Options) data structure. The GMOPT field is another numeric sum of options, which indicates the following:

The actual MQGET call takes six input parameters and returns three output parameters, in addition to the message data at the location pointed to by ptrBuffer. The MQMD data structure is the message descriptor, which contains information about the originator of the message and fields that can be used to identify the message. In this example, I was able to use default values for all of the subfields in the MQMD data structure. The actual number of bytes retrieved from the message is available in the dataLength parameter.

Now that the buffer is available, it is used to chain to the QCUSTCDT1 file to retrieve the customer record by customer number. The file retrieval and field processing is traditional RPG file processing, with data from the file being moved to fields in the customerData data structure for return to the client application.


Calls to MQSeries functions - MQPUT

The MQPUT section starts by setting up the buffer that will be used in the call. In this sample, the data being put is in the customerData data structure, so the length of that data structure is the buffer length. Because MQPUT also uses a pointer, I assign the address of the data structure to the ptrBuffer field. Using those two values (the length and the pointer), the MQPUT call can move the data from my program to the queue, where it is then transmitted to the client.

The PMOPT field is part of the MQPMO (Put Message Options) field. In this case, the only option used is the fail if quiescing option.

The MQPUT call uses six input parameters and two output parameters to indicate the success or failure of the call.

The Visual Basic Client Program

Compared to the RPG code, the Visual Basic code is much simpler. All of the code to process the Get Customer Data command button is shown in Figure 3.

The ActiveX control provides a number of classes for MQSeries functionality, some of which are used in the sample. The procedure starts by defining objects based on the classes.

As in the RPG program, the Visual Basic program starts by connecting to the Queue Manager (on the Windows NT Server, I called my queue manager “QMNT”). It then opens the input queue and the output queue, using similar options to those used in the RPG program.

At the point where this procedure is called, the user has already entered a customer number on the form, so the next section moves the customer number into the message buffer that will be sent to the AS/400. Because the customer number is entered in ASCII, it needs to be converted to EBCDIC. Character set translation is somewhat quirky in MQSeries, and has a number of conditions that are imposed if automatic translation is used. In my sample, I wanted to use the WriteString method of the MQMessage class, so I needed to indicate that I wanted to translate to character set 37 (EBDCIC). The alternative would be to simply write the ASCII string to the buffer and perform the translation within the RPG program.

The MQSeries manuals dance around this issue of translating between character sets, but seem to come down on the side of translating within the receiving program. That is probably the best approach, since the client program is then free to communicate with either an ASCII-based or EBCDIC-based server, without having to hardcode the translate-to character set, as shown in the example. However, I chose to simplify things by simply indicating that I wanted to translate to EBCDIC.

Oddly enough, you don’t need to explicitly perform character set translation to return data from the AS/400 to the Windows NT Server. On the AS/400 channel definition, you can specify the Convert Message (CVTMSG) parameter value, which automatically translates from AS/400 EBCDIC to the required character set used by the receiving application. Quite maddeningly, the Windows NT Server version of MQSeries includes a similar option on its channel definition, but the option is not implemented. That accounts for the requirement to perform character set translation in code.

After putting the translated customer number into the output message (with the WriteString method), the Put method of the MQQueue class is called. That method sends the message on the associated queue.

After sending the message, the Visual Basic program sets up and uses the Get method of the MQQueue class to receive the reply from the AS/400 system. The options for the get are MQGMO_WAIT (wait for a message) and MQGMO_CONVERT (convert to the native character set). The wait interval is set to MQWI_UNLIMITED.

The actual message is retrieved into the inputMsg object, which is an instance of the MQMessage class. The advantage of receiving the message into the inputMsg object, rather than into a simple character buffer, is that I can use the ReadString method of the MQMessage class to parse the returned data. The parsing is shown following the call to the Get method. Upon returning from the Get, the offset to the data is set to the first byte (“zeroeth” byte, since the position is zero-based). The ReadString method retrieves the number of bytes specified, starting at the offset position, then increments the offset position to the beginning of the next field. Using this method, I can simply read the number of bytes required, rather than be concerned with starting and ending positions. As soon as I parse a field, I move it to the display field defined on the Visual Basic form (with some formatting done for the numeric fields).

Conclusion

Although I have purposely simplified the code samples as much as possible, there is a lot of prerequisite knowledge that you will need to successfully implement an MQSeries application. For example, you need to be familiar with the Message Descriptor, Get Message Options and Put Message Options structures, since those give you complete control over how your messages are handled. Although many people seem to be moving away from printed manuals, I found it to be necessary to have the actual hardcopy manuals at hand when creating MQSeries applications. I would not attempt to code an MQSeries program without having the manuals at hand, and I suggest that you do not start without them.

One of the virtues of MQSeries is that it provides a common meeting ground for programmers working on different platforms. For example, if you know RPG but do not know Visual Basic and ActiveX, you can work with a Visual Basic programmer to specify how your application is to work. Apart from the MQSeries configuration work (where you need to have common channel names), the main point of discussion you will have with the programmer on “the other side” is the format and content of the messages you are exchanging. Because MQSeries reports successes and failures in the same terms no matter what the platform, you also have more common ground to work with when things don’t work out.

IBM is putting increasing emphasis on MQSeries as one of their application enablers. Although it is not required for many applications, in cases where you do need cross platform capabilities, you will find it to be a very fitting tool.

References

MQSeries Application Programming Reference (SC33-1673)

MQSeries Application Programming Guide (SC33-0807)

MQSeries Intercommunication (SC33-1872)

MQSeries for AS/400 Administration Guide V4R2M1 (GC33-1956)

MQSeries for AS/400 Application Programming Reference (RPG) V4R2 (SC33-1957)

MQSeries for Windows NT V5R1: Using the Component Object Model Interface (SC34-5387)


      **************************************************************************

      * Program EJMQ - demonstration of MQSeries calls

      *

      * This program performs the following functions:

      *

      *  1. Connect to the Queue Manager

      *  2. Open an input and output queue

      *  3. Retrieve an input message (customer lookup)

      *  4. Retrieve the customer record from the database

      *  5. Send an output message of customer data

      **************************************************************************

 

     Fqcustcdt1 if   e           k disk

 

      **************************************************************************

      * Define variables used for MQ calls

      **************************************************************************

     D buffer          s              6S 0  based(ptrBuffer)

     D bufferLength    s             10I 0

     D completionCode  s             10I 0

     D dataLength      s             10I 0

     D hConnection     s             10I 0

     D hInputQueue     s             10I 0

     D hOutputQueue    s             10I 0

     D ptrBuffer       s               *

     D queueMgrName    s             48

     D queueOptions    s             10I 0

     D reasonCode      s             10I 0

 

      **************************************************************************

      * Define customer data message format to send to requester

      **************************************************************************

     D customerData    ds

     D   customerNo                   6S 0

     D   initials                     3

     D   lastName                     8

     D   creditLimit                  4S 0

     D   balanceDue                   6S 2

     D   creditDue                    6S 2

 

      **************************************************************************

      * Define data structures used for MQ calls

      *  MQOD  - object descriptor

      *  MQMD  - message descriptor

      *  MQGMO - get message options

      *  MQPMO - put message options

      **************************************************************************

     D MQOD            ds

     D/COPY CMQODG

 

     D MQMD            ds

     D/COPY CMQMDG

 

     D MQGMO           ds

     D/COPY CMQGMOG

 

     D MQPMO           ds

     D/COPY CMQPMOG

 

 


      **************************************************************************

      * MQ constants

      **************************************************************************

     D/COPY CMQG

 

      **************************************************************************

      * Set Queue Manager name, connect to Queue Manager

      **************************************************************************

 

     C                   eval      queueMgrName = 'QM400'

 

     C                   callp (e) mqconn(queueMgrName   :

     C                                    hConnection    :

     C                                    completionCode :

     C                                    reasonCode)

 

      **************************************************************************

      * Set Input Queue name, open Input Queue

      **************************************************************************

 

     C                   eval      ODON         = 'NT.To.AS400'

 

     C                   eval      queueOptions = OOINPQ +

     C                                            OOFIQ

 

     C                   callp (e) mqopen(hConnection    :

     C                                    MQOD           :

     C                                    queueOptions   :

     C                                    hInputQueue    :

     C                                    completionCode :

     C                                    reasonCode)

 

      **************************************************************************

      * Set Output Queue name, open Output Queue

      **************************************************************************

 

     C                   eval      ODON         = 'AS400.To.NT'

 

     C                   eval      queueOptions = OOOUT +

     C                                            OOFIQ

 

     C                   callp (e) mqopen(hConnection    :

     C                                    MQOD           :

     C                                    queueOptions   :

     C                                    hOutputQueue   :

     C                                    completionCode :

     C                                    reasonCode)

 

 


      **************************************************************************

      * Call MQGET to retrieve the request from NT Server

      *

      * MQGMO.GMOPT - GMWT   - wait for message to arrive on queue

      *               GMCONV - convert message data

      *               GMFIQ  - fail if MQ is quiescing

      *

      * MQGMO.GMWI  - WIULIM - wait unlimited for receipt of message

      **************************************************************************

 

     C                   dow       '1' = '1'

 

     C                   eval      bufferLength  = %len(buffer)

     C                   alloc     bufferLength  ptrBuffer

     C                   eval      buffer        = 0

 

     C                   eval      GMOPT = GMWT   +

     C                                     GMCONV +

     C                                     GMFIQ

 

     C                   eval      GMWI  = WIULIM

 

     C                   callp (e) mqget(hConnection    :

     C                                   hInputQueue    :

     C                                   MQMD           :

     C                                   MQGMO          :

     C                                   bufferLength   :

     C                                   ptrBuffer      :

     C                                   dataLength     :

     C                                   completionCode :

     C                                   reasonCode)

 

      **************************************************************************

      * Get customer credit data, move to message data structure

      **************************************************************************

 

     C     buffer        chain     qcustcdt1

 

     C                   if        %found

     C                   eval      customerNo  = CUSNUM

     C                   eval      initials    = INIT

     C                   eval      lastName    = LSTNAM

     C                   eval      creditLimit = CDTLMT

     C                   eval      balanceDue  = BALDUE

     C                   eval      creditDue   = CDTDUE

 

     C                   else

     C                   eval      customerNo  = CUSNUM

     C                   eval      lastName    = 'NOTFOUND'