MQI支持的各种通道函数类型 MQ产品提供了出口函数的特性,可以让用户根据自己的业务需求选择使用不同的出口函数来实现不同的功能。MQ服务器与服务器之间的连接通道属于单向通道,称作为消息通道(Message Channel),而客户端应用和MQ服务器之间的连接通道称作为MQI通道,属于双向通道,为实现客户端应用对MQ服务器资源的访问而服务。以下表格列举了MQI通道所支持的通道出口函数;本文以MQI通道支持的发送和接收出口函数为例讲述如何用C语言实现MQI通道上数据的加密或压缩功能。
通道类型
| 接收出口 | 安全出口 | 发送出口 | 自动定义出口 | 客户机连接通道
| 支持 | 支持 | 支持 | 不支持 | 服务器连接通道
| 支持 | 支持 | 支持 | 支持 | MQI通道发送/接收出口函数的工作原理 MQ的消息通道的概念是由数据传送的两端的MCA实现的,也就是说对于消息通道而言,就是数据传送的两端的MCA进程。而MQI通道稍有不同;MQI通道连接的是客户端应用和MQ服务器;在客户端主要由MQ客户端API担当数据的交互任务,充当通道代理的角色;而服务端是服务器连接通道(Server Connection Channel)负责数据的接收/发送以及为客户端应用实现对MQ队列管理器中资源的访问而服务。 服务器端的出口函数由服务器通道维护和调用,客户端的出口函数则有MQ客户端API去调用。在MQ客户端应用试图连接MQ队列管理器时,需要知道如何寻址队列管理器、如何寻址出口函数的可装载程序包。 MQ的C/C++客户端可以有两种方式寻址队列管理器:一,从当前运行环境中寻找MQSERVER环境变量的定义,该定义格式为:MQSERVER=MY_SVRCONN/TCP/'127.0.01(1414)';二,如果MQSERVER环境变量未定义,则寻找客户端连接通道(Client Connection Channel)定义文件,分别由MQCHLLIB和MQCHLTAB两个环境变量所决定;MQCHLLIB指定客户端连接通道定义文件的所在的路径,而MQCHLTAB指定客户端连接通道定义文件的名称。 在环境变量MQSERVER中无法引用通道出口函数的可装载程序包,因此要实现出口函数,需要采用第二种方式,在客户机连接通道定义中指定出口函数。 通常,MQI通道发送/接收出口函数是成对使用的,并且被分别定义在通道的两端。当客户机在发送数据时,客户端的发送出口函数被调用,服务器端的接收出口函数被调用;一个完整的消息报文会通过多次的交互之后完成;因此当服务器端要返回与调用相关的结果参数时,服务器端的发送出口函数和客户端的接收出口函数也会被调用。 MQI通道出口函数的C语言框架 通道出口函数是被客户端API或服务器连接通道的进程动态装载的;用C语言实现的源代码框架如下: /******************************* MQI出口函数C语言代码框架 *******************************/ #include <cmqc.h> #include <cmqxc.h>
void MQStart() {;} /* dummy entry point - for consistency only */ void MQENTRY ChannelExit ( PMQVOID pChannelExitParms, PMQVOID pChannelDefinition, PMQLONG pDataLength, PMQLONG pAgentBufferLength, PMQVOID pAgentBuffer, PMQLONG pExitBufferLength, PMQPTR pExitBufferAddr) { ... Insert code here } 首先需要包含的是MQ提供的两个头文件:cmqc.h和cmqxc.h。然后声明并定义MQStart()函数以及其他需要被引用的出口函数原型。 以上框架中的ChannelExit()函数的声明定义是MQ要求用户提供的通道出口函数的定义格式。该定义包含六个参数,通道出口函数本身并不要求有返回值,而是在函数参数表中种通过传址方式获得返回数据。 PMQVOID pChannelExitParms, 通道出口参数结构指针:该参数为输入/输出方式的VOID类型指针,指向一个MQCXP类型的通道出口参数结构;从该结构中可以查询到当前被调用的原因代码,比如是初始化调用还是传输调用,以及调用的出口类型是安全出口还是发送出口或是接受出口类型等等。该结构同时也接收出口函数本身的参数回传,以便于用户控制MCA通道代理的处理方式。 PMQVOID pChannelDefinition, 通道定义结构指针:该参数为输入/输出方式的VOID类型指针,指向一个MQCD类型的通道定义结构;在该结构中MQ事先保存了调用本函数的当前通道定义数据;从该结构中我们可以判断当前通道名称、通道类型以及其他所有的通道属性。 PMQLONG pDataLength, 数据块长度指针:该参数为输入/输出方式的LONG类型指针,该出口函数被调用时,该指针内存放着代理缓冲区(AgentBuffer)中数据的实际长度;当出口函数即将返回前,需要在此处设定代理缓冲区(AgentBuffer)数据块中的数据长度;如果用户使用了自己的缓冲区(ExitBuffer),则此处为ExitBuffer中数据的实际长度。 PMQLONG pAgentBufferLength 代理缓冲区长度指针:该参数为输入方式的LONG类型指针,该指针指向MQ要传送的数据块所在缓冲区的长度;该长度是MQ通道代理能提供的最大缓冲长度;该参数指定的长度一般大于实际的数据长度,剩余的未使用的空间,用户可以使用,但是不能超过该长度;如果用户觉得该缓冲区不够用,可以自己申请数据缓冲区,使用以下pExitBufferLength和pExitBufferAddr参数指定缓冲区地址和长度。 PMQVOID pAgentBuffer 代理缓冲区指针:该参数为输入/输出方式的VOID类型指针,该指针指向MQ要传送的数据块所在的缓冲区;用户出口函数可以直接修改该缓冲区中的数据块;其最大长度由pAgentBufferLength指定。 PMQLONG pExitBufferLength, 用户缓冲区长度指针:该参数为输入/输出方式的LONG类型指针,MCA通道代理并不使用该参数;当该函数被第一次调用时(如客户端连接MQ服务器并装载出口函数程序包时),此参数为0,用户函数将已申请的内存空间最大长度存放在该参数内,以便在随后的调用中重复使用参数值。 PMQPTR pExitBufferAddr 用户缓冲区地址指针:该参数为输入/输出方式的指针类型的指针,MCA通道代理并不使用该参数;当该函数被第一次调用时(如客户端连接MQ服务器并装载出口函数程序包时),此参数为null,用户函数可以申请内存空间并将地址存在该参数内,在随后的调用中,该参数会被MCA通道代理回传给用户函数,以便于用户函数在同的出口函数之间重复使用该内存空间并传送数据,该内存空间由用户函数自己管理。MQ建议,如果使用不支持指针的编程语言开发用户出口函数,则不要使用该参数。 MQI通道发送/接收出口函数的样例代码 下面是用C语言实现的通道发送/接收出口函数的完整的源文件。该样例实现了客户端C应用程序和MQ服务器之间通道发送/接收出口函数。其中函数xor()主要用以模拟压缩/解压和加密/解密算法,在实际应用中可以由用户使用特定的算法代替。该函数只是对缓冲区中的数据块做异或操作,二次异或则可还原被异或的数据。 该样例将发送出口和接收出口合成为一个函数,通过switch语句来根据调用上下文做出相应的操作。 /*---------------------------------------------------------- * mqexit.c - Source file for C language MQ Exit functions *----------------------------------------------------------*/ #include <stdio.h> #include <cmqc.h> #include <cmqxc.h> static int TSHOFFSET=10; static int MIN_MASK_SIZE=300; void xor(unsigned char *p, unsigned long len); void MQENTRY ChannelExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ); void MQENTRY SendMessageExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ); void MQENTRY ReceiveMessageExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ); void MQStart( void ) { ; } void MQENTRY ChannelExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ) { PMQCXP pChlExParms = ( PMQCXP ) channelExitParms; PMQCD pChDef = ( PMQCD ) channelDef; switch ( pChlExParms->ExitId ) { case MQXT_CHANNEL_SEND_EXIT: SendMessageExit( channelExitParms, channelDef, dataLength, agBufLength, agBuf, exitBufLength, exitBufAddr ); break; case MQXT_CHANNEL_RCV_EXIT: ReceiveMessageExit( channelExitParms, channelDef, dataLength, agBufLength, agBuf, exitBufLength, exitBufAddr ); break; default: ; } } /* * This is the function for the send message exit. */ void MQENTRY SendMessageExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ) { PMQCXP pChlExParms = ( PMQCXP ) channelExitParms; PMQCD pChDef = ( PMQCD ) channelDef; unsigned char *pp; long len; switch ( pChlExParms->ExitReason ) { case MQXR_XMIT: switch ( pChDef->ChannelType ) { case MQCHT_CLNTCONN: pChlExParms->ExitResponse = MQXCC_OK; pChlExParms->ExitResponse2 = MQXR2_USE_AGENT_BUFFER; if(*dataLength < MIN_MASK_SIZE) break; pp=agBuf; len=*dataLength; pp+=TSHOFFSET; xor(pp,len-TSHOFFSET); *dataLength=(*dataLength);
break; case MQCHT_SVRCONN: pChlExParms->ExitResponse = MQXCC_OK; pChlExParms->ExitResponse2 = MQXR2_USE_AGENT_BUFFER; if(*dataLength < MIN_MASK_SIZE) break; pp=agBuf; len=*dataLength; pp+=TSHOFFSET; xor(pp,len-TSHOFFSET); *dataLength=(*dataLength);
break; default: ; } break; default: ; } } /* * This is the function for the receive message exit */ void MQENTRY ReceiveMessageExit( PMQVOID channelExitParms, PMQVOID channelDef, PMQLONG dataLength, PMQLONG agBufLength, PMQVOID agBuf, PMQLONG exitBufLength, PMQPTR exitBufAddr ) { PMQCXP pChlExParms = ( PMQCXP ) channelExitParms; PMQCD pChDef = ( PMQCD ) channelDef;
unsigned char *pp; long len; switch ( pChlExParms->ExitReason ) { case MQXR_XMIT: switch ( pChDef->ChannelType ) { case MQCHT_SVRCONN: pChlExParms->ExitResponse = MQXCC_OK; pChlExParms->ExitResponse2 = MQXR2_USE_AGENT_BUFFER; if(*dataLength < MIN_MASK_SIZE) break; pp=agBuf; len=*dataLength; pp+=TSHOFFSET; xor(pp,len-TSHOFFSET); *dataLength=(*dataLength);
break;
case MQCHT_CLNTCONN: pChlExParms->ExitResponse = MQXCC_OK; pChlExParms->ExitResponse2 = MQXR2_USE_AGENT_BUFFER; if(*dataLength < MIN_MASK_SIZE) break; pp=agBuf; len=*dataLength; pp+=TSHOFFSET; xor(pp,len-TSHOFFSET); *dataLength=(*dataLength);
break;
default: ; } break; default: ; } } void xor(unsigned char *p, unsigned long len) { unsigned int i=0; for(i=0;i<len;i++) { p[i]^=(unsigned char)255; } } 编译MQI通道发送/接收出口函数的源代码 由于MQ通道出口函数是被客户端API和服务器端的MCA通道代理在运行时动态装载的,因此编译时须指定编译器生成动态装载代码。 在Windows平台上使用Visual C/C++编译器的makefile文件如下: #--------------------------------------------- # makefile on Windows XP for VC/C++ 6.0 #--------------------------------------------- MQM_DIR=<MQ安装路径> MQM_INC=$MQM_DIR/Tools/c/include MQM_EXIT_PATH=$MQM_DIR/exits CPP=cl.exe CPP_PROJ=/nologo /MD /W3 /GX /O2 /D "NDEBUG" /D "WIN32" / /D "_WINDOWS" / /FD /c /I $(MQM_INC) LINK32=link.exe LINK32_FLAGS= /nologo /subsystem:windows /dll /EXPORT:ChannelExit /EXPORT:SendMessageExit /EXPORT:ReceiveMessageExit all:mqexit.dll mqexit.dll:$*.obj makefile $(LINK32) $(LINK32_FLAGS) $*.obj /OUT:$*.dll copy $*.dll $(MQM_EXIT_PATH) .c.obj: $(CPP) $(CPP_PROJ) $< #--------------------------------------------- 在AIX 5L平台上使用Visual Age C/C++编译器的makefile文件如下: #--------------------------------------------- # makefile on AIX 5L for VAC #--------------------------------------------- CCFLAG=-I/usr/mqm/inc -G -qmkshrobj -q32 all:mqexit mqexit:mqexit.c xlc_r ${CCFLAG} -eMQStart -omqexit mqexit.c mv mqexit /var/mqm/exits #--------------------------------------------- MQI通道发送/接收出口函数的配置 通道出口函数是注册在MQI通道的,以下是创建MQI通道的MQSC命令,注意:客户机连接通道名称必须与服务器连接通道名称相同。 创建服务器连接通道A.SVRCONN的MQSC命令: def channel(A.SVRCONN) chltype(SVRCONN) TRPTYPE(TCP) + SENDEXIT('mqexit(ChannelExit)') RCVEXIT('mqexit(ChannelExit)') replace 创建客户机连接通道A.SVRCONN的MQSC命令: def channel(A.SVRCONN) chltype(CLNTCONN) CONNAME('<服务器IP地址>(1414)') + TRPTYPE(TCP) QMNAME(<队列管理器名称>) SENDEXIT('mqexit(ChannelExit)') + RCVEXIT('mqexit(ChannelExit)') replace MQI通道发送/接收出口函数的验证 设置环境变量MQCHLLIB为客户机连接通道定义文件所在的路径: Windows平台: Set MQCHLLIB=<MQ安装路径>/Qmgrs/<队列管理器名称>/@ipcc AIX平台: export MQCHLLIB=/var/mqm/QMgrs/<队列管理器名称>/@ipcc 执行amqsoutc命令发送消息报文,如果成功发送,并能用amqsgetc成功接收消息报文,则说明该出口函数以能正常工作。 参考资料: 《WebSphere MQ Intercommunication》 《WebSphere MQ Clients》 《MQSeries Security : Example of Using a Channel Security Exit, Encryption and Decryption |