570 likes | 1.15k Views
开发 (1) - 使用 EasyCICS. 刘睿. 概要. EasyCICS 基本概念和入门程序 EasyCICS 客户机开发 EasyCICS 服务器开发 EasyCICS 高级特性和注意事项. EasyCICS 基本概念和入门程序. 什么是 EasyCICS. EasyCICS 是针对 IBM 的 CICS 系统开发的一套简易的程序开发界面。它是主要目的是缓解数据交换的难度,便于各种前端开发工具访问 CICS 系统。
E N D
概要 • EasyCICS基本概念和入门程序 • EasyCICS客户机开发 • EasyCICS服务器开发 • EasyCICS高级特性和注意事项
什么是EasyCICS • EasyCICS是针对IBM的CICS系统开发的一套简易的程序开发界面。它是主要目的是缓解数据交换的难度,便于各种前端开发工具访问CICS系统。 • EasyCICS分为服务器和客户机两部分。服务器部分提供增强的C语言函数库和头文件,支持C/C++开发。客户机部分提供C/C++函数库和头文件,JAVA支持,OLE组件,供C/C++、JAVA、Visual Basic、Power Builder、Delphi、C++Builder等开发者访问CICS时使用。 • EasyCICS为所有开发工具提供了统一的开发界面(API)。
EasyCICS版本发展及组成 • EasyCICS v1.0 发布于1999 年7 月。截止当前,推荐使用的版本为2.29。 • EasyCICS包括以下内容: • EasyCICS server SDK (ANSI C & C++ for AIX, Solaris, HP-UX, Windows) v2.27 • EasyCICS Client SDK for AIX, Solaris, HP-UX, Windows, Linux, Dos : • EasyCICS ANSI C Client v2.28f • EasyCICS C++ Client v2. 28 • EasyCICS JAVA Client v2.29 • EasyCICS COM/OLE Client v2.28 • EasyCICS DLL Client v2. 28
EasyCICS的参考资料 • EasyCICS编程入门.doc • EasyCICS手册(2006-7-9).doc • 《三层C-S结构编程指南》 • http://www.lrsolution.com/easycics.html
关键字 • 关键字(Key)是程序员自定义的可以在EasyCICS服务器和客户机之间传递信息的字符串。相关的函数有GetValue和SetValue,为解决C语言中的越界问题,在C语言中引入了GetValue1函数。 • 注意:这种方式只用于传递0维数据,总长度不能超过32K字节。 • 示例: • SetValue( "NO", "2020088" ); /* C */ • oEc.SetValue "NO", "2020088" 'VB • GetValue( "TIME", s ); /* C */ • GetValue1( "TIME", s, sizeof(s) ); /* C */ • s= oEc.GetValue("TIME")
结果集 • 结果集(Resultset)是一个二维表格,可以在EasyCICS服务器和客户机之间二维的字符信息。创建结果集的相关的函数有:RsCreate,RsAddRow,RsSetCol,RsSaveRow;读取结果集的相关函数有:RsOpen,RsFetchRow,RsGetCol和RsGetColNum,RsGetRowNum。为解决C语言中的越界问题,在C语言中引入了RsGetCol1函数。 • 示例: • RsCreate(2); /* C */ • RsAddRow(); /* C */ • RsSetCol( 1, Usr_name ); /* C */ • oEc.RsOpen /* VB */ • rc = oEc.RsGetRowNum /* VB */ • cc = oEc.RsGetColNum /* VB */ • s= oEc.RsGetCol(1) /* VB */ • oEc.RsClose /* VB */
ECI调用方式 • EasyCICS提供4种ECI调用方式,分别介绍如下: • 服务器/客户机提交 同步/异步 • CallProgramAndCommit 服务器提交,同步 • CallProgram 客户机提交,同步 • CallProgramDSyncAndCommit 服务器提交,异步 • CallProgramDSync 客户机提交,异步 • 客户机调用CallProgram和CallProgramDSync时,最终一定要调用Commit或RollBack,事务是否提交以此为准。但是在此期间,program一直占据application server。如果发生网络故障,须等到超时,才能完成事务回滚,在此期间,可能因数据库记录锁定导致相关存取挂起。 • 异步调用比较复杂,尤其是多个异步调用同时进行时,有4种得到返回值的方法。
关键字与结果集的调用顺序 • 根据以上关于公共数据区的讨论,可以导致EasyCICS的以下规则:关键字函数(GetValue / SetValue)应首先使用,然后是结果集函数(Rsxxx)。 • 无论是服务器还是客户机,应采用如下的调用顺序:先使用GetValue读取对方传来的关键字数据;再使用RsOpen等读取对方传来的结果集;然后使用BeginWrite清除公共数据区;再使用SetValue设置给对方的关键字数据;再使用RsCreate等设置给对方的结果集。 • 注:如果在设置结果集之后再用SetValue设置关键字数据,而且该结果集大于32K,就必须在读完结果集之后再用GetValue读取该关键字(因为第一帧没有该关键字的数据)。另外,如果在设置结果集之后再用SetValue设置关键字数据,应保证这些追加设置的关键字数据总长少于1K字节。 • EasyCICS Server提供的SetValueA函数保证无论何时调用,都保证在第一帧传递该关键字数据。由SetValueA函数设置的关键字数据总长应少于100字节。
EasyCICS示例程序: "gettime" • 服务器向客户机提供其时间
"gettime"服务程序 #include <time.h> #include "easycics.h" void main(){ struct tm *newtime; time_t aclock; if( InitEasyCics() ) ExitEasyCics(); BeginWrite(); time( &aclock ); /* Get time in seconds */ newtime= localtime( &aclock ); /* Convert time to struct tm form */ SetValue( "TIME", asctime(newtime) ); ExitEasyCics(); }
"gettime"客户程序(C) #include "ec.h" void main(){ char s[200]; int r; r= ConnectServer( "CICSNT01", "TEST", "TEST" ); printf( "ConnectServer: return code= %d\n", r ); r= CallProgramAndCommit("GETTIME"); printf( "CallProgramAndCommit: return code= %d\n", r ); GetValue( "TIME", s ); printf( "Server Time= %s\n", s ); }
"gettime"客户程序(VBS) set oEc = Wscript.CreateObject("EasyCics.App") r = oEc.ConnectServer("CICSNT01", "TEST", "TEST") if r<>0 then MsgBox "can't connect" Wscript.Quit r end if oEc.CallProgramAndCommit "GETTIME" r= oEc.GetErrCode if r<>0 then MsgBox "ECI call failed" Wscript.Quit r end if MsgBox oEc.GetValue("TIME")
EasyCICS示例程序:"telecom" • 客户机提供设备号码 • 服务器根据该号码查询,返回结果集
"telecom"服务程序(1/5) #include "easycics.h" #if defined ( ORA ) #define SQLNOTFOUND 1403 #else #define SQLNOTFOUND 100 #endif EXEC SQL INCLUDE sqlca; EXEC SQL BEGIN DECLARE SECTION; char Usr_name[61]; char Dev_no[9]; long Call_flg; char Called_arno[11]; char Called_no[15]; char Call_dat[21]; double Call_dur; double Call_rate; double Call_fee; double Add_fee; char as_dev_no[9]; EXEC SQL END DECLARE SECTION;
"telecom"服务程序(2/5) void ErrRtn(char *err_msg){ char s[200]; sprintf( s, "ERR->%s, SQLCODE=%d", err_msg, sqlca.sqlcode ); PrintStatus(s); /*PrintStatus(sqlca.sqlerrmc);*/ BeginWrite(); SetValue( "Err", s ); CicsRollBack(); ExitEasyCics(); } void main(){ char statusbuf[1024], s[30]; if( InitEasyCics() ) ExitEasyCics(); /*Read:*/ GetValue1( "NO", as_dev_no, sizeof(as_dev_no) );
"telecom"服务程序(3/5) /*Write:*/ BeginWrite(); SetValue( "VER", "Telecom 1.0"); RsCreate(10); EXEC SQL DECLARE c1 CURSOR FOR SELECT bas_infot.Usr_name, auto10a_list.Dev_no, auto10a_list.Call_flg, auto10a_list.Called_arno, auto10a_list.Called_no, auto10a_list.Call_dat, auto10a_list.Call_dur, auto10a_list.Call_rate, auto10a_list.Call_fee, auto10a_list.Add_fee FROM auto10a_list, bas_infot WHERE ( auto10a_list.Dev_no = bas_infot.Dev_no ) AND auto10a_list.Dev_no = :as_dev_no; if( sqlca.sqlcode <0 ) ErrRtn( "declear cursor" ); EXEC SQL OPEN c1; if( sqlca.sqlcode <0 ) ErrRtn( "open cursor" );
"telecom"服务程序(4/5) do{ EXEC SQL FETCH c1 INTO :Usr_name, :Dev_no, :Call_flg, :Called_arno, :Called_no, :Call_dat, :Call_dur, :Call_rate, :Call_fee, :Add_fee; if( (sqlca.sqlcode == SQLNOTFOUND) || (sqlca.sqlcode <0) ) break; else RsAddRow(); sprintf( statusbuf, "%s,%s,%d,%s,%s,%s,%7.0f,%8.3f,%7.2f,%6.2f“, Usr_name, Dev_no, Call_flg, Called_arno, Called_no, Call_dat, Call_dur, Call_rate, Call_fee, Add_fee ); PrintStatus(statusbuf); RsSetCol( 1, Usr_name ); RsSetCol( 2, Dev_no ); sprintf( s, "%lu", Call_flg ); RsSetCol( 3, s ); RsSetCol( 4, Called_arno ); RsSetCol( 5, Called_no ); RsSetCol( 6, Call_dat ); sprintf( s, "%6.2f", Call_dur ); RsSetCol( 7, s ); sprintf( s, "%6.2f", Call_rate ); RsSetCol( 8, s ); sprintf( s, "%6.2f", Call_fee ); RsSetCol( 9, s ); sprintf( s, "%6.2f", Add_fee ); RsSetCol( 10, s ); RsSaveRow(); }while(1);
"telecom"服务程序(5/5) EXEC SQL CLOSE c1; /*#ifdef( SYBASE ) EXEC SQL DEALLOCATE CURSOR c1; #endif*/ /*#ifdef( INFORMIX ) EXEC SQL FREE c1; #endif*/ if( sqlca.sqlcode <0 ) ErrRtn( "close cursor" ); ExitEasyCics(); }
“telecom”客户程序(C) #include "ec.h" void main(){ int i, j, rc, cc, r; char s[100], c, cr; r= ConnectServer( "CICSNT01", "TEST", "TEST" ); printf( "Connect Code: %d\n", r ); BeginWrite(); printf("Enter Query Number:"); scanf( "%s", s ); scanf( "%c", &cr ); SetValue( "NO", s ); SetEciTimeOut(30); r= CallProgramAndCommit("TELECOM"); if(r){ puts("Call Program Error !"); return; } RsOpen(); rc = RsGetRowNum(); cc = RsGetColNum(); for( i=1; i<=rc; i++ ){ RsFetchRow(); for(j=1; j<=cc; j++){ RsGetCol1( j, s, sizeof(s) ); printf(s); printf( "," ); } puts(""); } RsClose(); }
“telecom”客户程序(VBS) set oEc = Wscript.CreateObject("EasyCics.App") r = oEc.ConnectServer("CICSNT01", "TEST", "TEST") if r<>0 then MsgBox "can't connect" Wscript.Quit r end if s = InputBox("请输入查询号码:") if s="" then Wscript.Quit 0 oEc.SetValue "NO", s oEc.CallProgramAndCommit "TELECOM" oEc.RsOpen rc = oEc.RsGetRowNum cc = oEc.RsGetColNum s = "" For i = 1 To rc oEc.RsFetchRow For j = 1 To cc s = s + oEc.RsGetCol(j) + "," Next s = s + chr(13) + chr(10) Next MsgBox s
注意事项 • C/C++的编译和连接方式参见《EasyCICS手册(2006-7-9)》第4章。注意混用CUC/CTG与RPC Client的头文件和库后可以造成各种错误。 • 推荐尽量选择CUC/CTG,因为CUC/CTG 更稳定,而且在TXSeries v6以后, RPC Client会被淘汰。
ConnectServer EasyCICS客户程序首先要连接CICS服务器(使用ConnectServer函数),然后可以根据返回值判断连接是否成功。以下是示例: r = oEc.ConnectServer("CICSNT01", "TEST", "TEST") if r<0 then MsgBox "can't connect" end if
CallProgramAndCommit • C语言示例: r= CallProgramAndCommit("TELECOM"); if(r){ puts("Call Program Error !"); return; } • COM/OLE示例: oEc.CallProgramAndCommit "GETTIME" r= oEc.GetErrCode if r<>0 then MsgBox "ECI call failed" end if • JAVA示例: try{ ... oEc.CallProgramAndCommit("TELECOM"); ... } catch(ServerErrorException se){ System.out.println(se.getMessage()); System.out.println(se.getErrorCode()); } catch(ResultSetErrorException re){ System.out.println(re.getMessage()); System.out.println(re.getErrorCode()); }
EasyCICS客户机的调用规范 客户机调用CallProgram和CallProgramDSync时,最终一定要调用Commit或RollBack,事务是否提交以此为准。但是在此期间,program一直占据application server。如果发生网络故障,须等到超时,才能完成事务回滚,在此期间,可能因数据库记录锁定导致相关存取挂起。
从异步调用得到返回值 • 常用函数/方法 • GetReply • GetReplyWait • GetSpecReply,SetMsgQlf • GetSpecReplyWait,SetMsgQlf • 示例程序参见《EasyCICS手册(2006-7-9)》5.2.4。
关于半个汉字或生僻汉字而引起的Windows解析错误 • 这种错误的原因是:Windows的在COM/OLE实现中,需要根据字符集进行字符转换。如果字符串中出现半个汉字或生僻汉字,会引发转换错误。 • 在EasyCICS v2.26的COM/OLE实现中增加了GetValueByte和RsGetColByte函数,直接返回字节数组,而不是返回字符串。这样可以避免因插入半个汉字或生僻汉字而引起的Windows解析错误。自己定义的结构(中间插入半个汉字)也没有任何问题。 • 另一种办法是使用EasyCICS v2.26a对Windows动态连接库的支持。其中DllEc.dll用于一般使用,DllEcx.dll增加了一个句柄,可用于多线程的程序。使用DllEc.dll和DllEcx.dll比使用COM/OLE组件速度快一些,用于多线程程序也比较容易。在demo.zip中增加了一个C语言的例子。用在Delphi和PowerBuilder中也很容易,大家可以根据DllEc.h和DllEcx.h自己进行封装。
EasyCICS Windows动态连接库(DLL) Client • 使用EasyCICS Windows动态连接库(DLL)客户机开发程序与使用C/C++开发完全一样。EasyCICS提供了两种动态连接库。其中DllEc.dll用于一般使用,DllEc.dll增加了一个句柄,可用于多线程的程序。使用DllEc.dll和DllEcx.dll比使用COM/OLE组件速度快一些,用于多线程程序也比较容易。在demo.zip中增加了一个C语言的例子。用在Delphi和PowerBuilder中也很容易,大家可以根据DllEc.h和DllEcx.h自己进行封装。 • 如果在Windows上使用C/C++的话,也可以使用EasyCICS DLL Client。注意使用相应的头文件和输入库。单线程访问ECI的程序可以使用DllEc.h和 DllEc.lib,多线程访问ECI的程序可以使用DllEcx.h和 DllEcx.lib。另外EasyCICS v2.29a还提供了在Borland C++环境中使用的输入库DllEc.lib和DllEcx.lib。
EasyCICS Java Client - JavaGateway类型 Local Gateway 从本地CICS Client直接获取ECI/EPI连接。这种情形相当于使用JNI访问CICS Client。每一个客户机必须安装完整的CICS Client的运行环境,并在CLASSPATH中包括ctgclient.jar文件和ctgserver.jar文件。 优点:不用启动Transaction Gateway进程 缺点:Client程序必须与CICS Client装在同一台机器上。 示例: App oEc = new easycics.App(); oEc.strJGateName = "local:"; oEc.ConnectServer("CICSNT01", "TEST", "TEST"); Remote Gateway 在装有CICS Client的机器上启动Transaction Gateway,Client程序连接此Gateway机器即可。此Gateway进程运行在安装了CICS TG的机器上,其它客户机通过与此安装了Gateway进程的机器通信即可。而这些客户机只需要ctgclient.jar文件并在CLASSPATH中指定即可。在程序中必须提供安装了Gateway进程的机器使用的IP地址和端口号(缺省是2006)。 优点:Client程序无须安装CICS Client,只须ctgclient.jar即可。 缺点:必须启动Transaction Gateway进程。 示例: App oEc = new easycics.App(); oEc.strJGateName = “172.20.60.240”; oEc.iJGatePort = 2006;
复用连接 • ConnectAs • 示例程序参见《EasyCICS手册(2006-7-9)》6.3.2。
EasyCICS客户程序使用多线程的注意事项 • 对于使用COM/OLE接口的程序,必须在主线程生成EasyCICS的App对象。在不同的线程使用不同的App对象可以避免互相干扰。 • 对于使用C/C++的程序,如果要在不同的线程调用CICS服务程序,应该使用C++来处理。也就是说,在各自的线程生成各自的Ecx对象,以避免重入错误。另一种好办法使用动态连接库DllEcx.dll,在每个线程里使用dllCreateHandleX函数创建CICS连接句柄。 • 多线程的程序要尽量避免使用全局和静态变量,以及线程不安全的函数(比如strtok等),除非程序员明白其后果。 • 另外应该在所有线程的ECI调用结束之后再退出程序,否则可能引起客户程序异常。
EasyCICS客户程序使用多进程的注意事项 这主要是指C语言的程序,注意以下几点: • 编译方式要参照示例程序。有时因为没有加上“-lpthread”而造成程序运行时挂住。 • 在父进程做了ECI调用后,再使用fork生成子进程,则子进程不能再做ECI调用。否则会造成应用运行时挂住。
ECI调用的控制 • 设置ECI调用的超时时间 - SetEciTimeOut • 设置transaction分类 – SetTransId • 注:设置交易分类: • 制作类似CPMI的交易定义,修改TClass为1~10。例如“LPMI”:cicsadd -r CICSNT01 -c td -m CPMI LPMI TClass=1 • 酌情修改RD:ClassMaxTasks (缺省:“1,1,1,1,1,1,1,1,1,1”),RD:ClassMaxTaskLim (缺省:“0,0,0,0,0,0,0,0,0,0”)。
针对High Availability,Load Balance和分布式调用的处理 • 客户机: • CICS Client Work Load Manager • CICS Universal Client User exit • ConnectServer2 • ServerList函数组(ServerListInit, ServerListAdd, ConnectServerListX) 示例程序参见《EasyCICS手册(2006-7-9)》7.5.1。 • 服务器: • EasyCICS全局SYSID配置规则 • 任何一个域不许使用缺省的RD/LocalSysId属性(即不能用ISC0)。 • 任何一个域的RD/LocalSysId属性必须唯一。 • 通向另一个域的CD项名称与该域的RD/LocalSysId属性相同。 • 要求设置TD属性中CPMI和CECI的RSLCheck属性为none。 • CICS Server Work Load Manager
使用SSL的CTG Java程序例 package self; import easycics.*; import com.ibm.ctg.client.*; import com.ibm.ctg.server.*; public class GetTime{ static public void main( String astrArg[] ){ try{ App oEc = new easycics.App(); oEc.strJGateName = "ssl://127.0.0.1:8050"; SslJavaGateway.setKeyRing("D:/client.jks", "12345678"); oEc.ConnectServer("CICSNT01", "TEST", "TEST"); oEc.CallProgramAndCommit("GETTIME"); System.out.println(oEc.GetValue("TIME")); } catch(ServerErrorException se){ System.out.println(se.getMessage()); System.out.println(se.getErrorCode()); } } }
如何使用C#开发EasyCICS using System; namespace MyTest{ class MainApp { public static int Main() { int r; EcOleImp.AppClass oEc= new EcOleImp.AppClass(); oEc.About(); r = oEc.ConnectServer("CICSNT01", "TEST", "TEST"); if(r!=0){ Console.WriteLine("Connect Failed!"); return -1; } oEc.CallProgramAndCommit("GETTIME"); r= oEc.GetErrCode(); if(r!=0){ Console.WriteLine("Invocation Failed!"); return -2; } Console.WriteLine("Current Server Time = " + oEc.GetValue("TIME")); return 0; } } }
EasyCICS服务程序的最简框架 #include "easycics.h" void main(){ if( InitEasyCics() ) ExitEasyCics(); /* 读取客户机提供的信息 */ /* 调用BeginWrite(); 设置返还客户机的信息 */ ExitEasyCics(); }
如果是两个不同的CICS域的CICS服务程序相互调用,比如PrgA调用PrgB: 如果在两阶段提交的情况下,也就是说采用两阶段提交的XA Switch Load File;采用支持两阶段提交的连接方式,比如PPC TCP和PPC Gateway;且采用无SYNCONRETURN的调用,比如CallProgramExt,则完全由PrgA决定提交和回滚,PrgB的提交和回滚操作将无效。 如果在一阶段提交的情况下,也就是说采用一阶段提交的XA Switch Load File;采用只支持一阶段提交的连接方式,比如CICS TCP和Local SNA;或采用有SYNCONRETURN的调用,比如CallProgram,PrgA和PrgB自行决定提交和回滚。 如果是在同一个CICS域的CICS服务程序相互调用,比如PrgA调用PrgB,且没有使用SysID,则当前的同步操作将完成从上一个同步操作到目前的所有支持事务的语句。 服务程序调用服务程序要注意事项如下: 如果有必要,请使用SetCurrentCA来切换通信区。 任何结果集的操作不能交叉进行,必要时请使用数组变量。 服务程序调用服务程序,客户方的结果集长度不得超过32K,服务器方可以传递超过32K的数据。且不支持上载和下载数据块。必要时可以利用TSQ等传递数据。 TXseries服务程序互相调用时的事务边界情况
主控程序 #include "easycics.h" void main(){ char funcs[20]; int func; if( InitEasyCics() ) ExitEasyCics(); GetValue1( “Func”, funcs, sizeof(funcs) ); func= atoi(funcs); switch(func){ case 1: CallProgram("PROG1"); break; case 2 CallProgram("PROG2"); break; default: PrintStatus( “Error Func” ); } ExitEasyCics(); }
切换公共数据区 #include "easycics.h" char Ca1[BUF_SIZE]=""; void main(){ int r; char s[100]; if( InitEasyCics() ) ExitEasyCics(); SetCurrentCA(Ca1); BeginWrite(); RsCreate(1); RsAddRow(); RsSetCol( 1, "r1" ); RsSaveRow(); RsAddRow(); RsSetCol( 1, "r2" ); RsSaveRow(); r= CallProgram( "EC05" ); if(r) CicsAbend( "CALL" ); GetValue( "TIME5", s ); SetCurrentCA(0); BeginWrite(); SetValue( "TIME5", s ); ExitEasyCics(); }
EasyCICS服务程序调用CICS API 如果需要返回值RESP,请保证在此C源文件实现调用过: EXEC CICS ADDRESS EIB (dfheiptr);
EasyCICS服务程序高级调用 • 延时函数 - CicsSleep • 加锁 • CicsLockId • CicsUnLockId • CicsLockIdTest • 分配内存空间 • CicsMallocLocal • CicsMallocShared • CicsFree • 存取CWA • SaveToCwa • LoadFromCwa • 获取唯一ID • GetUniqueNum, 32位整数 • GetUniqueId, 8字节字符串(由0-9,A-F组成) • GetUniqueId6, 6字节字符串(由ASC64- ASC127的字符组成)
由多个二维表格组成的结果集 • 由多个二维子表格组成的结果集称复合结果集,创建复合结果集的方法是在创建结果集后分别创建每一个二维子表格。在RsCreate函数后使用RsNewTable函数可以创建一个子表格。 • 读取复合结果集的方法是首先获取各结果集的行数和列数,再分别读取。以下示例显示了连续读取一个由两个子表格组成的复合结果集的方法。 • 示例程序参见《EasyCICS手册(2006-7-9)》5.1.5。
下载大数据块 • 注意服务器提供了SaveBlock函数,而客户机提供了GetBlockSize和LoadBlock函数。程序调用的顺序应该是:关键字,结果集,大数据块。 • 示例程序参见《EasyCICS手册(2006-7-9)》7.2.1。
上载数据块 • 注意上载数据块的同时不能再上载结果集 • 示例程序参见《EasyCICS手册(2006-7-9)》7.2.2。