时间:2003-12-05
作者:经乾(dev2dev ID: jq75) BEA系统(中国)有限公司 渠道部技术顾问
在TUXEDO 8.0中,客户机和服务器之间可以使用XML缓冲区进行数据交换,但由于8.0版本没有集成XML Parser,所以对XML的支持是有限的。从8.1版本开始,TUXEDO集成了Apache Xerces C++ Parser 1.7,这样服务器端就可以直接分析XML文档了,而不用再去调用其它XML Parser。
TUXEDO 8.1的samples中给了一个xmlstockapp例子,由于它太复杂(至少我这么认为),因此,本文将通过一个简单实用的例子来介绍XML缓冲区使用。
一.XML缓冲区的分配和传输
XML缓冲区支持的最大长度是4GB,分配方法与CARRAY缓冲区一样,需要指定长度。客户端需要打开一个XML文档,以字符方式读取文档内容,保存到缓冲区中,然后由tpcall()调用提交给服务进程。清单1-1是通信录应用程序的XML客户机代码段。
清单1-1 XML缓冲区客户机代码段,文件名:XML_cli.c
#define XMLDOCSIZE 1048576
... ...
FILE *xml_fd;
char *xml_buffer = NULL;
if ((xml_fd = fopen("friends.xml", "r")) != NULL) {
xml_buffer = (char *)malloc(sizeof(char) * XMLDOCSIZE);
nsize = fread(xml_buffer, sizeof(char), XMLDOCSIZE, xml_fd);
}
sendlen = nsize + 1; /* 实际读取的长度 */
if((sendbuf = (char *) tpalloc((char *)"XML", NULL, sendlen)) == NULL) {
fprintf(stderr,"Error allocating send buffer, tperrno=%ld\n",tperrno);
tpterm();
return(1);
}
strncpy(sendbuf, xml_buffer, nsize);
ret = tpcall((char *)"ADD_FRIEND", (char *)sendbuf, sendlen, (char **)&rcvbuf, &rcvlen, TPNOTIME);
... ...
复制到缓冲区的XML文件必须遵循1.0标准,可以包含任何自定义的标记。本例中用到了friends.xml,这个文件包含了两条要添加到数据库好友信息,内容如清单1-2所示。
清单1-2 XML文件示例,文件名:friends.xml
<?xml version='1.0' encoding='UTF-8'?>
<friends>
<friend>
<friend_id>1</friend_id>
<fname>JQ</fname>
<fmobile>13910793488</fmobile>
</friend>
<friend>
<friend_id>2</friend_id>
<fname>Snna</fname>
<fmobile>13663129935</fmobile>
</friend>
</friends>
二.基于XML标记的DDR
XML缓冲区支持基于标记值的DDR,对于通信录应用程序来说,如果我们要把friend_id取值在1-10范围内的记录保存在table1中,把取值在11-20范围内的记录保存在table2中,把其它取值范围的记录保存在table3中,则可以在ubb配置文件中定义下面的路由标准来实现:
*ROUTING
symbol FIELD="friends/friend/ friend_id"
BUFTYPE="XML"
FIELDTYPE=LONG
RANGES="1-10:GROUP1,11-20:GROUP2,*:GROUP3"
XML缓冲区的路由字段还可以是STRING类型的,这时RANGES的取值必须用单引号引起来,请看如下代码段:
*ROUTING
symbol FIELD="stockquotes/stock_quote/symbol"
BUFTYPE="XML"
FIELDTYPE=STRING
RANGES="'BEAS'-'BEAS':GROUP1, 'MSFT'-'MSFT':GROUP2"
三.XML缓冲区的分析
服务器端收到XML缓冲区后,需要对它进行分析,从中取出标记值。Tuxedo 8.1集成了Apache Xerces C++ Parser 1.7,并提供了两种类型的分析器:SAX Parser和DOM Parser,服务器端通常使用DOM Parser,客户端通常使用SAX Parser。使用Xerces C++ Parser的步骤一般是:
· 初始化XMLC42系统;
XMLPlatformUtils::Initialize();
· 根据XML缓冲区创建MemBufferInputSource;
MemBufInputSource* memBufIS =
new MemBufInputSource( (const XMLByte*)xmlbuf, strlen(xmlbuf), bufId, false);
· 创建SAX Parser或DOM Parser;
DOMParser *parser = new DOMParser;
SAXPArser *parser = new SAXParser;
· 对于SAX Parser,需要为文档和错误处理设置回调函数;
SAXPrintHandlers handler;
parser->setDocumentHandler(&handler);
parser->setErrorHandler(&handler);
· 调用Xerces C++ Parser分析XML缓冲区;
parse->parse(*memBufIS);
· 删除Parser和MemBufferInputSource;
delete parser;
delete memBufIS;
· 退出XMLC42系统;
XMLPlatformUtils::Terminate();
对于通信录应用程序来说,客户机给服务器传递了friends.xml文件,服务器端创建了一个DOM Parser对它进行分析,并把结果存入数据库,代码如清单1-3所示。
清单1-3 XML缓冲区的服务器代码,文件名:XML_serv.pc
#include <xercesc/parsers/DOMParser.hpp>
#include <xercesc/util/PlatformUtils.hpp>
#include <xercesc/framework/MemBufInputSource.hpp>
#include <atmi.h>
#include <userlog.h>
#include <stdlib.h>
#include <string.h>
char *localbuf=NULL;
static const char* bufId = "mybuf";
EXEC SQL begin declare section;
static long friend_id;
static char fname[10];
static char fmobile[14];
EXEC SQL end declare section;
EXEC SQL INCLUDE sqlca;
void TopTree( DOM_Node node);
void SubTree( DOM_Node node);
void parseXMLBuffer(char** xmlbuf);
#ifdef __cplusplus
extern "C"
#endif
void
#if defined(__STDC__) || defined(__cplusplus)
ADD_FRIEND(TPSVCINFO *rqst)
#else
ADD_FRIEND(rqst)
TPSVCINFO *rqst;
#endif
{
parseXMLBuffer(&rqstàdata);
tpreturn(TPSUCCESS, 0, xmlbuf, rqstàlen, 0);
}
void parseXMLBuffer(char** xmlbuf)
{
int errorCount ;
localbuf = (char *)malloc(sizeof(char *) * 2048);
XMLPlatformUtils::Initialize();
MemBufInputSource* memBufIS =
new MemBufInputSource( (const XMLByte*)*xmlbuf, strlen(*xmlbuf)-1, bufId, true);
DOMParser *parser = new DOMParser;
parseràparse(*memBufIS);
if ((errorCount = parseràgetErrorCount()) == 0)
{
DOM_Document document = parseràgetDocument();
DOM_Element topLevel = document.getDocumentElement();
TopTree(topLevel);
}
delete parser;
delete memBufIS;
XMLPlatformUtils::Terminate();
}
void TopTree( DOM_Node node){
if (node.getNodeType() == DOM_Node::ELEMENT_NODE)
{
if (node.getNodeName().equals ("friend")) SubTree(node);
else
{
DOM_NodeList children = node.getChildNodes();
for (int i=0; i<children.getLength(); i++) TopTree(children.item(i));
}
}
}
void SubTree( DOM_Node node){
DOM_NodeList children = node.getChildNodes();
for (int i=0; i<children.getLength(); i++)
{
DOM_Node nod = children.item(i);
if(nod.getNodeType()==DOM_Node::ELEMENT_NODE)
{
if(nod.getNodeName().equals("friend_id"))
friend_id = atol(nod.getFirstChild().getNodeValue().transcode());
else if(nod.getNodeName().equals("fname"))
strcpy(fname, nod.getFirstChild().getNodeValue().transcode());
else if(nod.getNodeName().equals("fmobile"))
strcpy(fmobile, nod.getFirstChild().getNodeValue().transcode());
}
}
EXEC SQL insert into FRIEND (FRIEND_ID,NAME,MOBILE)
values (:friend_id, :fname, :fmobile);
if (SQLCODE != SQL_OK) userlog("Cannot insert into FRIEND");
}
注意:服务器文件名为XML_serv.pc,先要使用esqlc命令对它作预编译,生成C源程序,然后把扩展名改为cpp,才能用buildserver来编译,原因是Xerces C++ Parser只提供了C++的编程接口。读者可以参考如下命令来编译:
proc iname=XML_serv.pc oname=XML_serv.cpp
buildserver -v -s ADD_FRIEND -f XML_serv.cpp -o XML_serv -f %TUXDIR%\lib\libtxml.lib
四.UBBConfigNT配置文件的编写
这个例子使用了Oracle8i数据库来测试,Tuxedo的配置文件如清单1-4所示。
清单1-4 Tuxedo的配置文件,文件名:UBBConfigNT
*RESOURCES
IPCKEY 123456
DOMAINID simpapp
MASTER SITE1
MAXACCESSERS 10
MAXSERVERS 5
MAXSERVICES 10
MODEL SHM
LDBAL N
*MACHINES
DEFAULT:
APPDIR="C:\TuxDAP\solutions\xml"
TUXCONFIG="C:\TuxDAP\solutions\xml\tuxconfig"
TUXDIR="G:\bea\tuxedo"
TLOGNAME=TLOG
TLOGDEVICE="C:\TuxDAP\solutions\xml\TLOG"
JQ LMID=SITE1
*GROUPS
DEFAULT: LMID=SITE1
GROUP1 LMID=simple GRPNO=1
OPENINFO="Oracle_XA:Oracle_XA+Acc=P/scott/tiger+SqlNet=oradb+SesTm=120+
MaxCur=5+LogDir=."
TMSNAME="TMS_ORA8i" TMSCOUNT=2
*SERVERS
DEFAULT: RESTART=Y MAXGEN=5 REPLYQ=Y CLOPT="-A"
XML_serv SRVGRP=GROUP1 SRVID=10
*SERVICES
ADD_FRIEND
*ROUTING
五.运行测试
执行XML_Cli.exe进行测试,friends.xml文件已经通过XML缓冲区成功地发送到服务器端;
打开SQL*PLUS,查看一下FRIEND表的内容,发现里面已经插入了两条记录,这两条记录的数据就是通过friends.xml文件来传输的!