服务程序能够以“客户进程发送请求同样方式”的指定优先级来转交请求。你可能记得,这可通过调用tpsprio( )函数来完成。
当tpforward( )被调用时,与tpreturn( )相同,main( )恢复控制,服务进程可以自由地做更多的工作。该函数语法如下:
void
tpforward(svc, data, len, flags) /* Forward request */ char *svc, *data; long len, flags;
tpforwad( )的参数
tpforward( )的第一个参数svc,是一字符指针,表示请求将被转交的服务名。请求缓冲区由第二个参数data来指定,请求数据的长度放在len中。这两个参数(data、len)和最后的一个参数flags与tpreturn( )中相应的参数是相同的意思。回忆一下,当前,flags没有定义值。
发送未被恳求的信息
BEA TUXEDO系统允许未被恳求的信息发送给客户进程,而不扰乱“请求/响应调用或者会话通讯”的处理。通过名字(tpbroadcast( ))或者通过标识符
(tpnotify( )),未被恳求的信息能够发送给客户进程。经tpbroadcast( )发送的信息可以是来自服务或者来自另一个客户。经tpnotify( )发送的信息仅仅能来自服务。如下表:
Initiator Receiver tpbroadcast() client, server client tpnotify() server client
tpbroadcast( )参数 tpbroadcast( )允许信息发送给应用系统的已注册客户。(已注册客户是那些已成功调用tpinit( )但还没有调用tpterm( )的客户。)该函数的语法如下:
int
tpbroadcast(lmid, usrname, cltname, data, len, flags) char *lmid, *usrname, *cltname, *data; long len, flags;
lmid、usrname和cltname是指向标识符的指针,用来选择客户的目标列表。这些参数的NULL值是作为它们的通配符,以便信息能够被指引到客户组或全部领域。
参数data指向信息的内容(由len参数决定长度)。如果data指向自定义缓冲区类型,例如FML缓冲区,则len可以是0。参数flags可以是:
TPNOBLOCK
如果阻塞条件存在,则不发送信息。 TPNOTIME
不确定地等待;不超时。 TPSIGRSTRT
当一个信号对仍何潜在的系统调用进行中断时,则调用被重新发起。若
该标志没有被设置,则一信号导致tpbroadcast( )失败,并且错误码为TPGOTSIG。
tpnotify( )的参数
tpnotify( )只能从服务进行调用。该函数语法如下:
int
tpnotify(clientid, data, len, flags) CLIENTID *clientid; char *data;
long len, flags;
*clientid是一指向CLIENTID结构的指针。*data、len和flags参数 与它们在tpbroadcast( )中一样。
登广告、取消广告服务
当服务器被导入时,它们登服务广告。缺省的规格调用(为了服务器)为所有被建立的服务登广告;这就是-A选项所表示的意思。当一服务被广告,它持有公告牌中的服务表条目。这能引导应用去决定导入服务器,来提供它们的可用服务的子集。如servopts(5)参考部分介绍的,-s选项允许一“逗号格开的”服务(由服务名指定的)列表。它也允许用-s services:func符号。BEA TUXEDO系统管理员能够用tmadmin(1)的登广告和取消广告的命令来控制服务器提供的服务。
tpadvertise( )和tpunadvertise( )函数,允许动态的控制在一请求/响应服务器的服务内被运用或者会话服务器登记或取消一服务的广告。局限性是:被登广告(或取消广告)的服务必须是在“与制造请求的服务的服务器相同的”服务器内是可用的。
tpadvertise( )的参数
tpadvertise的语法如下:
int
tpadvertise(svcname, func) char *svcname; void (*func);
*svcname是一指针,指向一15个字符或更少个字符的字符串(用来命名被登广告的服务)。名字长度若长于15个字符,则截去多出的部分;若为空,则将产生一个错误,[TPEINVAL]。
func是BEA TUXEDO系统服务函数(被用来处理服务的)的地址。func不允许为空。
tpunadvertise( )
tpunadvertise( )是用来从公告牌的服务列表中去掉一个服务。语法如下:
tpunadvertise(svcname) char *svcname;
唯一的参数是一指向被取消广告的svcname。
系统提供的服务器和子程序
BEA TUXEDO系统是由提供基本客户鉴别服务(AUTHSVR)的服务器来递送的。一个标准的main( )程序和两个被main( )调用的子程序也被提供。
系统提供的服务器
本节描述的服务器,目的是帮助你摆脱为常规工作任务编写服务程序而带来的烦恼。
AUTHSVR
AUTHSVR(5)用来为应用软件提供单独的客户鉴别。它是当应用软件的安全级别为TPAPPAUTH时,被tpinit(3c)调用的。
AUTHSVR中的服务,在TPINIT缓冲区的data字段中查找用户口令(不要与TPINIT缓冲区的passwd字段中的应用口令相混淆)。data中的串的检测与/etc/passwd文件相反(缺省的,应用系统能指定一个不同的被检测文件)。当data字段被一本地端客户使用时,它将象被接收时那样,用tpinit( )往前发送。这意味着,如果应用系统想将口令进行加密,则客户程序必须因此而编写代码。当被工作站客户使用时,则在通过网络发送数据之前,tpinit( )对数据进行加密。
BEA TUXEDO系统的main( )
为加快服务器发展的速度,BEA TUXEDO系统为服务器装载模块提供一预先确定的main( )程序。当buildserver(1)命令被执行时,main( )将被自动包括进来。 预先确定的main( )程序做下面的事情:
? ? ? ? ?
运行进程而不被挂起(忽略UNIX系统的SIGHUP信号)
安排清除接收到的标准UNIX系统软件的终止信号(SIGTERM)。服务器关闭和当再需要时重启。
为了公告牌服务,而加入到共享内存。 为进程创建一个信息队列。
为服务器提供的初始服务登广告。初始服务或者是所有与预先确定的main( )连接编辑的服务,或者是BEA TUXEDO系统管理员在配置文件中指定的子集。
处理命令行参数,将之最终转化为系统能识别的双破折号(--)。 调用tpsvrinit( )函数,来处理任何出现在双破折号(--)之后的命令行参数,并且随意开启资源管理器。这些参数是用于系统特殊初始化。 为服务请求信息检测它的请求队列,直到被命令终止。
? ? ?
? 直到被命令终止,当一服务请求信息到达请求队列:
o 如果-r选项被指定,则记录服务请求的开始时间 o 修改公告牌,以表示服务器为BUSY(忙)
o 为请求信息分配缓冲区并且分派服务;即调用服务子程序
? 直到被命令终止,当服务从处理它的输入返回时:
o如果-r选项被指定,则记录服务请求的结束时间 o 修改统计表
o修改公告牌,以表示服务器为IDLE;即准备工作 o 为下一个服务请求检测队列
? 当服务器可能终止时,调用tpsvrdone( ),处理任何可能需要的用户停机
操作。
系统提供的main( )是不能被程序员修改的。正如前面所讲的,它提供进出应用系统、缓冲区和交易管理、通讯等所有的细节。使程序员有空余时间通过服务子程序逻辑去实现应用。
除了上面的功能性外,main( )中还存在两个“允许程序员做不同的初始化和退出活动”的用户。下面部分解释这两个系统提供的子程序是怎样被使用的。
BEA TUXEDO系统提供的子程序
有两个main( )提供的子程序,tpsvrinit( )和tpsvrdone( ),它们由BEA
TUXEDO系统软件提供。缺省的版本可被修改,以适合于你的应用。
tpsvrinit( )
当服务器被导入时,BEA TUXEDO系统的main( ),在处理服务请求之前的初始化阶段,调用tpsvrinit( )。如果应用系统在服务器中没有提供这个程序,则一缺省将被调用,用来打开资源管理器和在中心事件日志中记录“服务器已经成功开始”的信息。中心事件日志将在第七节“错误日志”中讨论。现在,你只要简单的理解为,通过调用userlog(3c)函数写入信息。在系统提供的main( )的开始部分,tpsvrinit( )被用来满足应用所需要的任何初始化意图。这里介绍两种可能性:接收命令行选项和打开一数据库。
注意在后面的例子中,虽然没有出现信息通讯,但信息通讯也能在这个程序中被执行。如果tpsvrinit( )以未决定的异步应答返回,则将失败。另外,应答被BEA TUXEDO忽略,并且服务器温和地存在。tpsvrinit( )也能开始和完成一个交易,但这将在第七节“错误日志”中讨论。
该函数的语法如下: int
tpsvrinit(argc, argv) /* Server initialization routine */ int argc; char **argv;
使用tpsvrinit( )接收命令行选项
当服务器被启动,在调用tpsvrinit( )程序之前,它在配置文件中读为它指定的选项。使用UNIX函数getopt(3c)(见UNIX系统程序员参考手册),它读选项
直到EOF出现。命令行中的双引号导致getopt返回EOF。getopt将下一个将被处理的argv索引放在外部变量optind中。预先定义的main( )接着调用tpsvrinit( )。
Listing3-9表是关于tpsvrinit( )接收命令行选项的例子。
Listing 3-9 Receiving Command Line Options in tpsvrinit()
tpsvrinit(argc, argv) int argc; char **argv; {
int c;
extern char *optarg; extern int optind; . . .
while((c = getopt(argc, argv, \
switch(c){ . . . } . . . }
当BEA TUXEDO系统的main( )调用tpsvrinit( )时,它读取命令行中双破折号(--)后的任何参数。上面的例子中,选项f和x每个都带一参数,是用冒号显示的。optarg指向选项参数的开始。我们省略了switch部分的逻辑。
使用tpsvrinit( )打开资源管理器
listing 3-10表显示一个源程序片段,介绍tpsvrinit( )的另外一个通常用法:打开资源管理器。BEA TUXEDO提供打开资源管理器的函数:tpopen( )和tx_open( )。它也提供补充函数:tpclose( )和tx_close( )。这些ATMI基本件的详细资料可以在《BEA TUXEDO参考手册》中找到。应用系统使用这些调用来打开和关闭它们的资源管理器是非常方便的。它们通过访问在配置文件中可用的、资源管理器特殊场合信息来工作。在下面的例子中,源程序不读取命令行选项,但没有理由既不读取选项,又不打开数据库。同样,注意userlog(3c)函数写重要事件日志。
Listing 3-10 Opening a Resource Manager in tpsvrinit()
1.序论
BEA TUXEDO系统开发环境
这一章介绍BEA TUXEDO系统开发环境。你要编写的应用软件,也就是在此环境下 进行的。
你除了用C程序来表述应用软件的逻辑性外,你将会用到“应用交易监控接口”(ATMI),它涉及到BEA TUXEDO系统交易监控和应用软件间的界面。ATMI基本件是类UNIX系统调用的C语言函数;但它们,在BEA TUXEDO系统交易监控的控制下,在应用模块间实现通讯(包括所有你需要的相关资源),有特殊的用途。
BEA TUXEDO系统着重于“客户—服务”这种体系结构。这本书的第2到第7章描述了ATMI是怎样用于编写、调试客户端和服务端的程序。本章只作简要介绍。
客户进程
一个客户进程要求用户发送一个请求给服务进程,服务进程将返回一个响应。
基本的客户端操作
一个客户进程用一ATMI基本件介入一个应用系统,利用另一基本件进行信息缓存的分配。这些基本件的应用,持续到信息缓存被送往一个服务并且得到响应为止。
下面是一个简单的描述:
main() {
allocate a TPINIT buffer
place initial client identification in buffer
enroll as a client of the BEA TUXEDO application allocate buffer
do while true {
place user input in buffer send service request receive reply pass reply to the user } leave application }
------------------------------------------------------------------------------- 上图大部分是由ATMI基本件来完成的。place user input in buffer和pass reply to the user部分是由C语言来完成。
当客户程序准备测试时,你要先用buildclient(1)命令来编译和链接它们。
客户重复发送服务请求
一个客户在离开应用程序之前,可以发送和接受很多个服务请求。这些将作为一系列的“请求/响应”调用而被发送。如果有重要的信息需要从一个调用传到另一个调用,则会有一通向会话服务的连接被设立。客户程序里的逻辑也是如此,不过是不同的ATMI基本模块被使用而已。
服务器进程和服务子程序
服务器是提供一个或多个服务的进程。它们不停地检测服务请求的消息队列,并且为它
们分配适当的服务子程序。
基本的服务端操作
应用软件通过将它们的服务子程序与BEA TUXEDO提供的main()连接起来,达到建立服务进程的目的。本系统提供的main()是一套预定义的函数。它执行服务器的初始化和终止;为“接收和分派”引入的请求给服务程序,进行缓冲区的分配。所有这些处理相对于应用程序来说是透明的。
服务器和服务子程序的交互作用,可以通过下面的描述来概括(图Figure1-1):
Figure 1-1 Pseudo-code for a Request/Response Server and a Service Subroutine
一个服务在初始化后分配缓冲器,一直等待到请求信息加入到消息队列、请求信息出
列、分派它们给服务子程序处理。如果需要应答,还得考虑请求处理部分。
会话范例稍微有些不同。假想的代码如下(Figure1-2)。
Figure 1-2 Pseudo-code for a Conversational Service Subroutine
BEA TUXEDO系统提供的main()包含的内容有:使自己成为一个服务、公布服务、分配缓冲区、使请求信息出列。ATMI基本件被应用于处理请求的服务子程序中。当服务子程序准备编译和测试时,它们将通过buildserver( )命令连接到服务端的main(),形成可执行的服务模块。
ATMI基本件
“应用交易监控接口”(ATMI)是一套非常紧凑的基本件,这些基本件用于开关资源、开始和终止交易、分配和释放缓存、提供客户端和服务端的通信。
下表是它们的简要描述:
组 名 称 操 作 应用接口 tpinit( ) 连接一个应用软件 tpterm( ) 离开一个应用软件 缓存管理接口 tpalloc( ) 分配缓存 tprealloc( ) 重新计量缓存 tpfree( ) 释放缓存 tptypes( ) 取缓存类型
请求/响应 通信接口 会话接口 主动告示 接口 交易管理 接口 服务程序模板 动态广告接口 资源管理接口 事件载体和事件监控器接口 tpcall( ) tpacall( ) tpgetrply( ) tpcancel( ) tpgprio( ) tpsprio( ) tpconnect( ) tpdiscon( ) tpsend( ) tprecv( ) tpnotify( ) tpbroadcast( ) tpsetunsol( ) tpgetunsol( ) tpchkunsol( ) tpbegin( ) tpcommit( ) tpabort( ) tpgetlev( ) tpservice( ) tpreturn( ) tpforward( ) tpadvertise( ) tpunadvertise( ) tpopen( ) tpclose( ) tppost( ) tpsubscribe( ) tpunsubscribe( ) 发送请求 等待响应 异步方式发送请求 异步方式调用后接受回应 取消通信 处理特殊响应 取最后请求的优先级 取下一请求的优先级 开始一个会话 结束一个会话 在会话中发送数据 在会话中接受数据 通过客户id识别 通过名称识别 设置主动信息来处理交易 取主动信息 检查主动信息 开始一个交易 提交当前交易 退出当前交易 检测是否处如交易模式中 开始一个服务 结束一个服务 向前请求并且结束服务程序 登一服务名广告 取消登一服务名广告 打开一资源管理 关闭一资源管理 投递一个事件 预订一个事件 解除一预订事件 X/Open’s TX接口纵观:
除了ATMI的交易管理动词,BEA TUXEDO系统也支持X/Open’s TX接口,用来定义和管理交易。因为X/Open用ATMI的交易划分动词作为TX界面的基础,TX界面的语法和语义与ATMI十分相似。
下表是它们之间的比较。在很大程度上,应用 ATMI程序的地方也可用TX来实现,如下表:
TX 动词 tx_begin tx_close tx_commit tx_info tx_open tx_rollback tx_set_commit_return tx_set_transaction_control 相应的ATMI动词 主要区别 tpbegin tpclose tpcommit tpgetlev tpopen tpabort tpscmt 无 超时值不传递给tx_begin 参看tx_set_transaction_timeout 无 tx_commit在返回前可以开始一个新的交易 这是一种“链式”交易 tx_info返回交易特性的设置,这些特性是由三个tx_set_*程序设定的 无 tx_rollback支持“链式”交易 无 定义应用软件是“链式”交易还是非“链式”交易 tx_set_transaction_timeout tpbegin 将交易延时参数与tx_begin分开 TX界面在使用其它TX动词前,要求先调用tx_open( )。
下面是关于TX界面怎样用于支持“链式”交易的事例。注意:tx_begin被用于开始一系列链式交易;还有,在调用tx_close之前,应用软件必须转到非链式交易,使最后的tx_commit( )或tx_rollback( )不开始一新的交易。
tx_open();
tx_set_transaction_control(TX_CHAINED); tx_set_transaction_timeout(120); tx_begin();
o_forever {
do work as part of transaction;
if (no more work exists)
tx_set_transaction_control(TX_UNCHAINED); if (work done was successful) tx_commit(); else
tx_rollback();
if (no more work exists) break; }
tx_close();
类型缓冲区
信息是在类型缓冲区中传递给服务的。为什么说是“类型”呢?如果缓冲区信息在不同的机器间传递,则不同类型的数据需要不同的软件来初始化缓冲区、还要发送和接受数据(或许还要加密和解密)。缓冲区被定义成特殊的类型,以使程序适应缓冲区,且使它的内容能被调用。详细情况可以从buffer(3c)、tuxtypes(5)和typesw(5)中找到。
BEA TUXEDO系统提供九种缓冲区类型:STRING,CARRAY,VIEW,VIEW32,FML,FML32,X_OCTET,X_COMMON和X_C_TYPE。应用软件还可以定义其他所需要的类型。
当数据是以空字符结尾的字符数组时,STRING缓冲区类型就会被用到。 在CARRAY缓冲区中的数据是一组未定义的字符,它们都可为空。当传输这种缓冲区类型时,需要提供它的长度。X_OCTET缓冲区类型等价于CARRAY。 VIEW类型是应用软件定义的一种C结构,而且还有对它的描述文件。VIEW类型的缓冲区必须有子类型,用来指定个别的数据结构。X_C_TYPE缓冲区类型等价于VIEW。X_COMMON缓冲区类型与VIEW很相似,但它既可用于COBOL,又可用于C语言,它的字段类型局陷于short、long和string型。VIEW32缓冲区类型与VIEW相似,但它允许更大的字符域、更多的域和全部的缓冲区。
FML缓冲区是BEA TUXEDO系统独有的类型,是自定义的,每一数据域有自己的标识符、事件数目、可能有的长度指示器。这种类型在处理所化的费用上有很好的机动性,在这种数
listing 3-2表表明,服务访问数据缓冲区来决定它的类型。该服务知道不止一个缓冲区类型,并且通过调用tptypes( )ATMI函数基本件,来确定接收的请求的缓冲区类型。它也可找到缓冲区的最大尺寸,因此它知道是否要重新分配缓冲区尺寸。这个例子是来自ABAL服务。
Listing 3-2 Checking for Buffer Type
#define TMTYPERR 1 /* return code indicating tptypes failed */ #define INVALMTY 2 /* return code indicating invalid message type */ void
ABAL(transb)
TPSVCINFO *transb; {
struct aud *transv; /* view message */
FBFR *transf; /* fielded buffer message */ int repc; /* tpgetrply return code */
char typ[TMTYPELEN+1], subtyp[TMSTYPELEN+1]; /* type, subtype of message */
char *retstr; /* return string if tptypes fails */
/* find out what type of buffer sent */
if (tptypes((char *)transb->data, typ, subtyp) == -1) { retstr=tpalloc(\
(void)sprintf(retstr,
\ tpreturn(TPFAIL, TMTYPERR, retstr, 100, 0); }
/* Determine method of processing service request based on type */ if (strcmp(typ, \ transf = (FBFR *)transb->data;
... code to do abal service for fielded buffer ... tpreturn succeeds and sends FML buffer in reply }
else if (strcmp(typ, \{
transv = (struct aud *)transb->data;
... code to do abal service for aud struct ... tpreturn succeeds and sends aud view buffer in reply }
else {
retstr=tpalloc(\
(void)sprintf(retstr,
\ tpreturn(TPFAIL, INVALMTY, retstr, 100, 0); } }
检测服务请求的优先级
listing 3-3表显示:假想的PRINTER服务测试刚刚调用tpgprio( )函数,得到请求的优先级。基于优先级,打印任务被发送给适当的目的打印机。pbuf->data的内容是指向打印机的。同样,pbuf->flags被询问,看是否期望应答。如果期望,则目的打印机名返回给客户。
Listing 3-3 Determining the Priority of the Received Request
#include
char *roundrobin();
PRINTER(pbuf)
TPSVCINFO *pbuf; /* print buffer */ {
char prname[20], ocmd[30]; /* printer name, output command */ long rlen; /* return buffer length */ int prio; /* priority of request */ FILE *lp_pipe; /* pipe file pointer */
prio=tpgprio(); if (prio <= 20)
(void)strcpy(prname,\ jobs to big comp. center laser printer where operator sorts output and puts it in a bin */ else if (prio <= 60)
(void)strcpy(prname,roundrobin()); /* assign printer on a rotating basis to one of many local small laser printers
where output can be picked
up immediately;
roundrobin() cycles
through list of printers */ else
(void)strcpy(prname,\
/* assign job to high-speed laser
printer; reserved for those who
need verbose output on a daily,
frequent basis */
(void)sprintf(ocmd, \-d%s\prname); /* output lp(1) command */ lp_pipe = popen(ocmd, \create pipe to command */
(void)fprintf(lp_pipe, \(void)pclose(lp_pipe); /* close pipe */
if ((pbuf->flags & TPNOREPLY))
tpreturn(TPSUCCESS, 0, NULL, 0, 0);
rlen = strlen(prname) + 1;
pbuf->data = tprealloc(pbuf->data, rlen); /* ensure enough space for name */
(void)strcpy(pbuf->data, prname);
tpreturn(TPSUCCESS, 0, pbuf->data, rlen, 0);
char *
roundrobin()
{
static char *printers[] = {\\
static int p = 0;
if (p > 3) p=0;
return(printers[p++]); }
函数tpreturn( )和tpforward( )
tpreturn( )和tpforwad( )函数表明服务程序已经完成。它们或者返回给调用者一个应答,或者转交一请求给另外的服务作进一步的处理。 发送应答
一个服务程序的基本函数,处理一个请求并且返回应答给客户进程。在执行这个函数过程中,服务也能作为一个请求者,并且用tpcall( )或tpacall( )请求调用另一个服务。当tpreturn( )被调用,控制通常返回到main( )。如果服务已经以异步应答发送请求,则它必须接收所有期望的应答,或者在返回控制给main( )之前使用tpcancel( )将它们变为无效,否则,当它们被BEA TUXEDO系统的main( )接收时,突出应答将自动终止,并且返回一错误给调用者。
tpreturn( )函数除了标记服务程序的结束外,还产生应答信息给请求者。如果客户用tpcall( )调用服务,在成功调用tpreturn( )后,应答信息有效存放在*odata所指的缓冲区中。如果tpacall( )用来发送请求,tpreturn( )成功,则应答信息有效存放在*odata所指的tpgetrply( )缓冲区中。该函数的语法如下:
void
tpreturn(rval, rcode, data, len, flags) /* End service routine */ int rval, rcode; char *data;
long len, flags; 当前flags参数没有被使用。
tpreturn( )的参数:rval
rval参数能被设置为TPSUCCESS、TPFALL或TPEXIT。该标志标明服务在一“应用级别”上是否已经成功的完成。当为TPSUCCESS时,则调用函数成功,并且若有应答信息,则应答信息在调用者缓冲区中。如果服务不成功地结束(即是,应用系统逻辑将rval置为TPFALL),则将一错误报告给正在等待应答的客户进程。客户的tpcall( )或tpgetrply( )函数调用将会失败,tperrno变量将被设置为TPESVCFALL(象征应用定义失败)。在这种类型的失败情况下,如果期望一应答信息,则该应答信息在调用者的缓冲区中是可用的。如果将rval设置为TPEXIT,则TPFALL的功能性被执行,但在应答送回给客户之后服务器仍存在。若rval没有设置,则将缺省值TPFALL分配给该参数。
如果tpreturn( )在处理它的参数时遇到错误,则它将会发送failed信息(若期望应答信息的话)给调用进程。这是由调用者通过tperrno的值来监测的。在failed错误信息情况下,tperrno被设为TPESVCERR。若发生这种类型的错误,将没有应答数据返回,并且调用者的输出缓冲区内容和它的长度保持不变。
如果tpreturn返回的缓冲区中的信息是调用者不知或不允许的类型(即,调用被设置成:将flags设置为TPNOCHANGE),则TPEOTYPE返回给tperrno。不能决定应用成功还是失败,并且调用者的输出缓冲区内容和它的长度保持不变。
同样,当tpreturn( )被调用,并且调用等待应答时发生了超时,则返回给rval的值不相关。tperrno被设置为TPETIME,并且应答数据不被发送,调用者的应答缓冲区的内容和长度保持不变。在BEA TUXEDO中有两种超时:阻塞超时和交易超时。
tpreturn( )的参数:rcode
参数rcode被用来返回给调用者一应用定义的返回码。客户通过查询全局变量tpurcode,就能存取rcode中的值。不管应用成功或失败,该代码都被发送;也就是,它在成功或TPESVCFALL的情况下被返回。在其它错误情况下没有应答信息。
tpreturn( )的参数:data和len
data指向返回给客户进程的应答信息。信息缓冲区必须是先经tpalloc( )进行了分配。参数len表示应答缓冲区中的数据总数。
使描述符无效:tpcancel( )
如果服务调用tpgetrply( )失败(带TPETIME值),并且决定不再等待,则它能够调用tpcancel( )使描述符无效。如果应答曾经到达,则被悄悄放弃。tpcancel( )不能用于交易应答(不带TPNOTRAN标志处理请求);在一个交易内,tpabort( )做同样的工作,以使交易调用描述符无效。(参看表listing3-5)。
Listing 3-5 Invalidate a Reply after Timing Out
int cd1;
. .
.
if ((cd1=tpacall(sname, (char *)audv, sizeof(struct aud), TPNOTRAN)) == -1) { . . . }
if (tpgetrply(cd1, (char **)&audv,&audrl, 0) == -1) { if (tperrno == TPETIME) { tpcancel(cd1); . . . } }
tpreturn(TPSUCCESS, 0,NULL, 0L, 0);
转交请求
tpforward( )函数允许一个服务转交一请求给另一服务作进一步处理。 下图显示一个转交链。请求是由tpcall( )发起,最终应答是由该链中最后的服务调用tpreturn( )来提供的。
Figure 3-1 Forwarding a Request
冲区。在这个特定的情况下,使用同一缓冲区是适当的,因为*audv信息缓冲区,已被设置成可在同一缓冲区既可提供请求信息又可提供应答信息。服务询问b_id字段,但不覆盖它;bal和ermsg字段分别被初始化为0和空串,服务返回期望的值。svc_name和hdr_type变量描述服务名和被请求的平衡类型(会计平衡或出纳平衡)。
Listing 2-14 Using the Same Buffer for Request and Reply Messages
. . .
/* Create buffer and set data pointer */
audv = (struct aud *)tpalloc(\
/* Prepare aud structure */
audv->b_id = q_branchid; audv->balance = 0.0;
(void)strcpy(audv->ermsg, \
/* Do tpcall */
if (tpcall(svc_name,(char *)audv,sizeof(struct aud),
(char **)&audv,(long *)&audrl,0)== -1){
(void)fprintf (stderr, \ svc_name, svc_name, audv->ermsg); retc = -1; }
else
(void)printf (\ audv->b_id, hdr_type, audv->balance); . . .
注:对输入和输出用不同的缓冲区的实例,请参看第三节“编写服务端程序”的表listing3-2。
如果接收信息对已分配的、接收它的缓冲区来说过大,该缓冲区可以自己增长,以达到能接收该信息。BEA TUXEDO保证一接收信息,就能被放入缓冲区(通过缓冲区的自动增长)。然而,对程序员来说,对应答缓冲区的大小的改变进行测试(以便确定它们的实际大小)是必需的。新的大小可以通过访问olen参数来得到。要决定应答缓冲区的大小是否改变,可以将调用tpcall( )前的应答缓冲区,与返回后的*olen值进行比较。如果*olen比原始尺寸大,则缓冲区增长了。反之,缓冲区的大小没有改变。你应该通过调用后的odata的值来参照输出缓冲区,因为输出缓冲区的改变(因某种原因)不同于缓冲区尺寸的增加。这种情形不适用于请求缓冲区,因为放在缓冲区中的请求数据没有增长的可能性。注意,如果你对
请求和应答信息使用同一缓冲区,并且指向应答缓冲区的指针由于缓冲区的增长而改变了,则输入缓冲区指针不再指向有效地址。
表listing 2-15提供了一个常见的例子,是关于应用系统检测调用tpcall( )后缓冲区大小的改变。在这个例子中,规定输入和输出缓冲区在大小上必须保持相等。
Listing 2-15 Testing for Change in Size of the Reply Buffer
char *svc, *idata, *odata;
long ilen, olen, bef_len, aft_len; . . .
if (idata = tpalloc(\ error
if (odata = tpalloc(\ error
place string value into idata buffer
ilen = olen = strlen(idata)+1; . . .
bef_len = olen;
if (tpcall(svc, idata, ilen, &odata, &olen, flags) == -1) error
aft_len = olen;
if (aft_len > bef_len){ /* message buffer has grown */
if (idata = tprealloc(idata, olen) == NULL) error }
tpcall( )的标志参数的值
tpcall( )的最后一个参数是flags。赋给flags参数的值,能够改变某种途径的通讯操作,允许附加一些机动性给应用系统。如果flags被设为0,则通讯按缺省的方式进行处理。
TPNOTRAN 如果客户进程在调用tpcall( )时处在交易模式下,并且flags被设置为TPNOTRAN,
则被调用的服务将不是交易的一部分;也就是,服务执行的操作不是调用者交易的
一部分。这个方面的更多介绍在第五节“BEA TUXEDO系统中的全局交易”中可以找到。
TPNOCHANGE
通过使用这个值,调用程序暗示:它希望返回的信息与原始分配的、作为输出的缓
冲区是同一类型。换句话说,当这个标志被设定时,返回给调用者的缓冲区类型,必须与*odata指向的是相同的。这是以强大的类型检测而著称的。缺省的,允许缓冲区类型与原始的不同(只要调用者能识别这类型)。这样,*odata的缓冲区类型改变为接收缓冲区类型。这是以较差的类型检测而著称的。可以通过调用tptypes( )告知容纳新的缓冲区类型。
TPNOBLOCK
如果一阻塞条件存在,TPNOBLOCK将导致一函数调用。如果服务队列或内部缓冲区
被放满,当试图发送一请求时,调用者也会阻塞。尽管这样,当等待一个应答的到
来时,通讯程序的调用者会典型阻塞。缺省的阻塞延时周期,在应用系统的配置文件中定义。它描述当一阻塞条件存在的话,调用者等待阻塞条件平息,可以等待的时间总量。如果阻塞条件持续到超过这个限制,则函数调用失败并且将tperrno设置为TPETIME。当flags的值被设置为TPNOBLOCK时,如果阻塞条件存在,则调用立即失败,请求信息不被发送。这种情况下,tperrno被设置成TPEBLOCK。注意tpcall( )是一双重函数,即它可以发送请求,又可接收应答。当TPNOBLOCK被设置,它仅仅影响函数的发送部分;如果所有服务队列被填充或者要拷贝信息缓冲区的内部缓冲区满了,则调用不被阻塞并且立即返回。然而,如果它必须等待应答(通常情况是这样),这个标志的设置对“等待时从阻塞调用”是不利的。
TPNOTIME 通过设置flags为TPNOTIME,你告诉系统忽略阻塞时间限制,因为调用者愿意等到阻塞条件平息。然而,如果调用者处在交易模式,这个标志就没有效果;它受制
于交易超时限制。交易超时在第五节“BEA TUXEDO系统中的全局交易”中讨论。
TPSIGRSTRT
另一个flags参数的合法值是TPSIGRSTRT。当有一中断信号时,由它来识别。当
flags被设置为此值时,调用将又一次自动产生。当flags没有被置为此值、并且
有一中断信号时,则函数调用失败,tperrno返回TPGOTSIG。
标志值能被糅合在一起
tpcall( )返回一整数。失败时,该值为-1,tperrno的值设置为能反映发生的错误的值。导致错误的原因已经讨论了。通常,通讯调用会因多种原因失败。许多通讯调用返回的错误能被固定在应用级别上。它们包括应用系统被定义的错误(TPESVCFALL)、处理返回参数时的错误(TPESVCERR)、类型缓冲区错误(TPEITYPE,TPEOTYPE)、超时(TPETIME)和协议错(TPEPROTO),还有其它。
发送异步信息:tpacall( )
这部分讨论:在请求的发送者不等待应答的地方,发送异步信息。这个通讯的第一步是 由tpacall( )来执行的。语法如下:
int
tpacall(svc, data, len, flags) /* Send service request */ char *svc, *data; long len, flags;
tpacall( )函数发送一请求信息给参数svc中的服务名,并且立即得到返回信息。后
面的三个参数是data、len和flags,它们分别与tpcall( )函数中的idata、ilen和flags的定义类似。若调用成功,tpacall( )返回一作为描述符的整数,用来取得发送请求的正确回答。当tpacall( )处在交易模式时,在交易提交时可能没有突出的应答;也就是,在给定的交易中,每个发送的请求期望一个回答,一个相应的回答必须最终被接收到。
tpacall( )的flags参数的值
通讯标志(tpacall( )用作flags参数的值)属于通讯的发送部分。(??)
TPNOREPLY
如果flags参数被设置为 TPNOREPLY,它提示tpacall( ),表明不期望回答。当这个标志被设定,若成功调用,则tpacall( )返回0值作为描述符(因为0不能被tpgetrply( )使用)。 (??)
若出错,tpacall( )返回-1,并且给tperrno赋值,表明错误种类。tpacall( )返回很多与tpcall( )相同的错误代码。不同的是,一个是同步调用,另一个是异步调用。 表listing 2-20描述:通过一系列的异步调用,查询银行总平衡。由于银行应用数据分布在几个数据库点,需要执行一个SQL查询(而不是每一数据库点一个SQL查询)。客户audit通过选择典型的支行标识符(即,每个数据库点一个支行标识符)选择性的做SQL查询,并且为每一数据库点调用ABAL或TBAL。典型的支行标识符在实际的SQL查询中不被使用,但它能引起BEA TUXEDO系统发送请求给适当的数据库点。下面代码的for循环,在每一数据库点调用一次tpacall( )。
Listing 2-20 Sending Asynchronous Requests
audv->balance = 0.0;
(void)strcpy(audv->ermsg, \
for (i=0; i /* Prepare aud structure */ audv->b_id = sitelist[i]; /* routing done on this field */ /* Do tpacall */ if ((cd[i]=tpacall(sname, (char *)audv, sizeof(struct aud), 0)) == -1) { (void)fprintf (stderr, \ pgmname, sname, sitelist[i]); tpfree((char *)audv); return(-1); } } 取得异步应答:tpgetrply( ) tpgetrply( )是tpacall( )的补充函数。它将tpacall( )请求的应答出列。语法如下: int tpgetrply(cd, data, len, flags) /* Receive reply to service request */ int *cd; /* Call Descriptor */ char **data; long *len, flags; tpgetrply( )将tpacall( )返回的调用描述符的地址,作为第一个参数cd。缺省方式下,函数等待应答(cd 参数对应的值)的到来。在等待这个特殊的应答过程中,可能发生超时。超时意味者tpgetrply( )失败,并且tperrno被设置为TPETIME(除非flags参数被置为TPNOTIME)。tpgetrply第二、三参数data和len与tpcall( )函数的odata和olen定义相同。data包含调用tpalloc( )时被分配了的地址指针。 取得和设置优先级 ATMI提供两个函数,用来决定和设置信息请求的优先级。优先级对服务器怎样出列请求有影响。服务器对优先级最高的先出列。这些函数的语法如下: int tpgprio(); /* Get service request priority */ 和 int tpsprio(prio, flags); /* Set service request priority */ int prio; long flags; 在调用tpcall( )或tpacall( )函数后,请求者可以调用tpgprio( )来取得刚刚发送的请求信息的优先级。如果它被调用并且无请求发送,函数失败,返回-1并且将tperrno设置为TPENOENT。若成功,tpgprio( )返回1到100间的数值,100是最高优先级的值。服务的优先级被赋予系统的缺省值50(除非被管理员明确定义为其它某一值)。参看listing2-21表,看看以异步调用发送的信息优先级是怎样决定的。 Listing 2-21 Determining the Priority of the Sent Request #include main () { int cd1, cd2; /* call descriptors */ int pr1, pr2; /* priorities to two calls */ char *buf1, *buf2; /* buffers */ long buf1len, buf2len; /* buffer lengths */ join application if (buf1=tpalloc(\ error if (buf2=tpalloc(\ error populate FML buffers with send request if ((cd1 = tpacall(\ error if ((pr1 = tpgprio()) == -1) error if ((cd2 = tpacall(\ error if ((pr2 = tpgprio()) == -1) error if (pr1 >= pr2) { /* base the order of tpgetrplys on priority of calls */ if (tpgetrply(&cd1, &buf1, &buf1len, 0) == -1) error if (tpgetrply(&cd2, &buf2, &buf2len, 0) == -1) error } else { if (tpgetrply(&cd2, &buf2, &buf2len, 0) == -1) error if (tpgetrply(&cd1, &buf1, &buf1len, 0) == -1) error } . . . } 用tpsprio( )函数,程序员可以不顾服务赋予的请求优先级的级别。使用该函数,仅仅能影响最近由tpcall( )或tpacall( )发送的请求的优先级级别,或者影响前一个服务子程序的请求的优先级级别。这个函数有两个参数,第二个参数指出第一个参数怎样被解释。第一个参数prio是一整数。在缺省情况下,它指出相对于已存在的优先级,请求优先级是否应该增加或减少。若第一个参数被看成是一相对值,第二个参数必须置为0。 初始化一会话连接 这一节是围绕着“客户端程序怎样初始化‘请求/响应’服务请求”这个中心点讨论的。 客户端程序也能通过用tpconnect ( )代替tpcall( )或tpacall( ),来连接一个会话服务。这个方面的详细介绍在第四节“客户端和服务端的会话”。 发送一广播信息 tpbroadcast( )函数用来在一应用系统内发送未被恳求的信息给已注册用户。 编译客户端程序 你可以用多种方法编译你的客户端程序。你能用规则的C编译系统生成目标文件。目标 文件能作为一个单独文件保存或连接到一档案文件中去。如果你高兴,你可以将你的程序以源文件(.c)保存。当你调用buildclient生成一可执行客户时,你在命令行指定输入文件要带-f选项。 buildclient命令 它的选项指定:输出文件、应用系统提供的输入文件、各种各样的库。 buildclient调用UNIX的cc命令。设置环境变量CC和CFLAGS来命名可选的编译命令,并且为编译和连结编辑阶段设标志。 buildclient的-o选项 选项-o用来指定可执行的输出文件。若没有提供输出文件,则输出文件为a.out。 buildclient的-f 和-l选项 选项-f和-l用来指定在连接编辑阶段使用的文件。在-f(first)选项中指定的文件是在BEA TUXEDO系统库之前被调入的,反之,在-l(last)选项中指定的文件是在BEA TUXEDO系统库之后被调入的。输入文件应该列在库文件(使用到的)的前面。若输入文件是.c文件,则它们先被编译。目标文件可以是.o文件或是档案文件(.a)。如果不止一个文件作-f或-l选项的参数,则你可用多次-f和-l选项。 下面的编译命令例子中,环境变量CC设为cc,CFLAGS设为-I $TUXEDO/include。 buildclient -o audit -f audit.o 3.服务端编程 编写“请求/响应”服务 上一节讨论了能用来编写客户端程序的ATMI基本件。在这一节里,一些相同的 函数将在服务子程序中重新提起。你可能记得,“服务”是与BEA TUXEDO系统提供的main( )链接的、产生可执行的服务器程序的C语言子程序。 本节仅仅讨论在“请求/响应”模式下运作的服务。会话的客户端和服务端是第四节“会话的客户端和服务端”讨论的主题。 应用服务模板 因为服务与提供的main( )兼容,所以它们(服务)必须遵守某些约定。这些约定作为服务模板,为编写服务程序提供方便。在这儿对它们进行介绍。 “请求/响应”服务有下列特征: ? ? 一个“请求/响应”服务在某一时间只能接收一个请求,只能发送一个应答。 当正在为一个请求服务时,它只能在那个请求上处理;并且只有在它发送它的应答给请求者之后,或者在它已经将该请求转交给另一服务作另外的处理时,它才能接收另一请求。 ? ? 服务程序必须通过调用tpreturn( )或tpforward( )函数进行终止。 当你通过tpacall ( )与另一服务通讯时,初始服务必须等待所有突出的应答,或者必须在调用tpreturn( )或tpforward( )之前,通过tpcancel( )使它们无效。 ? 服务程序是通过参数svcinfo而被调用的,该参数是一指向服务信息结构的指针。 下面部分更仔细地介绍这些概念。 TPSVCINFO结构 典型的服务程序被定义成一个函数,接收一结构指针参数。这个服务信息结构在 atmi.h头文件中被定义成结构为TPSVCINFO。它包括下面的成员: char name[32]; /* service name being invoked */ long flags; /* describes service attributes */ char *data; /* request data */ long len; /* request data length */ int cd; /* connection descriptor if (flags & TPCONV) true */ int appkey; /* application authentication client key */ CLIENTID cltid; /* client identifier for originating client */ 结构成员 ? ? ? ? ? ? ? TPSVCINFO的name成员 显示服务程序调用的名字 告知它自己或调用者的服务属性 指向请求数据 显示请求数据的长度 如果是会话连接,就给定连接描述符 为鉴定提供客户关键字 为客户发起的调用运载标识符 该结构的name成员,显示请求进程用来调用服务的名字。 TPSVCINFO的flags成员 该结构的flags成员,使服务知道:它是处于交易模式还是调用者正在期望一个应答。若flags的值是TPTRAN,它表示服务处于交易模式。当用tpcall( )或tpacall( )调用服务,并且将所带flags参数设置为TPNOTRAN时,表示服务不能参加当前交易;但它仍可能处在交易模式。因此甚至当调用者设置TPNOTRAN通讯标志时,仍有可能将svcinfo->flags设为TPTRAN。如果用tpacall( )调用服务,并且通讯标志被设置为TPNOREPLY,则该结构的flags成员被设为TPNOREPLY。将flags成员设成这两种值是可能的。如果一被调用的服务,是调用进程相同交易的一部分,则它必须返回给调用者一应答。 TPSVCINFO的data和len成员 成员data指向一缓冲区。该缓冲区是在服务器的main( )中,早先用tpalloc( )进行了分配的。这个缓冲区用来接收请求信息。len容纳缓冲区中请求数据(data指定的)的长度。 TPSVCINFO的appkey成员 这个成员的使用由应用系统决定。如果“特殊应用”鉴定是你的目的之一,则“特殊应用”鉴定服务器(在这个时候称为“连接应用的客户”)应该返回一客户鉴定关键字(显示成功/失败)。这个关键字是由“为客户利益的系统”所掌握,并且传递给appkey字段中的并发请求。到关键字传递给服务时,客户已经传递了鉴定;但appkey字段能被用在服务内,用来鉴别用户调用服务的方式或者其它与用户关联的参数。若没被使用,则被系统设置为-1。 TPSVCINFO的cltid成员 成员cltid是CLIENTID类型的结构。它被系统用来运载客户鉴定。在这个结构中,你不能进行改动。 访问带有请求的数据 当访问由data指定的请求数据时,服务必须被编码成期望数据在缓冲区中,且该缓冲区是配置文件中为服务定义的类型。 Listing3-1表举例说明一典型的服务定义;这个是来自ABAL(帐务平衡)服务程序。ABAL是BAL服务器的一部分。 Listing 3-1 Typical Service Definition #include #include \#include \ EXEC SQL begin declare section; static long branch_id; /* branch id */ static float bal; /* balance */ EXEC SQL end declare section; /* * Service to find sum of the account balances at a SITE */ void #ifdef __STDC__ ABAL(TPSVCINFO *transb) #else ABAL(transb) TPSVCINFO *transb; #endif { struct aud *transv; /* view of decoded message */ /* Set pointer to TPSVCINFO data buffer */ transv = (struct aud *)transb->data; set the consistency level of the transaction /* Get branch id from message, do query */ EXEC SQL declare acur cursor for select SUM(BALANCE) from ACCOUNT; EXEC SQL open acur; /* open */ EXEC SQL fetch acur into :bal; /* fetch */ if (SQLCODE != SQL_OK) { /* nothing found */ (void)strcpy (transv->ermsg,\failed in sql aggregation\ EXEC SQL close acur; tpreturn(TPFAIL, 0, transb->data, sizeof(struct aud), 0); } EXEC SQL close acur; transv->balance = bal; tpreturn (TPSUCCESS, 0, transb->data, sizeof(struct aud), 0); } 检测类型缓冲区