Support This Project Get i5/OS Programmer's Toolkit at SourceForge.net. Fast, secure and Free Open Source software downloads

使用i5/OS的Queue Objects

Version:
0.1
Date:
2009-06-20
Author:
李君磊
Queue object是i5/OS上原生的,最常用,最易用,最灵活的IPC方式。 这里我们对Queue Object的重要属性,以及Data Queue和User Queue的操作进行了总结, 同时提供了基于Queue Object的若干有趣且实用的应用实例。这些实例包括:

这里的内容

1. 术语及缩略语

本文中的部分术语

本文中使用的缩略语

2. 对i5/OS Queue Object的介绍

作为一种MI Object,Queue object可以供多个作业插入和读取数据;Queue object的system object type为 hex 0A。 对Queue的插入操作称为Enqueue(入列),对Queue的读取操作称为Dequeue(出列)。 被入列到一个Queue上的数据,称为Queue Entry或Queue Message。 任意对一个Queue有权限的作业都可以向Queue上入列数据或由Queue里出列数据。 对Queue的入列和出列可以基于Key Value(键值),或者基于Queue Entry 的到达顺序,此时,出列操作又分为FIFO(先入先出)和LIFO(后入先出)。

在i5/OS上,通过CL命令和编程接口可以操作Queue objects有:
External Object Type System(MI) Object Type/Subtype
Data Queue *DTAQ hex 0A01
User Queue *USRQ hex 0A02
下面,我们将data queue简称为DTAQ,将user queue简称为USRQ。

此外,每个i5/OS作业还在时时刻刻地使用着一个Queue Object,即包含在PCS (Process Control Space)中的QMIRQ,一个作业在每次发出I/O操作请求后, 由QMIRQ接收I/O操作的结束通知。

Queue object的主要属性:

每个Queue message包含两个部分,message prefix和message text;message prefix中包含 着message text长度以及键值数据。值得注意的是,尽管在入列时,我们可以指定小于 最大queue message长度的message text,但入列后message text实际占用的存储还是等于 Queue Message最大长度。这就意味着,如果为了兼容多种长度相差很多的数据格式,而指 定冗余的queue message最大长度来存放多种数据格式的数据,将导致存储的浪费。

应用程序请求入列一个queue object时,必须提供message prefix和message text。当 queue object中没有可用存储空间时,依赖于queue object的是否可扩展属性,queue object 被扩展或引发一个"queue full"异常(MCH3802)。

Remarks:
当使用QSNDDTAQ 入列 data queue时,当data queue满了时,QSNDDTAQ会引发CPF950A。
应用程序请求由一个queue object 出列 message时,如果message text包含指针, 如果允许message text包含指针属性为真,则指针上的保护tag(标记)被保留;否则, message text中指针上的保护tag将被清除,出列后message text中包含的指针将不 再可用。如果queue message的键值包含指针,出列后键值中指针的保护tag也会 被清除,出列后指针不再可用。

由一个queue object 出列 message时,如果queue object中没有符合条件的meesage时, 应用程序可以选择是否等待,如果选择等待,可以指定永远等待或指定超时值,等待超时 时,会引发 dequeue time-out 异常(MCH5801)。

Remarks:
对于data queue,指定QRCVDTAQ API的"Wait time"参数为0,表示不等待; 对于user queue,通过使用DEQ指令的branch-form或indicator-form来实现不等待。

使用QRCVDTAQ 出列 data queue时,当等待超时,不会引发异常。要判断 是否超时,只能在对QRCVDTAQ调用后,依据存放message text数据的内容判断 是出列到了message,还是出列超时。

可以备份或恢复DTAQ和USRQ,但是由于queue object是i5/OS的用于进行IPC 的,备份和恢复将仅针对于queue object描述信息,而不包含queue object的数据;因此,被恢复的queue object总是空的; 此外,恢复一个queue object时,如果目的queue object 已经存在,将导致恢复失败。

3. DTAQ与USRQ的区别

同样作为i5/OS上的queue object,DTAQ与USRQ具有相同的object属性, 相同的system(MI) object type hex 0A,仅仅是subtype不同, DTAQ为hex 01,USRQ为hex 02;那么二者之间的区别是什么。 这里将由以下几方面就DTAQ与USRQ的区别进行说明:

3.1. Object Domain

i5/OS向用户暴露的DTAQ创建接口是CL命令CRTDTAQ,该命令创建的 DTAQ object总是属于System Domain的,处于User State的进程 无法直接访问,必须通过API或CL命令访问。

i5/OS向用户暴露的DTAQ创建接口是API QUSCRTUQ,该API允许用户 创建System Domain或User Domain的USRQ。参考 5.1.2. USRQ的Domain属性。 然而,作为一个System Domain object,创建的USRQ仅仅在Security Level 30及以下才可以被用户程序通过 MI指令访问。

3.2. 是否可以启用日志

可以对一个DTAQ object 启用日志,而USRQ则不行。 对DTAQ启用日志使得基于DTAQ的IPC过程具备了运行时刻的可观察性。 通过日志,可以记录DTAQ的入列操作信息容,出列操作信息以及对 DTAQ的删除,重命名的等操作。 这为调试基于DTAQ进行的IPC应用提供了便利。同时要注意的是, 由于不可能备份恢复Queue object的数据内容,因此, 日志中记录对DTAQ的入列,出列操作的journal entires 不可以用于Apply Journaled Changes(APYJRNCHG)或 Remove Journaled Changes(RMVJRNCHG)。

Remarks:
DTAQ操作可能的journal entry type参考 Appendix 4 Possible Journal Entry Type for Journal Code Q(Data queue operation)
由下面的示例,可以看到,对DTAQ启用日志后,入列,出列 操作会在日志中留下哪些记录:

3.3. 操作接口

对于DTAQ和USRQ,i5/OS分别由CL命令,API,和MI指令三个层面提供了操作接口; 下表中将CL命令,API,MI指令分别简写为CL,API,MI:
操作 Data Queue User Queue
创建 CL - CRTDTAQ API - QUSCRTUQ
删除 CL - DLTDTAQ CL - DLTUSRQ; API - QUSDLTUQ
入列 API - QSNDDTAQ MI - ENQ
出列 API - QRCVDTAQ MI - DEQ
清空 API - QCLRDTAQ ~
获取queue属性 API - QMHQRDQD MI - MATQAT
获取messages API - QMHRDQM MI - MATQMSG
Remarks:
"获取messages"指读取当前queue object上的全部或部分消息,该操作并不删除queue messages。

3.4. 操作效率

在Information Center等IBM官方文档中没有找到关于DTAQ,USRQ性能对比 的资料,以下仅仅是通过实例,对DTAQ,USRQ的操作效率存在差异这一事实 进行说明。 开始之前,需要提及的一点是,对于可扩展的queue object来说, 扩展queue object是一个耗时的过程, 因此是否需要避免queue object扩展,是在设计使用queue object 进行IPC的应用时,必须考虑的一个问题。 在下面的统计过程中, 我们测试使用的DTAQ及USRQ的初始消息数都给得比较大,以避免在 测试过程中发生扩展,影响统计的准确性。

下面就1000次入列操作分别对以下3种情况进行时间统计,时间单位为1/1000000秒:

"1000次入列操作时间统计"
操作 时间(us,micro-seconds) 命令/源码
case 1 4000 Appendix 1. Enqueue USRQ Q11 for 1000 times
case 2 151000 Appendix 2. Enqueue DTAQ Q12(not journaled) for 1000 times
case 3 40599000 Appendix 3. Enqueue DTAQ Q12(journaled) for 1000 times

尽管以上统计仅仅是对两个具有相同queue属性的DTAQ,USRQ的入列操作得出的,不具备普遍意义。 但足以说明对DTAQ与USRQ操作的效率差异。而以上统计中,启用日志 前后,入列 DTAQ的效率对比,说明了对DTAQ启用日志带来的效率损失。 对于严重依赖DTAQ完成大数据量IPC的应用系统,应该将这种效率损失计入考虑。

Remarks:
此外,创建DTAQ时,如果指定参数FORCE(*YES)(Force to auxiliary storage),则入列, 出列 操作将消耗更多时间。

3.5. DTAQ message可以包含Sender ID

DTAQ可以在每个queue message中包含入列作业信息(在CRTDTAQ命令文档中称为sender ID),USRQ如果要实现功能, 需要入列的应用程序通过编码,在message text中添加作业信息。

使一个DTAQ包含入列作业信息,需要在使用CRTDTAQ命令时指定SENDERID(*YES)参数,如:

CRTDTAQ DTAQ(LSBIN/Q14) MAXLEN(64) SENDERID(*YES) TEXT('with sender''s info')

那么所谓sender ID在queue message中是什么样子呢? 入列上面创建的DTAQ Q14,然后使用CL命令 i5/OS Programmer's ToolkitDSPQMSG 就可以看到:

CALL PGM(QSNDDTAQ) PARM('Q14' 'LSBIN' X'00011F' 'ABCDE *^_^*')
DSPQMSG Q(LSBIN/Q14) QTYPE(*DTAQ)
                                Queue Message                            
                                                            System:   810
Queue  . . . . . . . :   Q14             Attribute  . . . . . :          
  Library  . . . . . :     LSBIN         Owner  . . . . . . . :   LJL    
Object ASP number  . :   1               Object Domain  . . . :   System 
Type . . . . . . . . :   *DTAQ           Key length . . . . . :   0      
                                         Key length . . . . . :   100    
                                         Number . . . . . . . :   1      
                                                                         
        -------------------Character data-------------------             
Column   *...+....1....+....2....+....3....+....4....+....5              
000001  'REDLIGHT  LJL       254281LJL       ABCDE *^_^*   '             
000051  '                                                  '             
可以看到,所谓sender id就是附加在message text前面的26字节的作业标识, 和10字节的作业当前用户名。 其中,26字节作业标识的构成为:10字节作业名称,10字节USRPRF名称,和 6字节作业号。

4. Data Queue操作示例

这里的内容

4.1. 创建一个DTAQ

4.1.1. 创建一个FIFO或LIFO类型的DTAQ

CRTDTAQ DTAQ(Q21) MAXLEN(64) SEQ(*FIFO) TEXT('FIFO, entry length=64')
CRTDTAQ DTAQ(Q22) MAXLEN(64) SEQ(*LIFO) TEXT('LIFO, entry length=64')
向LIFO DTAQ Q22入列几条message,使用 DSPQMSG 命令可以看到,LIFO中消息 是按到达顺序降序存放的。
CALL PGM(QSNDDTAQ) PARM('Q22' 'LSBIN' X'00003F' 'abc')
CALL PGM(QSNDDTAQ) PARM('Q22' 'LSBIN' X'00003F' 'def')
DSPQMSG Q22
/* 结果 */
                Time                        Message
Opt     Number  enqueued                    Key    
             1  2009-05-19-16.36.49.641679         
             2  2009-05-19-16.36.40.429570         

4.1.2. 创建一个Keyed DTAQ

CRTDTAQ DTAQ(Q23) MAXLEN(64) SEQ(*KEYED) KEYLEN(8) TEXT('keyed, key length = 8')
向Keyed DTAQ Q23入列几条消息,使用 DSPQMSG 命令查看
CALL PGM(QSNDDTAQ) PARM('Q23' 'LSBIN' X'00003F' 'abc' X'008F' '00000005')
CALL PGM(QSNDDTAQ) PARM('Q23' 'LSBIN' X'00003F' 'def' X'008F' '00000002')
CALL PGM(QSNDDTAQ) PARM('Q23' 'LSBIN' X'00003F' 'ghi' X'008F' '00000003')

DSPQMSG Q(Q23)
/* 结果 */
                Time                        Message 
Opt     Number  enqueued                    Key     
             1  2009-05-19-16.48.14.771519  00000002
             2  2009-05-19-16.48.38.547735  00000003
             3  2009-05-19-16.48.04.499240  00000005

4.1.3. 创建一个包含发送作业标识的DTAQ

CRTDTAQ DTAQ(Q24) MAXLEN(64) SENDERID(*YES)

4.1.4. 关于CRTDTAQ命令的MAXLEN参数

MAXLEN参数指DTAQ entry的最大长度,上限是64512。 如 2. 对i5/OS Queue Object的介绍 中提到的, 虽然在入列时,可以指定小于MAXLEN的message text长度,但 实际存储时每条message text所占用的存储仍是MAXLEN指定的 字节数。因此,如果为了通过DTAQ交换变长数据,而指定冗余 的MAXLEN参数,将导致存储的浪费,DTAQ可容纳entry数降低, 以及操作效率降低。要实现变长数据的交换,请参考 6.5. 通过i5/OS queue object传递变长数据

4.1.5. 关于CRTDTAQ命令的SIZE参数

SIZE参数包含两个元素:Maximum number of entries,和Initial number of entries。 需要注意的是:

4.2. 删除一个DTAQ

删除一个DTAQ,使用CL命令DLTDTAQ,如:
DLTDTAQ LSBIN/Q21

4.3. 入列一个DTAQ

入列一个DTAQ需要使用 Send to a Data Queue (QSNDDTAQ) API。

4.3.1. 入列一个FIFO或LIFO类型的DTAQ

call qsnddtaq parm(          
     'Q21'         /* char(10),DTAQ名称 */  
     'LSBIN'       /* char(10),库名 */
     X'00003F'     /* pkd(5,0),入列message text长度 */ 
     'ABC'         /* char(*),message text */          
     )                       

4.3.2. 入列一个Keyed DTAQ

call qsnddtaq parm(      
     'Q23'         /* char(10),DTAQ名称 */  
     'LSBIN'       /* char(10),库名 */
     X'00003F'     /* pkd(5,0),入列message text长度 */ 
     'abc'         /* char(*),message text */          
     x'008F'       /* pkd(3,0),键值长度 */
     '00000001'    /* char(*),键值内容 */
     )                   

4.3.3. 入列一个Keyed DTAQ

ILE RPG程序r101.rpgle
     /*
      * @file r101.rpgle
      * enqueue DTAQ Q23(keyed, key length=8, entry length=64)
      */

     /* prototype of API QSNDDTAQ */
     d qsnddtaq        pr                  extpgm('QSNDDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                       64a   options(*varsize)
     d   key_len                      3p 0
     d   key                          8a   options(*varsize)

     d qname           s             10a   inz('Q23')
     d qlib            s             10a   inz('LSBIN')
     d entry_len       s              5p 0 inz(64)
     d entry           s             64a
     d key_len         s              3p 0 inz(8)
     d key             s              8a

      /free
          entry = 'abc';
          key = '00000002';
          qsnddtaq(qname
                   : qlib
                   : entry_len
                   : entry
                   : key_len
                   : key );

          *inlr = *on;
      /end-free
     /* eof */

4.4. 出列一个DTAQ

出列一个DTAQ需要使用 Receive Data Queue (QRCVDTAQ) API。

4.4.1. 出列FIFO/LIFO DTAQ

OPM CL程序cl101.clp 以无期限等待方式出列FIFO DTAQ Q21
             /************************************************/
             /* @FILE CL101.CLP                              */
             /* DEQUEUE FIFO DTAQ Q21                        */
             /************************************************/
             PGM
             DCL        VAR(&QNAM) TYPE(*CHAR) LEN(10) VALUE('Q21')
             DCL        VAR(&QLIB) TYPE(*CHAR) LEN(10) VALUE('LSBIN')
             DCL        VAR(&ENTLEN) TYPE(*DEC) LEN(5 0) /* length +
                          of dequeued data */
             DCL        VAR(&ENTRY) TYPE(*CHAR) LEN(64)
             DCL        VAR(&TIMEOUT) TYPE(*DEC) LEN(5 0) VALUE(-1) +
                          /* dequeue infinittely */
             DCL        VAR(&MSG) TYPE(*CHAR) LEN(32) VALUE('entry +
                          dequeued:')

             CALL       PGM(QRCVDTAQ) PARM(&QNAM &QLIB &ENTLEN +
                          &ENTRY &TIMEOUT)
             CHGVAR     VAR(%SST(&MSG 17 16)) VALUE(&ENTRY)
             SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA(&MSG)

             ENDPGM
             /* EOF - CL101.CLP */

OPM CL程序cl105.clp 以指定超时值等待方式出列FIFO DTAQ Q21

             /************************************************/
             /* @FILE CL101.CLP                              */
             /* DEQUEUE FIFO DTAQ Q21 WITH TIMEOUT           */
             /************************************************/
             PGM
             DCL        VAR(&QNAM) TYPE(*CHAR) LEN(10) VALUE('Q21')
             DCL        VAR(&QLIB) TYPE(*CHAR) LEN(10) VALUE('LSBIN')
             DCL        VAR(&ENTLEN) TYPE(*DEC) LEN(5 0) /* length +
                          of dequeued data */
             DCL        VAR(&ENTRY) TYPE(*CHAR) LEN(64)
             DCL        VAR(&TIMEOUT) TYPE(*DEC) LEN(5 0) VALUE(5) +
                          /* time-out value = 5s */
             DCL        VAR(&MSG) TYPE(*CHAR) LEN(32) VALUE('entry +
                          dequeued:')

             CALL       PGM(QRCVDTAQ) PARM(&QNAM &QLIB &ENTLEN +
                          &ENTRY &TIMEOUT)
             IF         COND(&ENTLEN *EQ 0) THEN(DO)
             CHGVAR     VAR(&MSG) VALUE('Dequeue timed-out')
             GOTO       CMDLBL(SENDMSG)
             ENDDO
             CHGVAR     VAR(%SST(&MSG 17 16)) VALUE(&ENTRY)
 SENDMSG:    SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA(&MSG)

             ENDPGM
             /* EOF - CL101.CLP */

4.4.2. 出列Keyed DTAQ

DTAQ Q23,类型为Keyed,entry长度64,key长度8,不含发送作业 标识信息。OPM CL程序cl102.clp出列DTAQ Q23中 键值大于'00000001'的entry。
             /************************************************/
             /* @FILE CL102.CLP                              */
             /* DEQUEUE KEYED DTAQ Q23                       */
             /************************************************/
             PGM
             DCL        VAR(&QNAM) TYPE(*CHAR) LEN(10) VALUE('Q23')
             DCL        VAR(&QLIB) TYPE(*CHAR) LEN(10) VALUE('LSBIN')
             DCL        VAR(&ENTLEN) TYPE(*DEC) LEN(5 0)
             DCL        VAR(&ENTRY) TYPE(*CHAR) LEN(64)
             DCL        VAR(&TIMEOUT) TYPE(*DEC) LEN(5 0) VALUE(-1) +
                          /* dequeue infinittely */
             DCL        VAR(&KEYORDER) TYPE(*CHAR) LEN(2) +
                          VALUE('GT') /* dequeue queue entries +
                          whose key value aregreater than variable +
                          &KEY */
             DCL        VAR(&KEYLEN) TYPE(*DEC) LEN(3 0) VALUE(8)
             DCL        VAR(&KEY) TYPE(*CHAR) LEN(8) VALUE('00000001')
             DCL        VAR(&SNDINFOLEN) TYPE(*DEC) LEN(3 0) +
                          VALUE(0) /* no sender info */
             DCL        VAR(&SNDINFO) TYPE(*CHAR) LEN(1)
             DCL        VAR(&MSG) TYPE(*CHAR) LEN(96) +
                          VALUE('key:         ; message text: ')

             CALL       PGM(QRCVDTAQ) PARM(&QNAM &QLIB &ENTLEN +
                          &ENTRY &TIMEOUT &KEYORDER &KEYLEN &KEY +
                          &SNDINFOLEN &SNDINFO)
             CHGVAR     VAR(%SST(&MSG 6 8)) VALUE(&KEY)
             CHGVAR     VAR(%SST(&MSG 30 64)) VALUE(&ENTRY)
             SNDPGMMSG  MSGID(CPF9898) MSGF(QCPFMSG) MSGDTA(&MSG)

             ENDPGM
             /* EOF - CL102.CLP */

4.4.3. 出列Keyed DTAQ并保留被出列entry

DTAQ Q23,类型为Keyed,entry长度64,key长度8,不含发送作业 标识信息。ILE RPG程序rl102.rpgle出列DTAQ Q23中 键值大于'00000001'的entry,并保留DTAQ中的entry。
     /*
      * @file r102.rpgle
      *
      * DTAQ Q23,类型为基于键值出列,entry长度64,key长度8,
      * 不含发送作业标识信息。
      *
      * 出列DTAQ Q23中键值大于'00000001'的entry,并保留DTAQ中的entry。
      */

     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /* prototype of API QRCVDTAQ */
     d qrcvdtaq        pr                  extpgm('QRCVDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                       64a   options(*varsize)
     d   timeout                      5p 0
     d   key_order                    2a   options(*nopass)                     /* optional parameter group 1 */
     d   key_len                      3p 0 options(*nopass)
     d   key                          8a   options(*nopass:*varsize)            /* input/output parameter */
     d   sender_info...
     d     _len                       3p 0 options(*nopass)
     d   sender_info                  1a   options(*nopass:*varsize)
     d   remove_msg                  10a   options(*nopass)                     /* optional parameter group 2 */
     d   receiver_len                 5p 0 options(*nopass)
     d   ec                                likeds(qusec_t)
     d                                     options(*nopass)

     d qname           s             10a   inz('Q23')
     d qlib            s             10a   inz('LSBIN')
     d entry_len       s              5p 0
     d entry           s             64a
     d timeout         s              5p 0 inz(-1)                              /* dequeue infinitely */
     d key_order       s              2a   inz('GT')                            /* key order: >= parameter @key */
     d key_len         s              3p 0 inz(8)
     d key             s              8a   inz('00000001')
     d sender_info...
     d   _len          s              3p 0 inz(0)                               /* no sender info */
     d sender_info     s              1a
     d remove_msg      s             10a   inz('*NO')                           /* do NOT remove message from DTAQ */
     d receiver_len    s              5p 0 inz(64)
     d ec              ds                  likeds(qusec_t)
     d msg             s             16a

      /free
          ec.bytes_in = 256;
          qrcvdtaq(  qname
                   : qlib
                   : entry_len
                   : entry
                   : timeout
                   : key_order
                   : key_len
                   : key
                   : sender_info_len
                   : sender_info
                   : remove_msg
                   : receiver_len
                   : ec );

           msg = %subst(entry : 1 : 16);
           dsply key '*EXT' msg;

          *inlr = *on;
      /end-free
     /* eof - r102.rpgle */

Remarks:
API QRCVDTAQ的参数9,键值数据是一个输入/输出参数,在发起调用 时表示输入的键值比较条件,在调用返回时,内容为出列到的message 的键值数据内容。

4.5. 清空一个DTAQ

清空一个DTAQ需要使用API Clear Data Queue (QCLRDTAQ) ,如:
CALL PGM(QCLRDTAQ) PARM('Q21' 'LSBIN')

4.6. 获取DTAQ属性

获取DTAQ属性需要使用 Retrieve Data Queue Description (QMHQRDQD) API。 QMHQRDQD支持两种属性 格式,RDQD0100和RDQD0200,前者用于描述普通DTAQ,后者用于描述 DDM DTAQ。下面示例中的ILE RPG程序 r103.rpgle 使用QMHQRDQD获取DTAQ Q23的属性。

     /*
      * @file r103.rpgle
      *
      * retrieve attribtutes of a DTAQ
      * Keyed DTAQ Q23 with entry length 64, key length 8,
      * does not include sender's ID
      */
     h dftactgrp(*no)

     /* receiver structure in format RDQD0100 */
     d rdqd0100_t      ds           112    qualified
     d   bytes_...
     d     returned                  10i 0                                      /* length of attribute data returned */
     d   bytes_...
     d     available                 10i 0                                      /* length DTAQ attribute data */
     d   entry_len                   10i 0                                      /* DTAQ entry length */
     d   key_len                     10i 0                                      /* DTAQ key length */
     d   sequence                     1a                                        /* DTAQ type:        */
     d                                                                          /*   - 'F', FIIO     */
     d                                                                          /*   - 'K', Keyed    */
     d                                                                          /*   - 'L', LIFO     */
     d   include_...
     d    sender_info                 1a                                        /* include sender info or not: Y/N */
     d   force_to_stg                 1a                                        /* force to storage: Y/N */
     d   text                        50a                                        /* text description */
     d   ddm_dtaq                     1a                                        /* is q DDM DTAQ:    */
     d                                                                          /*   - '0', no       */
     d                                                                          /*   - '1', yes      */
     d   auto_reclaim                 1a                                        /* automatic reclaim storage */
     d                                                                          /*   - '0', no       */
     d                                                                          /*   - '1', yes      */
     d   reserved                     1a
     d   num_messages                10i 0                                      /* messages currently on the DTAQ */
     d   entries_...
     d     allocated                 10i 0                                      /* entries currently allocated */
     d   qname                       10a
     d   qlib                        10a
     d   max_messages                10i 0                                      /* maximum number of messages */
     d   init_...
     d     messages                  10i 0                                      /* initial number of messages */

     /* prototype of API QMHQRDQD */
     d qmhqrdqd        pr                  extpgm('QMHQRDQD')
     d   dtaq_attr                         likeds(rdqd0100_t)                   /* receiver data */
     d   receiver_len                10i 0                                      /* length of receiver data */
     d   format_name                  8a                                        /* format name               */
     d                                                                          /*   - RDQD0100, normal DTAQ */
     d                                                                          /*   - RDQD0200, DDM DTAQ    */
     d   qname_lib                   20a                                        /* DTAQ name/library, e.g.   */
     d                                                                          /*   'Q23       LSBIN     '  */

     d do_report       pr

     d dtaq_attr       ds                  likeds(rdqd0100_t)
     d len             s             10i 0 inz(112)
     d format          s              8a   inz('RDQD0100')
     d qname_lib       s             20a   inz('Q23       LSBIN')

      /free

          qmhqrdqd(  dtaq_attr
                   : len
                   : format
                   : qname_lib );

          do_report();

          *inlr = *on;
      /end-free
     /* eof - main procedure */

     p do_report       b

     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /* prototype of QMHSNDPM */
     d qmhsndpm        pr                  extpgm('QMHSNDPM')
     d   msgid                        7a
     d   msgf                        20a
     d   msgdata                    128a   options(*varsize)
     d   msgdata_len                 10i 0
     d   msgtype                     10a
     d   callstack_...
     d     entry                     10a
     d   callstack_...
     d     counter                   10i 0
     d   msgkey                       4a
     d   ec                                likeds(qusec_t)

     d msgid           s              7a   inz('CPF9898')
     d msgf            s             20a   inz('QCPFMSG   QSYS')
     d msgdata         s            128a
     d msgdata_len     s             10i 0
     d msgtype         s             10a   inz('*INFO')
     d callstack_...
     d   entry         s             10a   inz('*PGMBDY')
     d callstack_...
     d   counter       s             10i 0 inz(1)
     d msgkey          s              4a
     d ec              ds                  likeds(qusec_t)
     d msg             s            128a

      /free

          ec.bytes_in = 256;

          // report DTAQ entry length
          msgdata = 'Entry length: ' + %char(dtaq_attr.entry_len);
          msgdata_len = %len(%trim(msgdata));
          qmhsndpm(  msgid
                   : msgf
                   : msgdata
                   : msgdata_len
                   : msgtype
                   : callstack_entry
                   : callstack_counter
                   : msgkey
                   : ec );

          // report DTAQ key length
          msgdata = 'Key length: ' + %char(dtaq_attr.key_len);
          msgdata_len = %len(%trim(msgdata));
          qmhsndpm(  msgid
                   : msgf
                   : msgdata
                   : msgdata_len
                   : msgtype
                   : callstack_entry
                   : callstack_counter
                   : msgkey
                   : ec );

          // report more DTAQ attributes
          // ... ...

      /end-free
     p do_report       e

     /* eof - r103.rpgle */

更多示例请参考 DSPQD 源码 qattr.mi 中的internal entry point display-ddmq-attr(line 361),和 display-dtaq-attr(line 396)。

4.7. 获取Data queue上的消息

获取DTAQ上的消息需要使用 Retrieve Data Queue Message (QMHRDQM) API。请参考 DSPQMSG 源码 qmsg.mi 中:

在使用QMHRDQM时需要注意的是:

5. User Queue操作示例

5.1. 创建一个USRQ

创建一个USRQ需要使用 Create User Queue (QUSCRTUQ) API。

QUSCRTUQ参数如下所示:

关于API QUSCRTUQ的详细文档请参考 Create User Queue (QUSCRTUQ) API

5.1.1. USRQ上最大可容纳的消息数

参数Initial number of messages(INIT_MSGS) Additional number of messages(EXT_MSGS) 和Number of queue extensions(NUM_EXTENDS) 一起决定了一个USRQ上最大可容纳的消息数(MAX_MSGS)。
MAX_MSGS = INIT_MSGS + EXT_MSGS * NUM_EXTENDS
/*
例如:
    INIT_MSGS = 1
    EXT_MSGS = 2
    NUM_EXTENDS = 1

    MAX_MSGS = 1 + 2 * 1 = 3
即USRQ可容纳的最大消息数为3,当试图向USRQ入列第4条消息时
将引发MCH3802(hex 2602)异常。
   */

Remarks:
通常,机器将对实际创建的USRQ的最大可容纳消息数进行 微小的修改。要取得精确的最大消息数可以使用 i5/OS Programmer's Toolkit中的 CL命令 DSPQD

5.1.2. USRQ的Domain属性

参数Domain与系统值QALWUSRDMN决定了QUSCRTUQ创建的USRQ的Domain属性。
QALWUSRDMN System Value Destination Library Optional Domain Parameter Domain of Created Object
*ALL Any *DEFAULT User domain
*ALL Any *SYSTEM System domain
*ALL Any *USER User domain
QTEMP QTEMP *DEFAULT User domain
QTEMP QTEMP *SYSTEM System domain
QTEMP QTEMP *USER User domain
Does not contain library name Library name *DEFAULT System domain
Does not contain library name Library name *SYSTEM System domain
Does not contain library name Library name *USER None; error is returned

5.1.3. 关于参数Number of queue extensions

参数Number of queue extensions决定了USRQ的最大扩展 次数。Information Center V5R2, V5R4中 API QUSCRTUQ 文档中的说明是:
Number of queue extensions
    INPUT; BINARY(4)
    The maximum number of extensions allowed for the user queue.
    A value of -1 indicates that the maximum number of extensions
    will be chosen by the machine.
    If this parameter is not specified, 0 is assumed.
但在V5R2上的实验结果是,Number of queue extensions 指定 -1 或不指定时,USRQ的最大扩展次数都是由机器决定的。 参考下面示例可见,两种情况下USRQ的`Choose maximum number of extends`属性 和`Maximum number of extends`属性完全相同:

5.2. 删除一个USRQ

删除一个USRQ可以使用CL命令DLTUSRQ或API QUSDLTUQ,如:
/* CL命令DLTUSRQ */
DLTUSRQ USRQ(LSBIN/Q31)
/* API QUSCLTUQ */
CALL PGM(QUSDLTUQ) PARM('Q34       LSBIN'
    X'0000002000000000000000000000000000000000000000000000000000000000')

5.3. 入列一个USRQ

入列一个USRQ需要使用MI指令 ENQ 。下面通过两个示例程序说明使用 ILE HLL或直接使用MI完成对USRQ的入列。

Remarks:
必须注意的是,使用 ENQ 指令时,参数3 message text必須是对齐到16字节边界的。
ILE RPG程序 r104.rpgle 使用 RSLVSP 指令取得USRQ objet Q31的system pointer,然后使用 ENQ 指令入列USRQ Q31。
     /*
      * @file r104.rpgle
      *
      * enqueue USRQ Q31
      */
     h dftactgrp(*no)

     /* my protoype */
     d i_main          pr                  extpgm('R104')
     d   p_key                        8a
     d   p_text                      64a

     d q               s               *
     d option          ds            34    qualified
     d   type                         2a   inz(x'0A02')                         /* USRQ's MI object type: hex 0A02 */
     d   name                        30a   inz('Q31')                           /* USRQ name */
     d   auth                         2a   inz(x'0000')
     /* prototype of MI instruction RSLVSP */
     d rslvsp          pr                  extproc('_RSLVSP2')
     d   syp                           *
     d   option                        *   value
     /* prototype of MI instruction ENQ */
     d enq             pr                  extproc('_ENQ')
     d   q                             *
     d   prefix                        *   value
     d   text                          *   value

     d prefix          ds                  qualified                            /* message prefix */
     d   text_len                    10i 0                                      /* length of message text */
     d   key                          8a   inz('00000001')                      /* key data */
     d text            s             64a   inz('hi, i''m rpg program R104:p')   /* message text */

     d i_main          pi
     d   p_key                        8a
     d   p_text                      64a

      /free

          rslvsp(q : %addr(option));

          prefix.key = p_key;
          text = p_text;
          enq(q : %addr(prefix) : %addr(text));

          *inlr = *on;
      /end-free
     /* eof - r104.rpgle */

r104.rpgle实现相同功能的MI程序 enq31.mi

/*
 * @file enq31.mi
 *
 * enqueue USRQ Q31(keyed, key length = 8, entry lenght=64)
 *
 * usage example: CALL ENQ31 PARM('00000003' 'ABCDE...')
 */

dcl sysptr q auto init("Q34", ctx("LSBIN"), type(q, 02)) ; 
dcl dd message-prefix char(9) auto ;
        dcl dd prefix-msglen bin(4) def(message-prefix) pos(1);
        dcl dd prefix-key char(5) def(message-prefix) pos(5) ; 

dcl spcptr .key parm                ; 
dcl spcptr .text parm                ; 
dcl ol pl-main(.key, .text) parm ext        ; 

dcl dd key char(8) bas(.key)    ; 
dcl dd text char(64) bas(.text) ; 

dcl dd msg char(64) auto bdry(16) ;
dcl spcptr msg-ptr auto init(msg) ; 

entry *(pl-main) ext;

        /* enq */
        cpynv prefix-msglen, 64;
        cpybla prefix-key, key  ;
        cpybla msg, text        ; 
        enq q, message-prefix, msg-ptr ; 

        rtx *;

pend;
/* eof - enq31.mi */

5.4. 出列一个USRQ

出列一个USRQ需要使用MI指令 DEQDEQ 指令操作数列表如下:

其中operand 1 message prefix 格式如下:

Offset  
Dec   Hex   Field Name                                      Data Type and Length
0   0   Timestamp of enqueue of message     Char(8) ++
8   8   Dequeue wait time-out value                     Char(8) +
          (ignored if branch options specified) 
16  10  Size of message dequeued                  Bin(4) ++
          (The maximum allowable size of a queue message
                is 64 K bytes.)
20  14  Access state modification option indicator  Char(1) +
                and message selection criteria  
20  14  Access state modification option when entering Dequeue wait   Bit 0 +
                0 =   Access state is not modified
                1 =   Access state is modified
20  14  Access state modification option when leaving Dequeue wait  Bit 1 +
                0 =   Access state is not modified
                1 =   Access state is modified
20  14  Multiprogramming level option                             Bit 2 +
                0 =   Leave current MPL set at Dequeue wait
                1 =   Remain in current MPL set at Dequeue wait
20  14  Time-out option                                                 Bit 3 +
                0 =   Wait for specified time, then signal time-out exception
                1 =   Wait indefinitely
20  14  Actual key to input key relationship                            Bits 4-7 +
          (for keyed queue)
                0010 =  Greater than
                0100 =  Less than
                0110 =  Not equal
                1000 =  Equal
                1010 =  Greater than or equal
                1100 =  Less than or equal
21  15  Search key (ignored for FIFO/LIFO queues but  Char(key length) +
                must be present for FIFO/LIFO queues with
                nonzero key length values)  
  *   Message key                                     Char(key length) ++
Remarks:
以上参数中使用'+'标识的为输入参数,使用'++'标识的为输出参数。

关于DEQ的详细文档,请参考Dequeue (DEQ)

5.4.1.使用不等待方式执行DEQ指令

执行DEQ指令可以使用等待或不等待两种方式。 USRQ上没有符合条件的message时,使用不等待方式执行DEQ指令,不会导致当前 线程进入等待状态。使用不等待方式执行DEQ指令的方法是,使用DEQ指令的 branch form或indicator form,例如:
dcl sysptr q auto init("Q31", ctx("LSBIN"), type(q, 02)) ; 
dcl dd prefix char(37) auto     ;
dcl spcptr prefix-ptr auto init(prefix) ;
dcl spc prefix-t bas(prefix-ptr) ; 
        dcl dd time-enqueued char(8) dir ; /* output */
        dcl dd timeout char(8) dir ;
        dcl dd text-len bin(4) dir ;       /* output */
        dcl dd access-state-option char(1) dir ;
        dcl dd search-key char(8) ;
        dcl dd returned-key char(8) ;      /* output */
dcl dd text char(64) auto bdry(16) ;
dcl spcptr text-ptr auto init(text) ; 

entry * ext                     ; 

brk "DEQB"                      ; 
        /* binary:      11000010 */
        /* dequeue messages whose keys are greater than search-key */
        cpybla access-state-option, x"1202" ;
        cpybla search-key, "00000001" ; 
        deq(b) prefix, text-ptr, q / neq(not-found), eq(found) ; 
        
found:      /* when a queue message dequeued, control flow will be branched here */

not-found:  /* when no queue message dequeued, control flow will be branched here */

brk "DEQI"                        ;
dcl dd ind-found char(1) auto   ; 
dcl dd ind-not-found char(1) auto   ;
        deq(i) prefix, text-ptr, q / neq(ind-not-found), eq(ind-found) ;
        /* when a queue message is dequeued, ind-found = '1', ind-not-found = '0' */
        /* when no queue message is dequeued, ind-found = '0', ind-not-found = '1' */

        rtx *                   ; 
pend                                ; 

Remarks:
更多示例请参考 i5/OS Programmer's Toolkit 中用于清空USRQ的工具程序clrusrq.mi, 该程序使用DEQ指令的branch form进行不等待出列。

5.4.2. 使用等待方式执行DEQ指令

使用等待方式执行DEQ指令时,可以指一个确定的超时时间,或无限期等待。

要无限期等待需要将DEQ指令的操作数1(message prefix)的输入参数 Access state modification option when entering Dequeue wait的 bit 3 Time-out option置为1(bit 0指最高位),如:

dcl dd prefix char(37) auto     ;
dcl spcptr prefix-ptr auto init(prefix) ;
dcl spc prefix-t bas(prefix-ptr) ; 
        dcl dd time-enqueued char(8) dir ; /* output */
        dcl dd timeout char(8) dir ;
        dcl dd text-len bin(4) dir ;       /* output */
        dcl dd access-state-option char(1) dir ;

        /* set 'Time-out option' */
        setbts access-state-option, 3;

Remarks:
更多示例请参考 6.5. 通过i5/OS queue object传递变长数据 中的ILE RPG服务程序 isqlsvr2.sqlrpgle
要指定超时时间,需要将DEQ指令的操作数1(message prefix)的输入参数 Access state modification option when entering Dequeue wait的 bit 3 Time-out option置为0(bit 0指最高位),并在参数Dequeue wait time-out value 中指定以系统时钟表示的超时时间,如:
dcl dd prefix char(37) auto     ;
dcl spcptr prefix-ptr auto init(prefix) ;
dcl spc prefix-t bas(prefix-ptr) ; 
        dcl dd time-enqueued char(8) dir ; /* output */
        dcl dd timeout char(8) dir ;
        dcl dd text-len bin(4) dir ;       /* output */
        dcl dd access-state-option char(1) dir ;

dcl dd seconds bin(4) auto init(7) ; /* 7 seconds */
        mult(s) seconds, 125000 ; 
        cpybtrls timeout, seconds, 17 ; /* copy seconds to timeout aligning from the left; shift 17 bits to the right */

        /* clear 'Time-out option' */
        clrbts access-state-option, 3;
当超时时间溢出,DEQ指令将引发Dequeue Time-Out异常(hex 3A01, MCH5801)。
Remarks:
如果指定Time-out option为0,则当前作业的默认超时时间将被采用。

注意系统时钟使用8字节表示,其中高49位每增1表示8个1/1000000秒。

5.5. 清空USRQ

IBM没有提供清空USERQ的CL命令或API,可以使用 i5/OS Programmer's Toolkit 提供的工具程序 clrusrq.mi。 调用说明:
CALL CLRUSRQ PARM(usrq_name)

5.6. 获取USRQ属性

获取USRQ属性需要使用MI指令 MATQAT

5.7. 获取USRQ上的消息

获取USRQ上的消息需要使用MI指令 MATQMSG

6. Queue Object应用实例

这里的实例

6.1. IBM提供的USRQ示例: Creating a Batch Machine

在Information Center中,IBM提供了一个最基本USRQ的使用示例, Creating a Batch Machine。 这个示例中,Client程序(1个或多个)与Server程序 通过一个USRQ交互,USRQ类型为FIFO,entry长度100字节;client将用户交互输入的CL命令 作为message text发送到USRQ;server程序循环由USRQ出列client发送的命令,使用API QCMDEXC 执行client的CL命令,直到某一个client发送的命令字串为'quit'或'QUIT'。

6.2. Output Queue的Data Queue支持

en title Data Queue Support on Output Queues

i5/OS的每个Output Queue(简称OUTQ)可以可选地关联一个Data Queue,以便当OUTQ 上的一个spooled file到达READY状态时,向DTAQ上发送一个通知message。 将一个DTAQ关联到一个OUTQ,可以通过在使用CRTOUTQ或CHGOUTQ命令时,指定DTAQ参数; 或者通过添加一个系统级别的环境变量QIBM_NOTIFY_CRTSPLF。这种机制为用户应用 程序管理OUTQ上的spooled file提供了便利。

这里我们通过实验检查由通知DTAQ上可以得到哪些信息。额外地,作为补充说明, 附录 Appendix 5 A Joblog Saver Program 中的示例程序,将OUTQ QEZJOBLOG中的spooled file实时地存入数据文件,并在 保存后删除。

创建DTAQ
需要注意的是:

添加环境变量QIBM_NOTIFY_CRTSPLF
如果希望环境变量只影响当前交互作业,使用参数LEVEL(*JOB);如果 希望环境变量影响当前系统,使用参数LEVEL(*SYS)。

ADDENVVAR ENVVAR(QIBM_NOTIFY_CRTSPLF)
          VALUE('*DTAQ LSBIN/SPLF')
          LEVEL(*SYS)     /* take system wide effects */

检查DTAQ中得到的通知数据
随便编译一个RPG程序,以便生成一个spooled file; 使用 DSPQMSG 命令,分别查看spooled file通知entry的字符形式的十六进制形式内容,

/* DSPQMSG */
DSPQMSG Q(LSBIN/SPLF)

/* character format */
        -------------------Character data-------------------
Column   *...+....1....+....2....+....3....+....4....+....5 
000001  '*SPOOL    02REDLIGHT  LJL       254395AAA         '
000051  ' QPRINT    QGPL      REDLIGHT  LJL       254395  '
000101  '                   S65FAA4B  1090518164906       '

/* hexadecimal format */
        ------------------Hexadecimal data------------------
Column   * . . . + . . . . 1 . . . . + . . . . 2 . . . . +  
000001  '5CE2D7D6D6D340404040F0F2D9C5C4D3C9C7C8E34040D3D1D3'
000026  '40404040404040F2F5F4F3F9F5C1C1C1404040404040400000'
000051  '0001D8D7D9C9D5E340404040D8C7D7D3404040404040D9C5C4'
000076  'D3C9C7C8E34040D3D1D340404040404040F2F5F4F3F9F54040'
000101  '4040404040404040000000000000000000000003E2F6F5C6C1'
000126  'C1F4C24040F1F0F9F0F5F1F8F1F6F4F9F0F600            '
可以看到,入列到通知DTAQ的记录类别为'02'的数据的格式如下:
Format of notification type '02'
偏移(Decimal) 偏移(Hexadecimal) 数据类型/长度 含义 以上示例中的值
0 0 char(10) 功能码。总是'*SPOOL' '*SPOOL '
10 A char(2) 记录类别。'02' '02'
12 C char(26) 作业标识。10字节作业名称,10字节USRPRF名称,和6字节作业号 'REDLIGHT LJL 254395'
38 26 char(10) Spooled file名称 'AAA '
48 30 binary(4) Spooled file序号 x'00000001'
52 34 char(20) Output queue名称 'QPRINT QGPL '
72 48 char(26) 作业标识 'REDLIGHT LJL 254395'
98 62 char(10) 用户数据 ' '
108 6C binary(4) 保留 x'00000000'
112 char(8) 产出spooled file的线程ID x'0000000000000003'
120 78 char(10) 产出spooled file的机器名 'S65FAA4B '
130 82 char(7) Spooled file创建日期。格式为CYYMMDD '1090518'
137 89 char(6) Spooled file创建时间 '164906'
143 8F char(1) 保留 x'00'

作为接收通知的用户应用程序,在出列到以上通知信息后,要使用CL命令(如CPYSPLF, DLTSPLF)或Spooled File API(如Open Spooled File (QSPOPNSP) API)操作目的Spooled File时, 可以通过通知信息中以下参数唯一确定一个Spooled File:

通常使用前三个参数即可。

6.3. 通过keyed queue objects实现基于优先级的通讯

在各种各样的通讯模型中,通讯内容的优先级是一个常见的概念,基于优先级的通讯允许 具备高优先级的请求得到优先处理,以便在服务端存在负载压力时,确保时间关键的业务 在时限内被处理。在i5/OS上,我们可以借助Keyed Queue Object实现 基于优先级的通讯。

下面的示例中,假设的客户程序与服务程序通过一个Keyed DTAQ通讯, DTAQ的键值表示消息的优先级,客户程序使用字符形式的'00'至'99'标识自己发送给 服务程序的消息的优先级,'00'为优先级最高,'99'为最低。服务程序出列 DTAQ中键值大于等于'00'的所有消息,优先级高的(键值小的)消息将先被出列并处理。

示例中涉及的组件:

这里的内容:

6.3.1. 创建DTAQ Q27

CRTDTAQ DTAQ(Q27)
        MAXLEN(16)  /* entry length = 16 */
        SEQ(*KEYED) /* DTAQ type: keyed */
        KEYLEN(2)   /* key length = 2 */
        TEXT('for priority-based IPC')

6.3.2. 编写客户程序

ILE RPG程序r105.rpgle输入参数为:

调用示例:

/*
调用R105,参数如下:
 - priority: '01'
 - delay: 15, hex 015F
 - msg: 'morning :p'
*/
CALL PGM(R105) PARM('01' X'015F' 'morning :p')

/* 使用DSPQMSG命令查看入列到DTAQ的message */
DSPQMSG Q(Q27) QTYPE(*DTAQ)
/*
/* 键值数据 */
        -------------------Character data-------------------
Column   *...+....1....+....2....+....3....+....4....+....5 
000001  '01                                                '

/* message text的 hex 表示 */
        ------------------Hexadecimal data------------------
Column   * . . . + . . . . 1 . . . . + . . . . 2 . . . . +  
000001  '015F94969995899587407A9740404040                  '
*/

6.3.3. 编写服务程序

ILE RPG程序r106.rpgle循环由 DTAQ Q27出列键值大于等于'00'的entry,键值小的entry将先被出列, 从而实现按优先级出列的功能。 r106.rpgle出列到一个entry后, 按客户程序r105.rpgle 在请求信息中指定的延时时间调用DLYJOB命令,之后, 显示客户程序请求的优先级,及请求内容。

     /*
      * @file r106.rpgle
      *
      * deq R106
      *
      * queue message format
      *  - delay, pkd(2,0), delay time
      *  - msg, char(14), message from the client
      */
     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /* prototype of API QRCVDTAQ */
     d qrcvdtaq        pr                  extpgm('QRCVDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                       64a   options(*varsize)
     d   timeout                      5p 0
     d   key_order                    2a   options(*nopass)                     /* optional parameter group 1 */
     d   key_len                      3p 0 options(*nopass)
     d   key                          8a   options(*nopass:*varsize)            /* input/output parameter */
     d   sender_info...
     d     _len                       3p 0 options(*nopass)
     d   sender_info                  1a   options(*nopass:*varsize)
     d   remove_msg                  10a   options(*nopass)                     /* optional parameter group 2 */
     d   receiver_len                 5p 0 options(*nopass)
     d   ec                                likeds(qusec_t)
     d                                     options(*nopass)

     /* queue entry format, used both by the client and the server */
     d q_format        ds                  qualified
     d   dly_time                     2p 0                                      /* how long to    */
     d   msg                         14a                                        /* greeting words */

     d qname           s             10a   inz('Q27')
     d qlib            s             10a   inz('LSBIN')
     d entry_len       s              5p 0 inz(16)
     d timeout         s              5p 0 inz(-1)                              /* dequeue infinitely */
     d key_order       s              2a   inz('GE')                            /* key order: >= parameter @key */
     d key_len         s              3p 0 inz(2)
     d key             s              2a   inz('00')
     d sender_info...
     d   _len          s              3p 0 inz(0)                               /* no sender info */
     d sender_info     s              1a
     d priority        s             16a

     d qcmdexc         pr                  extpgm('QCMDEXC')
     d   cmd                         64a   options(*varsize)
     d   cmdlen                      15p 5

     d dlyjob_cmd      s             64a
     d len             s             15p 5

     d QUIT_CMDS       c                   'q Q qu QU quit QUITT'
     d KEY_ARG         c                   '01'

      /free

          dow '1';

              key = KEY_ARG;
              // deq DTAQ Q27
              qrcvdtaq(  qname
                       : qlib
                       : entry_len
                       : q_format
                       : timeout
                       : key_order
                       : key_len
                       : key
                       : sender_info_len
                       : sender_info );

              // should i quit?
              if %scan( %trim(q_format.msg) : QUIT_CMDS ) > 0;
                  priority = 'Priority: 100';
                  q_format.msg = 'see you :)';
                  dsply priority '' q_format.msg;
                  leave;
              endif;

              // delay job
              dlyjob_cmd = 'DLYJOB ' + %char(q_format.dly_time);
              len = %len(%trim(dlyjob_cmd));
              qcmdexc(dlyjob_cmd : len);

              // display message
              priority = 'Priority: ' + key;
              dsply priority '' q_format.msg;

          enddo;

          *inlr = *on;
      /end-free
     /* eof - r106.rpgle */

现在执行服务程序和客户程序

/* 提交服务作业 */
SBMJOB CMD(CALL R106)

/* 调用客户程序4次 */
CALL PGM(R105) PARM('99' X'015F' 'see you')
CALL PGM(R105) PARM('03' X'001F' 'Third')
CALL PGM(R105) PARM('02' X'001F' 'Second')
CALL PGM(R105) PARM('01' X'001F' 'First')

/* 到message queue QSYSOPR里检查并回复服务程序的消息 */
First
  Reply . . :   a
Second
  Reply . . :   a
Third
  Reply . . :   a

6.4. 通过i5/OS queue object模拟同步调用

在前面看到的基于queue的IPC实例中,请求发起程序通过queue发送完请求后 就继续执行自身所在线程的后续程序逻辑,无法等待请求处理结束, 也无法取得请求处理的结果。这种通讯方式在某些情况下是不可接受的, 例如,客户程序的多个请求间存在时序关系时,即特定类型的请求必须在其他类型 的请求处理完之后才可以发起。此外,请求的处理结果,在某些场合对 客户程序也是关键的,如某类型的请求执行失败后,客户程序就应该 放弃继续发送后续请求。

要解决上面的两个问题,可以使用queue object模拟同步调用。 这种通讯模型的具体的实现方法有多种,而基本的共性是,请求发起程序 在通过入列queue object发送完请求后,通过出列另一个 queue object等待请求处理程序完成请求处理,并反馈处理结果。

作为示例,我们实现以下的一种模拟同步调用模型

以下介绍中,实现一个基于CL命令形式的客户程序ISQL,和服务它的 服务程序ISQLSVR。ISQL向请求DTAQ ISQL入列以UUID为键值的请求entry, entry内容为待执行的SQL语句;入列请求后, ISQL由响应DTAQ ISQLR以等待方式出列ISQLSVR的响应;ISQLSVR由请求DTAQ ISQL出列ISQL的 请求,执行SQL语句,使用出列时得到请求entry键值中包含的UUID作 为键值,将SQL code作为响应信息入列到响应DTAQ ISQLR;ISQL依据出列 到的SQL code,提示用户SQL语句执行是否成功。

ISQL示例中的组件如下:

这里的内容:

6.4.1. 创建DTAQ ISQL, ISQLR

DTAQ ISQL

DTAQ ISQLR

创建DTAQ的CL命令

CRTDTAQ DTAQ(LSBIN/ISQL) MAXLEN(256) SEQ(*KEYED) KEYLEN(16) TEXT('Request Data Queue for ISQL')
CRTDTAQ DTAQ(LSBIN/ISQLR) MAXLEN(4) SEQ(*KEYED) KEYLEN(16) TEXT('Reply Data Queue for ISQL')

6.4.2. 编写CL命令ISQL

CL命令源码 isql.cmd 如下
/* @FILE ISQL.CMD */

             CMD        PROMPT('Run SQL statement')
             PARM       KWD(SQL) TYPE(*CHAR) LEN(256) MIN(1) +
                          INLPMTLEN(50) PROMPT('SQL statement')
/* EOF */

编译CL命令ISQL

CRTCMD CMD(LSBIN/ISQL) PGM(*LIBL/ISQLCPP) SRCFILE(LS2008/DEC)
  SRCMBR(*CMD) TEXT('i can sql')

6.4.3. 编写CL命令处理程序ISQL

ILE RPG程序 isqlcpp.rpgle 如下
     /*
      * @file isqlcpp.rpgle
      *
      * commmand processing program for CL command ISQL
      */

     h dftactgrp(*no)

     d i_main          pr                  extpgm('ISQLCPP')
     d   sql                        256a
     d   sqlcod                      10i 0 options(*nopass)

     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /* input checking procedure */
     d input_check     pr              n

     /*
      * do_request
      *  - send SQL request to server program ISQLSVR
      *  - receive the SQLCOD returned by ISQLSVR
      *  - report result message to the user
      *
      * @pre sql
      * @return sqlcod
      */
     d do_request      pr            10i 0

     /* send message to interactive user */
     d sendmsg         pr
     d   msg                        256a

     d i_main          pi
     d   sql                        256a
     d   sqlcod                      10i 0 options(*nopass)

     d msg             s            256a
     d rtn             s             10i 0

      /free

          if input_check();
              rtn = do_request();
          endif;

          sendmsg(msg);

          // return SQLCOD to the caller when parameter sqlcod is specified
          if %parms() > 1;
              sqlcod = rtn;
          endif;

          *inlr = *on;
      /end-free
     /* eof - i_main() */

     /* procedure input_check() */
     p input_check     b
     d input_check     pi              n

      /free
          if sql = *blanks;
              msg = 'Empty SQL statement';
              return *off;
          endif;

          return *on;
      /end-free
     p input_check     e

     /* procedure do_request() */
     p do_request      b

     d q_entry         ds                  qualified
     d   sql                        256a
     d   sqlcod                      10i 0
     d   ch_sqlcod                    4a   overlay(sqlcod)

     d uuid_template   ds                  qualified
     d   bytes_in                    10u 0 inz(32)
     d   bytes_out                   10u 0
     d   reserved                     8a   inz(x'0000000000000000')
     d   uuid                        16a

     d genuuid         pr                  extproc('_GENUUID')
     d   template                      *   value

     /* prototype of API QSNDDTAQ */
     d qsnddtaq        pr                  extpgm('QSNDDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                        1a   options(*varsize)
     d   key_len                      3p 0 options(*nopass)
     d   key                          1a   options(*nopass:*varsize)

     /* prototype of API QRCVDTAQ */
     d qrcvdtaq        pr                  extpgm('QRCVDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                        1a   options(*varsize)
     d   timeout                      5p 0
     d   key_order                    2a   options(*nopass)                     /* optional parameter group 1 */
     d   key_len                      3p 0 options(*nopass)
     d   key                          1a   options(*nopass:*varsize)            /* input/output parameter */
     d   sender_info...
     d     _len                       3p 0 options(*nopass)
     d   sender_info                  1a   options(*nopass:*varsize)
     d   remove_msg                  10a   options(*nopass)                     /* optional parameter group 2 */
     d   receiver_len                 5p 0 options(*nopass)
     d   ec                                likeds(qusec_t)
     d                                     options(*nopass)

     d qname           s             10a
     d qlib            s             10a   inz('*LIBL')
     d entry_len       s              5p 0
     d timeout         s              5p 0 inz(-1)                              /* dequeue infinitely */
     d key_order       s              2a   inz('EQ')                            /* key order: >= parameter @key */
     d key_len         s              3p 0 inz(16)
     d key             s             16a
     d sender_info...
     d   _len          s              3p 0 inz(0)                               /* no sender info */
     d sender_info     s              1a
     d ec              ds                  likeds(qusec_t)

     d do_request      pi            10i 0
      /free

          // generate UUID
          genuuid(%addr(uuid_template));

          // enqueue DTAQ ISQL
          entry_len = 256;
          qname = 'ISQL';
          q_entry.sql = sql;
          qsnddtaq(  qname
                   : qlib
                   : entry_len
                   : q_entry.sql
                   : key_len
                   : uuid_template.uuid );

          // dequeue DTAQ ISQL
          clear ec;
          ec.bytes_in = 256;
          qname = 'ISQLR';
          entry_len = 4;
          qrcvdtaq(  qname
                   : qlib
                   : entry_len
                   : q_entry.ch_sqlcod
                   : timeout
                   : key_order
                   : key_len
                   : uuid_template.uuid
                   : sender_info_len
                   : sender_info );
          if ec.bytes_out <> 0;
              msg = 'QRCVDTAQ() failed with exception ID: '
                       + ec.exid;
              return -9999;
          endif;

          // set msg according to returned SQLCOD
          if q_entry.sqlcod < 0;
              msg = 'SQL statement failed with SQLCOD '
                       + %char(q_entry.sqlcod);
          elseif q_entry.sqlcod > 0;
              msg = 'SQL statement succeeded with SQLCOD '
                       + %char(q_entry.sqlcod);
          else;
              msg = 'SQL statement succeeded';
          endif;

          return q_entry.sqlcod;
      /end-free
     p do_request      e

     /* procedure sendmsg() */
     p sendmsg         b

     /* prototype of QMHSNDPM */
     d qmhsndpm        pr                  extpgm('QMHSNDPM')
     d   msgid                        7a
     d   msgf                        20a
     d   msgdata                    512a   options(*varsize)
     d   msgdata_len                 10i 0
     d   msgtype                     10a
     d   callstack_...
     d     entry                     10a
     d   callstack_...
     d     counter                   10i 0
     d   msgkey                       4a
     d   ec                                likeds(qusec_t)

     d sendmsg         pi
     d   msg                        256a

     d msgid           s              7a   inz('CPF9898')
     d msgf            s             20a   inz('QCPFMSG   QSYS')
     d msgdata_len     s             10i 0
     d msgtype         s             10a   inz('*INFO')
     d callstack_...
     d   entry         s             10a   inz('*PGMBDY')
     d callstack_...
     d   counter       s             10i 0 inz(1)
     d msgkey          s              4a
     d ec              ds                  likeds(qusec_t)

     d cmd             s            512a
     d len             s             15p 5

      /free

          ec.bytes_in = 256;

          msgdata_len = %len(%trim(msg));
          qmhsndpm(  msgid
                   : msgf
                   : msg
                   : msgdata_len
                   : msgtype
                   : callstack_entry
                   : callstack_counter
                   : msgkey
                   : ec );

      /end-free
     p sendmsg         e

     /* eof - isqlcpp.rpgle */

6.4.4. 编写服务程序ISQLSRV

ILE SQLRPG程序 isqlsvr.sqlrpgle 如下
     /*
      * @file isqlsvr.sqlrpgle
      *
      * - dequeue SQL request from DTAQ ISQL
      * - process SQL statement
      * - reply client with SQLCOD
      */
     /* to use activation group scope commitment control */
     h dftactgrp(*no) actgrp('ISQLSVR')

     /*
      * procedure server_client
      *
      * - dequeue SQL request from DTAQ ISQL
      * - process SQL statement
      * - reply client with SQLCOD
      *
      * @return boolean, *off if the main procedure should quit.
      */
     d serve_client    pr              n

     /*
      * procedure run_sql()
      *
      * @return SQLCOD
      */
     d run_sql         pr            10i 0
     d   sql                        256a   options(*varsize)

      /free

          dow serve_client();
          enddo;

          *inlr = *on;
      /end-free
     p serve_client    b

     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /* prototype of API QSNDDTAQ */
     d qsnddtaq        pr                  extpgm('QSNDDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                        1a   options(*varsize)
     d   key_len                      3p 0 options(*nopass)
     d   key                          1a   options(*nopass:*varsize)

     /* prototype of API QRCVDTAQ */
     d qrcvdtaq        pr                  extpgm('QRCVDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   entry_len                    5p 0
     d   entry                        1a   options(*varsize)
     d   timeout                      5p 0
     d   key_order                    2a   options(*nopass)                     /* optional parameter group 1 */
     d   key_len                      3p 0 options(*nopass)
     d   key                          1a   options(*nopass:*varsize)            /* input/output parameter */
     d   sender_info...
     d     _len                       3p 0 options(*nopass)
     d   sender_info                  1a   options(*nopass:*varsize)
     d   remove_msg                  10a   options(*nopass)                     /* optional parameter group 2 */
     d   receiver_len                 5p 0 options(*nopass)
     d   ec                                likeds(qusec_t)
     d                                     options(*nopass)

     d qname           s             10a
     d qlib            s             10a   inz('*LIBL')
     d entry_len       s              5p 0
     d timeout         s              5p 0 inz(-1)                              /* dequeue infinitely */
     d key_order       s              2a   inz('GE')                            /* key order: >= parameter @key */
     d key_len         s              3p 0 inz(16)
     d key             s             16a
     d sender_info...
     d   _len          s              3p 0 inz(0)                               /* no sender info */
     d sender_info     s              1a
     d ec              ds                  likeds(qusec_t)

     d q_entry         ds                  qualified
     d   sql                        256a
     d   sqlcod                      10i 0
     d   ch_sqlcod                    4a   overlay(sqlcod)

     d uuid            s             16a
     d msg             s             26a
     d rtn             s              1a
     d QUIT_CMDS       c                   'q Q qu QU quit QUITT'
     d KEY_ARG         c                   x'00000000000000000000000000000000'

     d serve_client    pi              n
      /free

          rtn = *on;

          // dequeue client request from DTAQ ISQL
          clear ec;
          ec.bytes_in = 256;
          qname = 'ISQL';
          entry_len = 256;
          uuid = KEY_ARG;
          qrcvdtaq(  qname
                   : qlib
                   : entry_len
                   : q_entry.sql
                   : timeout
                   : key_order
                   : key_len
                   : uuid
                   : sender_info_len
                   : sender_info );
          if ec.bytes_out <> 0;
              // error handling
              msg = 'QRCVDTAQ failed: '
                       + ec.exid;
              dsply 'Error' '' msg;
              return *off;
          endif;

          // should i quit?
          if %scan(%trim(q_entry.sql) : QUIT_CMDS) > 0;
              rtn = *off;
          else;
              // run sql statement
              q_entry.sqlcod = run_sql(q_entry.sql);
          endif;

          // enqueue reply to DTAQ ISQLR
          entry_len = 4;
          qname = 'ISQLR';
          qsnddtaq(  qname
                   : qlib
                   : entry_len
                   : q_entry.ch_sqlcod
                   : key_len
                   : uuid );

          return rtn;
      /end-free
     p serve_client    e

     p run_sql         b
     d run_sql         pi            10i 0
     d   sql                        256a   options(*varsize)

     c/exec sql prepare stmt from :sql
     c/end-exec
     c                   if        sqlcod < 0
     c                   return    sqlcod
     c                   endif
     c/exec sql execute stmt
     c/end-exec

     c                   return    sqlcod
     p run_sql         e
     /* eof - isqlsvr.sqlrpgle

编译isqlsvr.sqlrpgle

CRTSQLRPGI OBJ(LSBIN/ISQLSVR) SRCFILE(LS2008/DEC)
           SRCMBR(*OBJ) COMMIT(*CS)
Remarks:
为了启用激活组级别事务控制,编译SQLRPG程序ISQLSVR时,指定了参数COMMMIT(*CS)。

6.4.5. 执行服务程序ISQLSRV和客户ISQL

提交服务程序作业
SBMJOB CMD(CALL PGM(ISQLSVR))

清空要使用SQL操作的物理文件

CLRPFM FILE(PF16)

使用ISQL命令执行删除物理文件PF16的操作

ISQL SQL('delete from pf16')
/*
结果
SQL statement succeeded wich SQLCOD 100.
*/
Remarks:
由于服务程序ISQLSVR使用了事务控制,因此需要对物理文件PF16启用日志。
使用ISQL命令执行删除物理文件PF16的操作
ISQL SQL('insert into pf16 values(''Field 1'', ''Field 2'', ''Field 3'')')
/*
结果
SQL statement succeeded.
*/

使用DSPJOB命令查看服务程序作业的事务控制信息

DSPJOB JOB(257356/LJL/LJL_DAILY) OPTION(*CMTCTL)
可以看到当前服务程序作业已启用activiation group level的事务控制
        Commitment                           
Opt     Definition     Text                  
        ISQLSVR        Activation-group-level
且该事务控制下,有一条未提交(pending)记录
Job:   LJL_DAILY      User:   LJL            Number:   257356         
                                                                      
Commitment definition  . . . . . . . . :   ISQLSVR                    
                                                                      
                                    -------------Changes--------------
File        Library     Member          Commit    Rollback     Pending
PF16        LSBIN       PF16                 0           0           0
                        PF16                 0           0           1

使用ISQL命令提交事务控制

ISQL SQL('commit')
/*
结果
SQL statement succeeded.
*/

6.4.6. 为CL命令ISQL编写一个返回SQLCOD的版本RUNSQL

目的是,当在CL程序或REXX脚本中使用该命令时,通过判断返回的SQLCOD可以决定后续程序逻辑。 runsql.cmd
/* @FILE RUNSQL.CMD */

             CMD        PROMPT('Run SQL statement')
             PARM       KWD(SQL) TYPE(*CHAR) LEN(256) MIN(1) +
                          INLPMTLEN(50) PROMPT('SQL statement')
             PARM       KWD(SQLCOD) TYPE(*CHAR) LEN(4) RTNVAL(*YES) +
                          MIN(1) PROMPT('SQLCOD returned')
/* EOF */

编译CL命令RUNSQL

CRTCMD CMD(LSBIN/RUNSQL) PGM(*LIBL/ISQLCPP) SRCFILE(LS2008/DEC)
       ALLOW(*BPGM *IPGM *BREXX *IREXX)

使用CL命令RUNSQL的示例OPM CL程序 cl103.clp

             /* @FILE CL103.CLP */
PGM
             DCL        VAR(&SQLCOD) TYPE(*CHAR) LEN(4)
 RUN_SQL:    RUNSQL     SQL('delete from pf16') SQLCOD(&SQLCOD) /* +
                          clear PF16 */
             RUNSQL     SQL('insert into PF16 values(''A'', ''B'', +
                          ''C'')') SQLCOD(&SQLCOD) /* insert new +
                          records */
             IF         COND(%BIN(&SQLCOD) *LT 0) THEN(DO)
             SNDPGMMSG  MSG('SQL operations failed.') +
                          TOMSGQ(*TOPGMQ) MSGTYPE(*INFO)
             RUNSQL     SQL('rollback') SQLCOD(&SQLCOD) /* rollbak +
                          when insert operation failed */
             GOTO       CMDLBL(END)
             ENDDO

             RUNSQL     SQL('commit') SQLCOD(&SQLCOD) /* commit */

 END:        ENDPGM
/* EOF - CL103.CLP */

Remarks:
在以上的示例中,请求SQL语句的长度受到了请求DTAQ entry长度 的限制,最长为256字节。要解除对SQL语句长度的限制,请参考 6.5. 通过i5/OS queue object传递变长数据

6.5. 通过i5/OS queue object传递变长数据

6.4. 通过i5/OS queue object模拟同步调用 中 ISQL 的示例中, 请求SQL语句的长度受到了请求DTAQ entry长度 的限制,最长为256字节; 虽然增加请求DTAQ ISQL的 entry 长度,可以允许传递更长的 SQL 语句, 但很难决定究竟多长才够,而对DTAQ而言,允许的最大entry长度也仅有64K字节; 同时,指定过份冗余的DTAQ entry 长度,对存储和性能都是一种浪费。 显然,要解决这里遇到的问题,需要传递变长数据。

那么,如何基于queue object传递变长数据呢? 答案在于指针。 在 2. 对i5/OS Queue Object的介绍 中介绍Queue object的主要属性时, 已经提到,如果queue object的允许message包含指针属性为真, 则可以通过一个queue object传递MI指针。 在MI指针类型中,Space Pointer(下面简称为SPCPTR),用于描述各种存储的寻址属性 (自动存储,静态存储和堆存储)。 作为一个i5/OS MI指针, SPCPTR包含了MI指针的通用属性(如指针类型, 标记位)及SPCPTR的专有属性(如在space object中的偏移)。 SPCPT对等于i5/OS HLL(高级语言)中 指向自动存储,静态存储和堆存储的HLL指针, 如ILE RPG中的pionter类型,或C语言中的void *, char *,等等。 通过在一个允许message包含指针属性的queue object上传递SPCPTR, 可以让出列queue object的程序访问入列程序内部分配的变长数据, 从而达到基于queue object传递变长数据的目的。

Remarks:
这里一个不可避免的前提,当然是,出列queue object的程序 使用入列程序分配的存储时,入列程序必须保证存储未被 释放。

包含指针的结构必须对齐到16字节

IBM没有提供创建一个允许queue message包含指针的DTAQ 的接口;因此,需要在queue object上传递指针时,我们 只能选择USRQ(user queue)。

SPCPTR并不是唯一可以由queue object传递的指针类型, 在queue object传递哪些指针类型,取决于软件的系统架构, 例如,可以通过在queue object上传递指向可执行的program object 的system pointer实现运行时刻的业务逻辑变更;如何 善用这项技术,规避负面影响,取决于作为软件设计者的你。

这里,我们对 6.4. 通过i5/OS queue object模拟同步调用 中 ISQL/ISQLSVR 程序 进行优化,通过在USRQ上传递变长数据而解除对SQL语句长度的限制。 优化后的客户程序和CL命令名称为ISQL2,优化后的服务程序名称为 ISQLSVR2。

ISQL2组件

这里的内容:

6.5.1. 创建USRQ ISQL2, ISQLR2

请求USRQ ISQL2

DTAQ ISQLR2

创建USRQ的CL命令

/* 请求USRQ ISQL2 */
CALL PGM(QUSCRTUQ) PARM(
     'ISQL2     LSBIN'   /* USRQ name */
     'QQ'                /* extended attribute */
     'K'                 /* queue type */
     X'00000010'         /* key length */
     X'00000010'         /* entry length */
     X'00000010'         /* initial number of messages */
     X'00000010'         /* number of messages per extension */
     '*EXCLUDE'          /* public authority */
     'ISQL request queue. key length = 16, entry length = 16' /* text description */
     '*YES'              /* replace */
     X'0000002000000000000000000000000000000000000000000000000000000000' /* error code structure */
     '*USER'             /* object domain */
     '*YES')             /* contains poniter */

/* 响应USRQ ISQLR2 */
CALL PGM(QUSCRTUQ) PARM(
     'ISQLR2    LSBIN'   /* USRQ name */
     'QQ'                /* extended attribute */
     'K'                 /* queue type */
     X'00000010'         /* key length */
     X'00000004'         /* entry length */
     X'00000010'         /* initial number of messages */
     X'00000010'         /* number of messages per extension */
     '*EXCLUDE'          /* public authority */
     'ISQL reply queue. key length = 16, entry length = 4') /* text description */

6.5.2. 编写CL命令ISQL2

CL命令源码 isql2.cmd 如下
/* @FILE ISQL.CMD */

             CMD        PROMPT('Run SQL statement')
             PARM       KWD(SQL) TYPE(*CHAR) LEN(256) MIN(1) +
                          INLPMTLEN(50) PROMPT('SQL statement')
/* EOF */

编译CL命令ISQL2

CRTCMD CMD(LSBIN/ISQL2) PGM(*LIBL/ISQLCPP2) SRCFILE(LS2008/DEC)
       SRCMBR(*CMD) TEXT('i can sql too :p')

6.5.3. 编写CL命令处理程序ISQLCPP2

ILE RPG程序 isqlcpp2.rpgle 如下
     /*
      * @file isqlcpp2.rpgle
      *
      */
     h dftactgrp(*no)

     d sql_param_t     ds                  qualified
     d   sql_len                     10i 0
     d   sql                      32767a

     d i_main          pr                  extpgm('ISQLCPP2')
     d   sql_parm                          likeds(sql_param_t)
     d   sqlcod                      10i 0 options(*nopass)

     /* structure Qus_EC_t */
     d qusec_t         ds           256    qualified
     d   bytes_in                    10i 0
     d   bytes_out                   10i 0
     d   exid                         7a
     d   reserved                     1a
     d   ex_data                    240a

     /*
      * do_request
      *  - send SQL request to server program ISQLSVR2
      *  - receive the SQLCOD returned by ISQLSVR2
      *  - report result message to the user
      *
      * @pre sql
      * @return sqlcod
      */
     d do_request      pr            10i 0

     /* send message to interactive user */
     d sendmsg         pr
     d   msg                        256a

     /*
      * prototype of procedure resolve_qs
      * @return boolean, *on if both the request USRQ and the reply
      *         USRQ are resolved.
      */
     d resolve_qs      pr              n

     /* request USRQ ISQL2 */
     d request_q       s               *

     /* reply USRQ ISQLR2 */
     d reply_q         s               *

     d msg             s            256a
     d rtn             s             10i 0

     d i_main          pi
     d   sql_parm                          likeds(sql_param_t)
     d   sqlcod                      10i 0 options(*nopass)

      /free

          // resolve request q, reply q
          if resolve_qs();
              rtn = do_request();
          endif;

          sendmsg(msg);

          // return SQLCOD to the caller when parameter sqlcod is specified
          if %parms() > 1;
              sqlcod = rtn;
          endif;

          *inlr = *on;
      /end-free

     /* procedure resolve_qs() */
     p resolve_qs      b
     /* prototype of MI instruction RSLVSP */
     d rslvsp          pr                  extproc('_RSLVSP2')
     d   syp                           *
     d   option                        *   value

     d resolve_option  ds            34    qualified
     d   type                         2a   inz(x'0A02')
     d   name                        30a
     d   auth                         2a   inz(x'0000')

     d rtn             s               n

     d resolve_qs      pi              n
      /free

          rtn = *on;

          // resolve request q, reply q
          monitor;   // monitor MCH3401
              resolve_option.name = 'ISQL2';
              rslvsp(request_q : %addr(resolve_option));
              resolve_option.name = 'ISQLR2';
              rslvsp(reply_q : %addr(resolve_option));
          on-error;
              msg = 'Failed to resolve request user queue' +
                    ' or reply user queue';
              rtn = *off;
          endmon;

          return rtn;
      /end-free
     p resolve_qs      e

     /* procedure do_request() */
     p do_request      b

     d uuid_template   ds                  qualified
     d   bytes_in                    10u 0 inz(32)
     d   bytes_out                   10u 0
     d   reserved                     8a   inz(x'0000000000000000')
     d   uuid                        16a

     d genuuid         pr                  extproc('_GENUUID')
     d   template                      *   value

     /* prototype of MI instruction ENQ */
     d enq             pr                  extproc('_ENQ')
     d   q                             *
     d   prefix                        *   value
     d   text                          *   value

     /* prototype of MI instruction DEQ */
     d deq             pr                  extproc('_DEQWAIT')
     d   prefix                        *   value
     d   text                          *   value
     d   q                             *

     /* enq prefix */
     d enq_prefix      ds            20    qualified
     d   text_len                    10i 0
     d   key                         16a

     /* deq prefix */
     d deq_prefix      ds            53    qualified
     d   enqueue_time                 8a
     d   timeout                      8a
     d   text_len                    10i 0
     d   acc_sts_mod                  1a   inz(x'D8')                           /* deq infinitely */
     d   key_in                      16a
     d   key_out                     16a

     /* request data */
     d request         ds                  qualified
     d   sql_len                     10u 0
     d   reserved                     8a                                        /* align to 16 bytes boundary */
     d   sql_ptr                       *
     d request_ptr     s               *   inz(%addr(request))

     /* reply message from ISQLSVR2 */
     d reply           ds            16    qualified
     d   sqlcod                      10i 0

     d do_request      pi            10i 0

      /free

          // generate UUID
          genuuid(%addr(uuid_template));

          // enqueue SQL statement to USRQ ISQL2
          enq_prefix.text_len = 16;
          enq_prefix.key = uuid_template.uuid;
          request.sql_len = sql_parm.sql_len;
          request.sql_ptr = %addr(sql_parm.sql);
          enq(request_q : %addr(enq_prefix) : %addr(request_ptr));

          // dequeue SQLCOD from USRQ ISQLR2
          deq_prefix.key_in = uuid_template.uuid;
          deq(%addr(deq_prefix) : %addr(reply) : reply_q);

          // set msg according to returned SQLCOD
          if reply.sqlcod < 0;
              msg = 'SQL statement failed with SQLCOD '
                       + %char(reply.sqlcod);
          elseif reply.sqlcod > 0;
              msg = 'SQL statement succeeded with SQLCOD '
                       + %char(reply.sqlcod);
          else;
              msg = 'SQL statement succeeded';
          endif;

          return reply.sqlcod;
      /end-free
     p do_request      e

     /* procedure sendmsg() */
     p sendmsg         b

     /* prototype of QMHSNDPM */
     d qmhsndpm        pr                  extpgm('QMHSNDPM')
     d   msgid                        7a
     d   msgf                        20a
     d   msgdata                    512a   options(*varsize)
     d   msgdata_len                 10i 0
     d   msgtype                     10a
     d   callstack_...
     d     entry                     10a
     d   callstack_...
     d     counter                   10i 0
     d   msgkey                       4a
     d   ec                                likeds(qusec_t)

     d msgid           s              7a   inz('CPF9898')
     d msgf            s             20a   inz('QCPFMSG   QSYS')
     d msgdata_len     s             10i 0
     d msgtype         s             10a   inz('*INFO')
     d callstack_...
     d   entry         s             10a   inz('*PGMBDY')
     d callstack_...
     d   counter       s             10i 0 inz(1)
     d msgkey          s              4a
     d ec              ds                  likeds(qusec_t)

     d cmd             s            512a
     d len             s             15p 5

     d sendmsg         pi
     d   msg                        256a

      /free

          clear ec;
          ec.bytes_in = 256;

          msgdata_len = %len(%trim(msg));
          qmhsndpm(  msgid
                   : msgf
                   : msg
                   : msgdata_len
                   : msgtype
                   : callstack_entry
                   : callstack_counter
                   : msgkey
                   : ec );

      /end-free
     p sendmsg         e

     /* eof - isqlcpp2.rpgle */

6.5.4. 编写服务程序ISQLSVR2

ILE SQLRPG程序 isqlsvr2.sqlrpgle 如下
     /*
      * @file isqlsvr.sqlrpgle
      *
      * - dequeue SQL request from USRQ ISQL2
      * - process SQL statement
      * - reply client with SQLCOD
      */
     /* to use activation group scope commitment control */
     h dftactgrp(*no) actgrp('ISQLSVR2')

     /*
      * prototype of procedure resolve_qs
      * @return boolean, *on if both the request USRQ and the reply
      *         USRQ are resolved.
      */
     d resolve_qs      pr              n

     /*
      * procedure server_client
      *
      * - dequeue SQL request from USRQ ISQL2
      * - process SQL statement
      * - reply client with SQLCOD
      *
      * @return boolean, *off if the main procedure should quit.
      */
     d serve_client    pr              n

     /*
      * procedure run_sql()
      *
      * @return SQLCOD
      */
     d run_sql         pr            10i 0
     d   the_sql                   5000a   options(*varsize)

     /* request USRQ ISQL2 */
     d request_q       s               *
     /* reply USRQ ISQLR2 */
     d reply_q         s               *
     d msg             s             26a

      /free

          // resolve request q and reply q
          if not resolve_qs();
              msg = 'Failed to resolve user queues';
              dsply '' '' msg;
              *inlr = *on;
              return;
          endif;

          // serves client
          dow serve_client();
          enddo;

          *inlr = *on;
      /end-free

     /* procedure resolve_qs() */
     p resolve_qs      b
     /* prototype of MI instruction RSLVSP */
     d rslvsp          pr                  extproc('_RSLVSP2')
     d   syp                           *
     d   option                        *   value

     d resolve_option  ds            34    qualified
     d   type                         2a   inz(x'0A02')
     d   name                        30a
     d   auth                         2a   inz(x'0000')

     d rtn             s               n

     d resolve_qs      pi              n
      /free

          rtn = *on;

          // resolve request q, reply q
          monitor;
              resolve_option.name = 'ISQL2';
              rslvsp(request_q : %addr(resolve_option));
              resolve_option.name = 'ISQLR2';
              rslvsp(reply_q : %addr(resolve_option));
          on-error;
              msg = 'Failed to resolve request user queue' +
                    ' or reply user queue';
              rtn = *off;
          endmon;

          return rtn;
      /end-free
     p resolve_qs      e

     p serve_client    b
     /* prototype of MI instruction ENQ */
     d enq             pr                  extproc('_ENQ')
     d   q                             *
     d   prefix                        *   value
     d   text                          *   value

     /* prototype of MI instruction DEQ */
     d deq             pr                  extproc('_DEQWAIT')
     d   prefix                        *   value
     d   text                          *   value
     d   q                             *

     /* enq prefix */
     d enq_prefix      ds            20    qualified
     d   text_len                    10i 0
     d   key                         16a

     /* deq prefix */
     d deq_prefix      ds            53    qualified
     d   enqueue_time                 8a
     d   timeout                      8a
     d   text_len                    10i 0
     d   acc_sts_mod                  1a   inz(x'DA')                           /* deq infinitely, key relation:'GE' */
     d   key_in                      16a
     d   key_out                     16a

     /* request data */
     d request         ds                  qualified
     d                                     based(request_ptr)
     d   sql_len                     10u 0
     d   reserved                     8a                                        /* align to 16 bytes boundary */
     d   sql_ptr                       *
     d request_ptr     s               *
     d sql_str         ds                  based(request.sql_ptr)
     d                                     qualified
     d   string                   32767a

     /* reply message to ISQLCPP2 */
     d reply           ds            16    qualified
     d   sqlcod                      10i 0

     d quit_cmd        s              4a
     d rtn             s               n
     d QUIT_CMDS       c                   'q Q qu QU quit QUITT'
     d MIN_KEY_VALUE   c                   x'00000000000000000000000000000000'  /* 16 bytes */
     d sql             s          32767a

     d serve_client    pi              n

      /free

          rtn = *on;

          // dequeue client's request from USRQ ISQL2
          deq_prefix.text_len = 16;
          deq_prefix.key_in = MIN_KEY_VALUE;
          deq(%addr(deq_prefix) : %addr(request_ptr) : request_q);

          // run sql statement
          sql = %subst(sql_str.string : 1 : request.sql_len);
          quit_cmd = %subst(sql : 1 : 4);
          if %scan(%trim(quit_cmd) : QUIT_CMDS) > 0;
              rtn = *off;
          else;
              reply.sqlcod = run_sql(sql);
          endif;

          // enqueue reply message to USRQ ISQLR2
          enq_prefix.text_len = 4;
          enq_prefix.key = deq_prefix.key_out;
          enq(reply_q : %addr(enq_prefix) : %addr(reply));

          return rtn;
      /end-free
     p serve_client    e

     p run_sql         b
     d run_sql         pi            10i 0
     d   the_sql                   5000a   options(*varsize)

     c/exec sql prepare stmt from :the_sql
     c/end-exec
     c                   if        sqlcod < 0
     c                   return    sqlcod
     c                   endif
     c/exec sql execute stmt
     c/end-exec

     c                   return    sqlcod
     p run_sql         e

     /* eof - isqlsrv2.sqlrpgle */

编译isqlsvr2.sqlrpgle

CRTSQLRPGI OBJ(LSBIN/ISQLSVR2) SRCFILE(LS2008/DEC)
           SRCMBR(*OBJ) COMMIT(*CS)
Remarks:
为了启用激活组级别事务控制,编译SQLRPG程序ISQLSVR时,指定了参数COMMMIT(*CS)。

6.5.5. 编写由CL程序或REXX脚本调用的CL命令RUNSQL2

CL命令RUNSQL2源码runsql2.cmd
/* @FILE RUNSQL2.CMD */

             CMD        PROMPT('Run SQL statement')
             PARM       KWD(SQL) TYPE(*CHAR) LEN(5000) MIN(1) +
                          VARY(*YES *INT4) INLPMTLEN(50) +
                          PROMPT('SQL statement')
             PARM       KWD(SQLCOD) TYPE(*CHAR) LEN(4) RTNVAL(*YES) +
                          MIN(1) PROMPT('SQLCOD returned')
/* EOF */

编译CL命令RUNSQL2

CRTCMD CMD(LSBIN/RUNSQL2) PGM(*LIBL/ISQLCPP2) SRCFILE(LS2008/DEC)
       ALLOW(*BPGM *IPGM *BREXX *IREXX)

6.5.6. 执行服务程序ISQLSVR2和客户ISQL2

编写使用CL命令RUNSQL2的CL程序 cl104.clp
             /* @FILE CL104.CLP */
PGM
             DCL        VAR(&SQLCOD) TYPE(*CHAR) LEN(4)
 RUN_SQL:    RUNSQL2    SQL('delete from pf16') SQLCOD(&SQLCOD) /* +
                          clear PF16 */
             RUNSQL2    SQL('insert into PF16 values(''A'', ''B'', +
                          ''C'')') SQLCOD(&SQLCOD) /* insert new +
                          records */
             IF         COND(%BIN(&SQLCOD) *LT 0) THEN(DO)
             SNDPGMMSG  MSG('SQL operations failed.') +
                          TOMSGQ(*TOPGMQ) MSGTYPE(*INFO)
             RUNSQL2    SQL('rollback') SQLCOD(&SQLCOD) /* rollbak +
                          when insert operation failed */
             GOTO       CMDLBL(END)
             ENDDO

             RUNSQL2    SQL('commit') SQLCOD(&SQLCOD) /* commit */

 END:        ENDPGM
/* EOF - CL104.CLP */
编译OPM CL程序CL104
CRTCLPGM PGM(LSBIN/CL104) SRCFILE(LS2008/DEC) LOG(*YES)

分别使用CL命令ISQL2,调用CL104操作物理文件PF16

/* 提交服务程序作业 */
SBMJOB CMD(CALL PGM(ISQLSVR2))

/* 清空PF16 */
ISQL2 SQL('delete from pf16')
ISQL2 SQL('commit')

/* 执行CL104 */
CALL CL104                                                          
   400 - RUNSQL2 SQL('delete from pf16') SQLCOD(&SQLCOD)            
SQL statement succeeded wich SQLCOD 100.                            
   600 - RUNSQL2 SQL('insert into PF16 values(''A'', ''B'', ''C'')')
  SQLCOD(&SQLCOD)                                                   
SQL statement succeeded.                                            
  1700 - RUNSQL2 SQL('commit') SQLCOD(&SQLCOD)                      
SQL statement succeeded.                                            
       - RETURN        /* RETURN due to end of CL program */        

/* 通知ISQLSVR2退出 */
ISQL2 SQL('qu')
Remarks:
由于ISQLSVR2使用事务访问物理文件,所以需要对以上示例中的物理文件PF16启用日志。

6.6. Java和i5/OS Queue object

作为IPC手段,Queue object同样可以供Java程序使用。 IBM Toolbox for Java 中提供了对Data Queue的访问支持, 这样通过使用 IBM Toolbox for Java 的客户机版本jt400.jar或主机版本jt400Native.jar,Java程序可以 从客户机或主机侧操作Data Queue对象;详细信息请参考 BaseDataQueue 类及及派生类的文档。

IBM没有提供由Java直接访问User Queue的接口,要在Java程序中使用User Queue需要使用主机语言编写访问User Queue的JNI方法。

作为IPC方法,Queue Object为Java程序与Java程序间,或Java程序与主机语言程序间进行进程间通讯带来了这样的好处

这里,作为示例,我们将使用Java实现 6.5. 通过i5/OS queue object传递变长数据 中的客户程序ISQL2。

Java版本的ISQL2组件如下:

这里的内容:

6.6.1. 编写Java程序isql.java, isqlsvr.java

Java程序 isql.java
public class isql {

    static {

        System.loadLibrary("ISQLSVR");
    }

    public static final String _usage =
        "usage info: java isql \"SQL statement to run.\"";

    public static void main(String[] args) {

        isql client = new isql();
        client.run(args);

    }

    protected void run(String[] args) {

        if(args.length < 1) {

            System.out.println(_usage);
            return;
        }

        String msg = "";
        try {

            // send request to ISQLSVR
            int sqlcod = isqlsvr.sendRequest(args[0]);

            // compose msg
            if(sqlcod < 0)
                msg = "SQL statement failed with SQLCOD " + sqlcod;
            else
                msg = "SQL statement succeeded with SQLCOD " + sqlcod;

        } catch(Exception e) {

            System.out.println(e.getMessage());
            return;
        }

        // report execuation result
        System.out.println(msg);
    }

}

/* eof - isql.java */
Java类isqlsvr.java
public class isqlsvr {

    public
        static
        native
        synchronized
            int sendRequest(
                            String sql
                            ) throws Exception;

}

/* eof - isqlsvr.java */

编译Java源码

/* 将源码上传到IFS */
/* 启动一个QShell session */
qsh

/* 确定CLASSPATH环境变量中包含当前路径,如: */
export CLASSPATH=.

/* 切换到源码目录 */
/* 使用javac编译Java类 */
javac -encoding utf-8 *java

/* 使用javah生成JNI方法头文件isqlsvr.h */
javah isqlsvr
Remarks:
使用javac编译时,如果使用-encoding选项指定源文件码制, 则-encoding选项指定的码制要与流文件的实际 码制相符;例如,如果流文件CCSID属性为1208,则应使用-encoding utf-8; 如果流文件CCSID属性为935,则应使用-encoding ibm-935。

查看流文件的CCSID属性可以使用CL命令wrklnk,然后在文件项上使用 8(Display attributes)选项;或使用QShell命令attr,如

attr isql.java

6.6.2. 编写JNI方法

isqlsvr.c
# include <isqlsvr.h>

// libc
# include <stdlib.h>
# include <string.h>
# include <except.h>

// libmi
# include <mih/rslvsp.h>
# include <mih/enq.h>
# include <mih/genuuid.h>

// libiconv
# include <iconv.h>

// apis
# include <qusec.h>
# include <qusrjobi.h>

typedef _Packed struct _deq_prefix {

  char time_enqueued[8];
  char timeout[8];
  int text_len_returned;
  char acc_state_mod[1];
  char key_in[16];
  char key_out[16];
} deq_prefix_t;

// prototype of instruction DEQWAIT
# pragma linkage(_DEQWAIT, builtin)
void _DEQWAIT (
               deq_prefix_t *,
               void*,
               _SYSPTR*
               );

# define REQUEST_Q "ISQL2"
# define REPLY_Q   "ISQLR2"
# define UTF8_CCSID \
  "IBMCCSID012080000000\0\0\0\0\0\0\0\0\0\0\0\0"
# define ECLEN   256
# define TEXTLEN 16
# define KEYLEN  16
# define ENQ_PREFIX_LEN 20
# define DEQ_PREFIX_LEN 53

typedef _Packed struct _request {

  size_t sql_len;
  char reserved[8];
  char *sql;
} request_t;

static request_t _r;

typedef _Packed struct _reply {

  int sqlcod;
  char reserved[12];
} reply_t;

void gen_uuid(char *uuid);

int get_job_ccsid();

void
utf8_2_ebcdic (
               const char *source,
               size_t source_length,
               char *dest
               );

void enq_request(
                 _SYSPTR q,
                 const char *key,
                 const char *sql
                 );

int deq_reply(_SYSPTR q, const char* key);

int resolve_queues(
                   _SYSPTR *request_q,
                   _SYSPTR *reply_q
                   );

/*
 * implements isqlsvr.sendRequest()
 *
 * - resolve request q
 * - resolve reply q
 * - enq SQL
 */
JNIEXPORT
jint
JNICALL
Java_isqlsvr_sendRequest (
                          JNIEnv *env,
                          jclass jthis,
                          jstring jsql
                          ) {

  int r = 0;
  int sqlcod = 0;
  jclass class_exception;
  const char *utf8_sql = NULL;
  char *sql = NULL;
  jboolean is_copy = JNI_FALSE;
  size_t len = 0;
  char uuid[16] = {0};

  _SYSPTR request_q = NULL;
  _SYSPTR reply_q = NULL;

  /* resolve qs */
  if(resolve_queues(&request_q, &reply_q) != 0) {

    // raise exception and return
# pragma convert(819)
    class_exception = (*env)->FindClass(env, "java/lang/Exception");
# pragma convert(0)
    if(class_exception != 0)
      (*env)->ThrowNew(env,
                       class_exception,
                       "Failed to resolve user queues"
                       );
    return;
  }

  // convert sql to ebcdic
  utf8_sql = (*env)->GetStringUTFChars(env, jsql, &is_copy);
  len = (*env)->GetStringUTFLength(env, jsql);
  sql = malloc(len * 2 + 1);
  utf8_2_ebcdic(utf8_sql, len, sql);
  (*env)->ReleaseStringUTFChars(env, jsql, utf8_sql);

  // enq request q
  gen_uuid(uuid);
  enq_request(request_q, uuid, sql);

  // deq reply q
  sqlcod = deq_reply(reply_q, uuid);

  free(sql);
  return sqlcod;
}

void gen_uuid(char *uuid) {

  _UUID_Template_T tmpl;

  memset(&tmpl, 0, 32);
  tmpl.bytesProv = 32;
  _GENUUID(&tmpl);

  memcpy(uuid, tmpl.uuid, 16);
}

int get_job_ccsid() {

  int ccsid = 0;
  Qwc_JOBI0400_t jobi;
  Qus_EC_t *ec = NULL;

  ec = (Qus_EC_t*)malloc(ECLEN);
  memset(ec, 0, ECLEN);
  ec->Bytes_Provided = ECLEN;
  jobi.Bytes_Avail = sizeof(Qwc_JOBI0400_t);
  QUSRJOBI(
           &jobi,
           sizeof(Qwc_JOBI0400_t),
           "JOBI0400",
           "*                         ",
           "                ",
           ec
           );
  if(ec->Bytes_Available != 0)
    ccsid = 37; // ascii ccsid
  else
    ccsid = jobi.Coded_Char_Set_ID;

  free(ec);
  return ccsid;
}

void
utf8_2_ebcdic (
               const char *source,
               size_t source_length,
               char *dest
               ) {

  size_t result_length = source_length * 2;
  char host_ccsid[32 + 1] = {0};
  iconv_t cvt;

  sprintf(host_ccsid,
          "IBMCCSID%05d\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",
          get_job_ccsid());
  cvt = iconv_open(host_ccsid, UTF8_CCSID);

  iconv(cvt,
        &source,
        &source_length,
        &dest,
        &result_length
        );

  iconv_close(cvt);
}

void enq_request(
                 _SYSPTR q,
                 const char *key,
                 char *sql
                 ) {

  _ENQ_Msg_Prefix_T *prefix;
  request_t *request = &_r;
  char text[16] = {0};

  prefix = (_ENQ_Msg_Prefix_T*)malloc(ENQ_PREFIX_LEN);
  memset(prefix, 0, ENQ_PREFIX_LEN);
  prefix->Msg_Len = TEXTLEN;
  memcpy(prefix->Msg, key, KEYLEN);

  request->sql_len = strlen(sql);
  request->sql = sql;
  memcpy(text, &request, 16);

  _ENQ(&q, prefix, text);

  free(prefix);
}

int deq_reply(_SYSPTR q, const char* key) {

  deq_prefix_t prefix;
  reply_t reply;

  prefix.acc_state_mod[0] = 0xF8; // 1111,1000; key realtion=EQ, deq infinitely
  memcpy(prefix.key_in, key, KEYLEN);

  _DEQWAIT(&prefix, &reply, &q);

  return reply.sqlcod;
}

int resolve_queues(
                   _SYSPTR *request_q,
                   _SYSPTR *reply_q
                   ) {

  int resolved = -1;
  volatile int ca = 0;

# pragma exception_handler(end, ca, _C1_OBJECT_NOT_FOUND, \
                           _C2_ALL, _CTLA_HANDLE)
  *request_q = rslvsp(0x0A02, REQUEST_Q, "", 0x0000);
  *reply_q = rslvsp(0x0A02, REPLY_Q, "", 0x0000);
  resolved = 0;

# pragma disable_handler

 end:
  return resolved;
}

/* eof - isqlsvr.c */

编译Service Program ISQLSVR

/* 编译module ISQLSVR */
CRTCMOD MODULE(LSBIN/ISQLSVR) SRCSTMF(isqlsvr.c) INCDIR('.')

/* 创建*SRVPGM ISQLSVR */
CRTSRVPGM LSBIN/ISQLSVR MODULE(*LIBL/ISQLSVR) EXPORT(*ALL)

6.6.3. 执行Java类isql

在i5/OS上运行Java程序,可以使用CL命令JAVA或RUNJVA,或QShell命令java。 下面首先提交ISQLSVR2作业,然后执行Java类isql。
/* clear physical file PF16 */
CLRPFM PF16

/* submit job ISQLSVR2 */
SBMJOB CMD(CALL ISQLSVR2)

/* 修改当前路径为isql.class, isqlsvr.class所在的IFS目录,如 */
CHGCURDIR '/home/ljl/tmp'

/* run java class isql */
JAVA CLASS(isql) PARM('delete from pf16')
/*
结果
SQL statement succeeded with SQLCOD 100
Java program completed
*/

JAVA CLASS(isql) PARM('insert into pf16 values(''20081212'', ''冬天'',''Friday'')')
/*
结果
SQL statement succeeded with SQLCOD 0
Java program completed
*/

/* check physical file PF16 */
RUNQRY *N PF16
/*
结果
Line   ....+....1....+....2....+....3....+.
       RQ        NAME              REMARK  
000001 20081212  冬天              Friday  
 ********  End of report  ********   
*/

附录

附录列表

Appendix 1. Enqueue USRQ Q11 for 1000 times

使用API QUSCRTUQ创建USRQ Q11,类型为FIFO,entry长度64
CALL PGM(QUSCRTUQ) PARM(
  'Q11       LSBIN'        /* queue name: LSBIN/Q11 */
  'USRQ'                   /* extended attribute    */
  'F'                      /* queue type: keyed     */
  X'00000000'              /* key length: 0         */
  X'00000040'              /* max entry length: 64  */
  X'00010000'              /* initial number of messages: 65536  */
  X'00001000'              /* extend number of messages: 4096    */
  '*EXCLUDE'               /* public authority                   */
  'FIFO USRQ: entry length = 64'  /* text description            */
)

入列 USRQ Q11的MI程序enq11.mi

dcl sysptr q auto                ; 
dcl dd message-prefix char(4) auto;
  dcl dd prefix-msglen bin(4) def(message-prefix) pos(1);

dcl dd message char(64) auto init("is usrq more faster?") ;
dcl spcptr .message auto init(message) ; 

entry * ext;

dcl dd index bin(4) auto init(0) ; 

        cpybla rslv-option-obj-type, x"0A02";
        cpyblap rslv-option-obj-name, "Q11", " ";
        rslvsp q, rslv-option-short, *, x'0000';

        /* enq */
        cpynv prefix-msglen, 64;
        cpynv index, 0          ; 
enq-loop:
        addn(s) index, 1        ;
        cmpnv(b) index, 1000 / hi(end-loop) ; 
        enq q, message-prefix, .message;

        b enq-loop              ; 
end-loop:       

        rtx *;

%include ptrres.mi              ; 

pend;

/* eof - enq11.mi */

ILE RPG程序renq11.rpgle 负责调用program enq11完成1000次入列操作,并统计用时:

     /*
      * @file renq11.rpgle
      *
      * call PGM ENQ11 for 1000 times
      */

     d b               s               z
     d e               s               z
     d ind             s             10i 0
     d dur             s             15p 0

     c                   eval      b = %timestamp()
     c                   call      'ENQ11'
     c                   eval      e = %timestamp()
     c                   eval      dur = %diff(e : b : *ms)

     c     'microseconds'dsply                   dur
     c                   seton                                          lr
     /* eof - renq11.rpgle */

调用程序RENQ11 10次,执行结果如下:

DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
DSPLY  microseconds              4000
/*
结果:
10次调用的平均结果为4000 micro-seconds(0.004s)
*/

使用 i5/OS Programmer's ToolkitDSPQD:命令查看USRQ Q11

DSPQD Q(LSBIN/Q11) QTYPE(*USRQ)
/* DSPQD命令的部分输出 */
Current maximum number of             
  messages . . . . . . . . . :   65536
Current number of messages            
  enqueued . . . . . . . . . :   10000
Extension value  . . . . . . :   4096 
Key length . . . . . . . . . :   0    
Maximum size of message to be         
  enqueued . . . . . . . . . :   64   
Maximum number of extends  . :   35
Current number of extends  . :   0   
Initial number of messages . :   65536
可以看到当进行10次对RENQ11的调用后,USRQ Q11上当前有10000条message(Current number of messages enqueued), 未发生过扩展(Current number of extends)。

Appendix 2. Enqueue DTAQ Q12(not journaled) for 1000 times

使用CL命令CRTDTAQ创建DTAQ Q12,类型为FIFO,entry长度64:
CRTDTAQ DTAQ(LSBIN/Q12)       /* queue name         */
        MAXLEN(64)            /* entry length: 64   */
        SEQ(*FIFO)            /* queue type: keyed  */
        SIZE(*MAX16MB 65536)  /* initial number of messages: 65536 */
        TEXT('FIFO DTAQ: entry length=64')

ILE RPG程序renq12.rpgle 负责完成1000次入列 DTAQ Q12的操作,并统计用时:

     /*
      * @file renq12.rpgle
      *
      * enqueue DTAQ Q12 for 1000 times
      */

     d b               s               z
     d e               s               z
     d ind             s             10i 0
     d dur             s             15p 0

     c                   eval      b = %timestamp()
     c                   for       ind = 1 to 1000  by 1
     c                   call      'QSNDDTAQ'
     c                   parm      'Q12'         qname            10
     c                   parm      'LSBIN'       qlib             10
     c                   parm      64            entlen            5 0
     c                   parm      *all'1'       ent              64
     c                   endfor
     c                   eval      e = %timestamp()
     c                   eval      dur = %diff(e : b : *ms)

     c     'microseconds'dsply                   dur
     c                   seton                                          lr
     /* eof */

调用程序RENQ12 10次,执行结果如下:

DSPLY  microseconds             17000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
DSPLY  microseconds             14000
DSPLY  microseconds             15000
DSPLY  microseconds             15000
/*
结果:
10次调用的平均结果为15100 micro-seconds(0.0151s)
*/

Appendix 3. Enqueue DTAQ Q12(journaled) for 1000 times

使用API QCLRDTAQ清空DTAQ Q12:
CALL PGM(QCLRDTAQ) PARM('Q12' 'LSBIN')

对DTAQ Q12启用日志:

STRJRNOBJ OBJ(LSBIN/Q12) OBJTYPE(*DTAQ) JRN(LSBIN/JRN01)

调用 Appendix 2. Enqueue DTAQ Q12(not journaled) for 1000 times 中的ILE RPG程序RENQ12 10次,执行结果如下:

DSPLY  microseconds            4066000
DSPLY  microseconds            4019000
DSPLY  microseconds            4145000
DSPLY  microseconds            4018000
DSPLY  microseconds            4007000
DSPLY  microseconds            4206000
DSPLY  microseconds            4031000
DSPLY  microseconds            4042000
DSPLY  microseconds            4023000
DSPLY  microseconds            4042000
/*
结果:
10次调用的平均结果为4059900 micro-seconds(4.0599s)
*/

Appendix 4 Possible Journal Entry Type for Journal Code Q(Data queue operation)

Appendix 5 A Joblog Saver Program

作为对 6.2. Output Queue的Data Queue支持 的补充说明,这里介绍一个 Joblog Saver程序。 该程序将OUTQ QEZJOBLOG中的spooled file实时地存入数据文件,用于在 进行问题分析时备查;同时删除OUTQ中的spooled file,即时地回收 处于OUTQ状态的作业及spooled file自身占用的系统资源。

创建DTAQ
创建DTAQ JOBLOGNTF,类型为FIFO,entry长度128

CRTDTAQ DTAQ(LSBIN/JOBLOGNTF) MAXLEN(128) SIZE(*MAX2GB) AUTORCL(*YES) TEXT('i watch joblogs')

准备保存joblog的数据文件JOBLOG

CRTPF FILE(LSBIN/JOBLOG) RCDLEN(132) IGCDTA(*YES)

修改OUTQ的DTAQ属性

CHGOUTQ OUTQ(QEZJOBLOG) DTAQ(LSBIN/JOBLOGNTF)

Joblog Saver程序
编写ILE RPG程序 savjoblog.rpgle

     /*
      * @file savjoblog.rpgle
      *
      * @remark 需要退出时,向DTAQ JOBLOGNTF入列个QUIT
      *          e.g. CALL PGM(QSNDDTAQ) PARM('JOBLOGNTF'
      *                 'LSBIN' X'00010F' 'QUIT')
      *
      */
     h dftactgrp(*no)

     /*
      * dequeue a notification message from DTAQ JOBLOGNTF
      *
      * @return *on if okey, *off if notified to quit.
      */
     d deq_notify      pr              n
     /*
      * copy joblog to PF JOBLOG;
      * delete spooled file.
      */
     d wrk_with_splf   pr

     /* Notification information */
     d notify          ds           128    qualified
     d   func_code                   10a
     d   rec_type                     2a
     d   job_id                      26a
     d   splf_name                   10a
     d   splf_num                    10i 0

      /free

          dow deq_notify();
              wrk_with_splf();
          enddo;

          *inlr = *on;
      /end-free

     /* deq_notify() */
     p deq_notify      b
     /* prototype of API QRCVDTAQ */
     d qrcvdtaq        pr                  extpgm('QRCVDTAQ')
     d   qname                       10a
     d   qlib                        10a
     d   qentrylen                    5p 0
     d   qentry                     128a
     d   timeout                      5p 0

     d qname           s             10a   inz('JOBLOGNTF')
     d qlib            s             10a   inz('LSBIN')
     d qentrylen       s              5p 0 inz(128)
     d timeout         s              5p 0 inz(-1)
     d quit            c                   'QUIT'

     d deq_notify      pi              n

      /free

          qrcvdtaq( qname
                   : qlib
                   : qentrylen
                   : notify
                   : timeout );

          if %subst(notify.func_code : 1 : 4) = quit;
              return *off;
          endif;

          return *on;
      /end-free
     p deq_notify      e

     p wrk_with_splf   b
     /* prototype of API QCMDEXC */
     d qcmdexc         pr                  extpgm('QCMDEXC')
     d   cmdstr                     128    options(*varsize)
     d   cmdlen                      15p 5

     d cmd             s            128a
     d len             s             15p 5
     d jid             s             28a

      /free

          jid = %subst(notify.job_id:21:6)
                + '/'
                + %trim(%subst(notify.job_id:11:10))
                + '/'
                + %trim(%subst(notify.job_id: 1:10));

          monitor;

          // copy spooled file to PF JOBLOG
          cmd = 'CPYSPLF FILE('
                + %trim(notify.splf_name)
                + ') TOFILE(LSBIN/JOBLOG) JOB('
                + %trim(jid)
                + ') SPLNBR('
                + %char(notify.splf_num)
                + ') MBROPT(*ADD)';
          len = %len(%trim(cmd));
          qcmdexc(cmd : len);

          // delete spooled file
          cmd = 'DLTSPLF FILE('
                + %trim(notify.splf_name)
                + ') JOB('
                + %trim(jid)
                + ') SPLNBR('
                + %char(notify.splf_num)
                + ') ';
          len = %len(%trim(cmd));
          qcmdexc(cmd : len);

          on-error;
          endmon;

      /end-free
     p wrk_with_splf   e
     /* eof */

提交Joblog Saver作业

SBMJOB CMD(CALL PGM(LSBIN/SAVJOBLOG)) JOB(JOBLOGSVR)
当joblog saver作业提交后,在OUTQ QEZJOBLOG上产生的spooled file 被joblog saver自动复制到数据文件JOBLOG,然后被删除。 用于试验,可以故意在OUTQ QEZJOBLOG上制造一个spooled file,如:
/* 下面的命令将导致被提交的作业在正常退出后,记录joblog */
SBMJOB CMD(DLYJOB DLY(3)) JOB(MAKEJOBLOG) LOG(*JOBD *JOBD *SECLVL)
当上面提交的作业退出后,查看文件JOBLOG,将可以看到该作业的JOBLOG信息。
  5722SS1 V5R2M0 020719                           Job Log                             810      09/05/19 14:24:35          Page    1
   Job name . . . . . . . . . . :   MAKEJOBLOG      User  . . . . . . :   LJL          Number . . . . . . . . . . . :   254693
   Job description  . . . . . . :   LJL_DAILY       Library . . . . . :   QGPL
 MSGID      TYPE                    SEV  DATE      TIME             FROM PGM     LIBRARY     INST     TO PGM      LIBRARY     INST
 CPF1124    Information             00   09/05/19  14:24:32.615208  QWTPIIPP     QSYS        05D4     *EXT                    *N
                                      Message . . . . :   Job 254693/LJL/MAKEJOBLOG started on 09/05/19 at 14:24:32
                                        in subsystem QBATCH in QSYS. Job entered system on 09/05/19 at 14:24:32.
 CPI1125    Information             00   09/05/19  14:24:32.619792  QWTPCRJA     QSYS        0108     *EXT                    *N
                                      Message . . . . :   Job 254693/LJL/MAKEJOBLOG submitted.
                                      Cause . . . . . :   Job 254693/LJL/MAKEJOBLOG submitted to job queue QBATCH in
                                        QGPL from job 254687/LJL/GREENTEA. Job 254693/LJL/MAKEJOBLOG was started
                                        using the Submit Job (SBMJOB) command with the following job attributes:
                                        JOBPTY(5) OUTPTY(5) PRTTXT() RTGDTA(QCMDB) SYSLIBL(QSYS       QSYS2
                                        QHLPSYS    QUSRSYS) CURLIB(LSBIN) INLLIBL(QGPL       QTEMP      LSBIN
                                        LJLCP      ATS        XTXRUN     MN         I5TOOLKIT) LOG(4 00 *SECLVL)
/* .... ... */

通知joblog saver作业退出
savjoblog.rpgle在DTAQ JOBLOGNTF上 接收一条内容为‘QUIT'的entry时,认为被通知退出。因此通知joblog saver作业退出只需要:

CALL PGM(QSNDDTAQ) PARM('JOBLOGNTF' 'LSBIN' X'00010F' 'QUIT')

Appendix 6 参考信息

关于作者

自我介绍下呗,我是李君磊,来自天津,一个很讲究生活质量的城市。 严格来说,我是个400新手,到这个OS下面来写东西不过3年多的时间。 不能讲对这个OS喜爱或不喜爱,但有一点是肯定的, 这个OS是精巧复杂的,在许多方面的设计是先进或超前的。

在我维护的开源项目 i5/OS Programmer's Toolkit 中,多半代码是MI的。 在此,不得不感谢 Mr. Leif Svalgaard,他在 midrange.com 的文章为我学习MI提供了莫大帮助。 还有几个我常去的网站,相信大家也常常去看,比如:

我的邮件: junleili-cn@users.sourceforge.net,欢迎同行们的意见与建议 :)


Support This Project
Generated on Mon Aug 22 08:26:42 2011 for i5/OS Programmer's Toolkit: Articles by  doxygen 1.5.9