xiaobo's profile不率意斋PhotosBlogListsMore Tools Help

Blog


    December 19

    用.net开发tuxedo客户端程序

        .net 开发 tuxedo, C# 调用 tuxedo服务

        1 安装
        1.1 安装版本的选择
        从bea网站可以下载到所有版本的tuxedo服务器与客户端的安装包。我下载了V10.0 专for xp版,装在Windows Vista Professional上。
        从9.1以后的客户端版本,就开始支持.net的托管代码的访问。在安装完了v10.0之后,察看了一下%TUXDIR\%bin\libscdnet.dll文件,他的版本其实还是9.1,可能在V10.0上这个托管库并没有升级。
        在BEA的网站上下载完安装包之后,别忘记了在下载页面里下载许可证文件。
        1.2 安装后的一些调整
        1.2.1 TUXDIR环境变量的修改
        我安装完成之后,客户端程序被安装在了C:\Program Files\BEA Systems\TUXEDO\tuxedo10.0_VS2005路径下,但是TUXDIR变量的设置值不知道什么原因没有指到正确的路径,而是指到了C:\Program Files\BEA Systems\TUXEDO,导致运行程序时出错,经检查C:\Program Files\BEA Systems\TUXEDO下的ULOG日志文件,报告找不到locale文件夹。我试着修改了一下TUXDIR环境变量,但没有作用。
        于是把C:\Program Files\BEA Systems\TUXEDO\tuxedo10.0_VS2005\下的所有文件复制了一份到上一层的C:\Program Files\BEA Systems\TUXEDO,这个问题就解决了。
        1.2.2 PATH环境变量的修改
        在PATH里增加对C:\Program Files\BEA Systems\TUXEDO\tuxedo10.0_VS2005\bin目录的指向。
        1.2.3 许可证文件
        把下载到的许可证文件改名为lic.txt,复制到udataobj目录下。由于我们复制了安装目录,就造成了有两个这个文件夹的情况。都复制进去好了。
        2 用C#写tuxedo客户端程序的基础知识
        2.1 托管类的引用
        %TUXDIR%/bin/libwscdnet.dll是.net2.0的托管代码类库,可以通过对这个库的引用来对tuxedo函数进行调用。当建立了一个C#客户端项目后,必须新建一个引用,选择%TUXDIR%/bin/libwscdnet.dll。其命名空间是Bea.Tuxedo;
        2.2 服务器地址等环境变量的设置
        根据网上的说法,有三种方法设置服务器地址:
        一. 用环境变量来设置:
        在系统的环境变量中设置WSNADDR=//<ip address>:<port>
        这样做的好处是不必在程序里配置。坏处是只支持一个服务器的连接。
       
        二. 用tuxreadenv函数
        用tuxreadenv函数来从一个配置文件中读取指定的节,作为当前环境变量的设置。
        如:tuxenv.ini.内容格式如下:
        [TUXCOMM]
        TUXDIR=c:\tuxedo
        PATH=%PATH%;c:\tuxedo\bin
        WSADDR=//192.168.0.1:6000
        在程序中使用: tuxreadenv("tuxenv.ini","TUXCOMM");语句来调用。
        在C#中,tuxreadenv函数被warp到Utils.tuxreadenv()了。
       
        三. 用tuxputenv函数inline地指定环境变量
        tuxputenv函数可以在程序中直接指定环境变量。如:
        tuxputenv("WSNADDR=//10.1.128.227:9401");
        如果有多个环境变量要设置,可以多次调用这个函数来分别执行设置。
        在C#中,tuxputenv函数被warp到Utils. tuxputenv ()了。
       
        2.3 高版本客户端调用低于7.1版的服务器的问题
        由于服务器是V6.5,而我装的客户端是10.0,因此存在一个协议兼容性的问题,在运行时报错:protocol error. 经查看ULOG文件,发现在调用7.1以下的服务器时,要设置一个环境变量WSINTOPPRE71的值为“yes”。
        增加对这个环境变量设置的方法,见上节三种方法中的任何一种。
        2.4 调用的一般形式
        客户端调用服务的一般过程为:
        设定环境变量?初始化应用上下文?调用服务?得到结果?关闭应用上下文
       
        下面是一个最简单的C#客户端:
        //设定环境变量
        Utils.tuxputenv("WSNADDR=//10.1.128.227:9401");
        Utils.tuxputenv("WSINTOPPRE71=yes");
       
        //初始化应用上下文
        AppContext ac = AppContext.tpinit(null);
       
        //同步调用服务。
        // 同步调用时,服务器不返回结果或是出错之前,
        // tpcall方法不会返回,程序将等在这里。
        TypedString sndstr = new TypedString(1000);
        sndstr.PutString(0, “hello world!”);
        TypedString rcvstr = new TypedString(1000);
        ac.tpcall("TOUPPER", sndstr, ref rcvstr, 0);
       
        // 得到结果
        string rcvstr_str = rcvstr.GetString(0, 1000);
        //关闭应用上下文
        ac.tpterm();
        2.5 异步调用服务
        TUXEDO支持异步调用模式。在异步调用方式下,用 tpacall方式调用服务。当异步调用一个服务后,客户端程序不等服务器完成工作就立即继续执行其他工作,只保留一个句柄。等到客户端程序有空的时,再回来用tpgetrply方汉等待已经调过的服务。如下面的程序
        //异步调用服务。得到异步调用描述符 acd.
        AsyncCallDescriptor acd = ac.tpacall("TOUPPER", sndstr, 0);
        // …. 做些其他的事情。
        // 继续刚刚的服务调用,等待结果。这个方法是一个同步函数。
        ac.tpgetrply(ref acd, ref rcvstr, 0);
        string rcvstr_str = rcvstr.GetString();
        2.6 多线程调用
        多线程调用时,需要在应用程序上下文初始化时,加入多线程标志。如下:
        TypedTPINIT tpinfo = new TypedTPINIT();
        tpinfo.flags = TypedTPINIT.TPMULTICONTEXTS;
        AppContext ac = AppContext.tpinit(tpinfo);
        2.7 结构数据传递问题
        Tuxedo windows客户端的原始API是面向C语言的,因此在很多的服务器程序的编写时,会采用struct结构来会传递数据的方案。对于C结构体数据,在tuxedo中对应的消息类型应该是CArray, 在用C#制作客户端时,可以采用TypedCArray这个类型来传递数据,其中,需要特别注意的问题是.net中interop操作时的一些技术细节。
        下面是一个具体的例子:
            [StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)]
            public struct MYMSGBODY
            {
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
                public string usrname; // char usrname[20]
       
                [MarshalAs(UnmanagedType.U4, SizeConst=4)]
                public uint lLogNo;  // unsigned long int lLogNo;
       
                [MarshalAs(UnmanagedType.I4)]
                public int iRecNum; //  int iRecNum;
       
            }
        在MSDN上有详细的结构体interop类型对应表可以查阅。这里要解释其中几个重要的地方:
        ? StructLayout(LayoutKind.Sequential, Pack = 4, CharSet = CharSet.Ansi)
        ? Pack=4表示以最大4字节边界对齐成员。
        ? CharSet = CharSet.Ansi表示字串是ansi字串。之后再详述。
        ? MarshalAs(UnmanagedType.U4, SizeConst=4)
        此属性标记为此string成员是以值传递的,也就是是一个数组,而不是指针。并指定了长度。这个长度是指C串里包括了结尾0的总长度。
        ? Long类型:这个类型在C与.net里的有重要不同,在C中, int的大小根据平台不同有16位长,32位长,(在windows和现代的uni中,一般都是32位), long的长度在windows和unix中一般是32位的,而在.net中,long类型的长度是64位,并且interop很多操作不支持对long型数据的转换,如对结构体取size时,如果有long型字段,就会出现异常,这里要注意。
        ? 内存分配的对齐问题
        这个问题是最难以讲清楚的问题。在C语句的编译器中,都会有一些关于结构体成员如何对齐地址的编译指令或是伪指令,如VC的#pragma pack, __declspec( align() )指令等。这些指令指示编译器如何在内存中排布结构体的成员。
        其中,pack=n的意思是:结构体中下一个成员的起始地址,要用 “成员类型的长度和n之中的比较小的那个”来对齐。比如说下面的结构体成员:
        #program pack(8)
        Struct ST_E1{
        char s1[2];   // 从0偏移开始, 占到1位置,共2字节
        int i;        // min(sizeof int= 4,pack=8)=4, 因此,i的起始地址应该按4对齐
                   // 也就是空两个字节,到偏移4处开始,到偏移7,共4字节
        char s2[3];  // min(sizeof char=1, pack=8) = 1, 因此s2的起始地址按1对齐,
                  // 也就是从偏移8开始,到10,共3字节。
        Char s3    // 同理,s3占偏移量11,一个字节
        }
        结构体总长度为12个字节长。
        Off 0 1 2 3 4 5 6 7 8 9 10 11
        V S1 S1 空 空 i i i i S2 S2 S2 S3
       
        如果pack指为1呢?那么分配的方式如下:
        Off 0 1 2 3 4 5 6 7 8 9
        V S1 S1 i i i i S2 S2 S2 S3
       
        共10个字节长。
       
        可以看出,如果两边的pack值不一样,那么这个结构体在送到目的地之后就会出现成员偏移乱掉的问题(开始想念web services了吧?但是我们不总是能选择所处的条件的)。因此,一定要检查服务器与客户端的这个编译选项是不是一样的。一般情况下,pack=4是比较常见的情况。
       
        有关更多的内存对齐方式的讨论,可以参见本人另一博客文章及其评论内容。
        http://www.cnblogs.com/haoxiaobo/archive/2005/09/05/230204.html
       
        ? 字符集问题
        字符集是另一个需要注意的兼容性问题。例如上面的成员定义:
         [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 0x20)]
        public string usrname; // char usrname[20]
       
        在大多数面向C语言的API中,基本上是没有char与byte的区别的,但是.net中对于char与byte则有本质的不同,char是指一个与文化相关的符号,而byte指一个8位二进制数的物理存储单位。 一个char需要几个byte来保存,要视字符集编码方式而定,在.net和java里,内部的char都是unicode,一个字符两个字节,而在C中,基本上都是ansi(除了windows nt之后新增的那些 _T类型)。
        由于这个原因,一定要认真考虑服务器系统的字符集编码,否则会导致字符串在interop转换时,产生非常令人生气的结果。
        在结构体的定义时,charset = charset.ansi即通知了interop程序,字符串在向结构体转换时,要用ansi方式进行转换。
       
        更多有关字符集的讨论,请参见本人另一篇博客文章:
        http://xiaobohao.spaces.live.com/blog/cns!D1C72860197EBF38!1250.entry
       
        完整的用结构体内存块数据做消息体,调用tuxedo服务的代码如下:
       
        Bea.Tuxedo.ATMI.Utils.tuxputenv("WSNADDR=//<ip>:<port>");
        Bea.Tuxedo.ATMI.Utils.tuxputenv("WSINTOPPRE71=yes");
       
        AppContext ac = AppContext.tpinit(null);
       
        RecivedStruct rec;  // 这个RecivedSTRUCT即是tuxedo服务所规定的返回消息结构体在C#里的对应定义,请参阅本节之前的说明,对C风格的结构进行C#定义。
       
       
        SendStruct app = new SendStruct ();  // 这个SENDSTRUCT即是tuxedo服务所规定的调用消息结构体在C#里的对应定义,请参阅本节之前的说明,对C风格的结构进行C#定义。
        app.sOperCode = “…”;  // 几个示意成员
        app.sDeptCode = “…”;
        //…
       
        // 开始把C#结构体的内容复制为byte[]。
        // 取得长度
        int iAppLen = Marshal.SizeOf(app); 
        int iRecLen = Marshal.SizeOf(typeof(RecivedStruct));
        TypedCArray tbSend = new TypedCArray(iAppLen);
        TypedBuffer tbRecive = new TypedCArray(iRecLen);
       
        byte[] arAppData = new byte[iAppLen];
       
        // 分配一个系统堆内存, 并用于一个指针来指向之
        IntPtr pApp = Marshal.AllocHGlobal(iAppLen);
        IntPtr pRec = Marshal.AllocHGlobal(iRecLen);
       
        // 将托管结构复制到此地址指向的内存块中。
        Marshal.StructureToPtr(app, pApp, false);
        // 再将此地址块复制到字节数组中。
        Marshal.Copy(pApp, arAppData, 0, iLen);
       
        //把此字节数组绑定到要发送数据中。
        tbSend.PutBytes(arAppData);
       
        try
        {
            // 调用服务,返回一个typedbuffer.
            ac.tpcall(sServiceName, tbSend, ref tbRecive, 0);
       
            // 开始从这个返回的内容里取出数据。
            // 初始化一个与返回值相同大小的数组。
            byte[] arRecived = new byte[tbRecive.Size];
            // 从返回值对象中取出字节数组。
            ((TypedCArray)tbRecive).GetBytes(arRecived, arRecived.Length);
            // 用相反的步子把数据从字节流中复制到C#结构中。
            Marsal.Copy(arRecived, 0, pRec, iRecLen);
       
            Rec = Marsal.PtrToStructure(pRec, typeof(RecivedStruct));
        }
        catch (TPException tpex)
        {
            Trace.TraceError(tpex.ToString());
        }
        finally
        {
            ac.tpterm();
            Marshal.FreeHGlobal(pApp);
            Marshal.FreeHGlobal(pRec);
        }
       
       
        3 常用Tuxedo类与方法:
        3.1 tpchkauth检查是否需要认证和认证的级别
        int tpchkauth();
        在调用tpinit()之前检查是否需要认证和认证的级别。
       
        返回值:
       
        TPNOAUTH:不需要认证;
        TPSYSAUTH:系统认证,需要密码;
        TPAPPAUTH:应用认证,需要密码和特殊应用数据;
        当返回值为TPSYSAUTH和TPAPPAUTH时,我们必须使用tpalloc()分配一个TPINIT结构,在该结构中填入认证数据,然后用该结构作为参数调用tpinit()。
       
        失败原因主要有:
       
        协议错;
        操作系统错;
        tuxedo底层错。
        
       
        3.2 tpinit初始化
        在使用tuxedo其他服务之前,必须调用tpinit加入到应用中。
       
        int tpinit(TPINIT *tpinfo);
        参数说明:
       
        tpinfo:指向TPINIT类型的指针。
       
        TPINIT类型在atmi.h中有定义,如以下几个域:
       
            char usrname [32]; (32 characters significant)
            char cltname [32]; (32 characters significant)
            char passwd [32]; (8 characters significant)
            char grpname [32]; (32 characters significant)
            long flags;
            long datalen;
            long data;
        usrname:用户名或login名;
       
        cltname:应用定义;
       
        passwd:应用密码;
       
        grpname:在事务中使用,必须在配置文件定义的组列表中;
       
        flags:定义请求/通知类型和系统存取方法,其中TPU_SIG、TPU_DIP和TPU_IGN不能同时指定;TPSA_FASTPATH和TPSA_PROTECTED不能同时指定。有如下的值:
       
        TPU_SIG:选择信号通知;
        TPU_DIP:选择dip-in通知;
        TPU_IGN:忽略通知;
        TPSA_FASTPATH:选择fastpath方式系统存取;
        TPSA_PROTECTED:选择protected方式系统存取;
        datalen:应用特殊数据的长度;
       
        data:应用特殊数据;
       
        域flags的值覆盖系统的缺省定义,前提是在配置文件中没有指定NO_OVERRIDE。
       
        如果参数使用(TPINIT*)NULL,则client使用系统缺省的通知设置和系统存取设置,若需要认证,则出错返回TPEPERM。
       
        tpinit()调用失败返回-1,失败原因有:
       
        参数错;
        没有空间在BB;
        没有权限;
        协议错;
        操作系统错;
        tuxedo底层错。
        示例:
       
        TPINIT *tpinfo;
        char password[9];
        /* prompt user for password */
        if ((tpinfo = (TPINIT *)tpalloc(“TPINIT”, NULL,
                            TPINITNEED(0))) == NULL) {
            (void)userlog(“unable to allocate TPINIT buffer”);
            exit(1);
        }
        (void)strcpy(tpinfo->passwd, password);
        (void)strcpy(tpinfo->usrname, “Smith”);
        (void)strcpy(tpinfo->cltname, “Teller”);
        tpinfo->flags = (TPU_DIP|TPSA_PROTECTED);
        if (tpinit(tpinfo) == -1) {
            (void)userlog(“failed to join application”);
            tpfree((char*)tpinfo);
            exit(1);
        }
        
       
        3.3 tperm离开应用
        使用tuxedo服务完毕,调用tpterm()离开应用。
       
        int tpterm();
        函数出错返回-1。
       
        错误原因有:
       
        协议错;
        操作系统错;
        tuxedo底层错。
        
       
        3.4 tpacall发送异步请求
        发送异步请求。
       
        int tpacall(char *service, char *bufptr, long length,
        long flags);
        参数说明:
       
        service:请求的service名(最大15个字符,以null结尾);
       
        bufptr:请求发送的数据;
       
        length:发送数据长度(只有CARRAY类型用,其他设为0);
       
        flags:发送模式,有如下的值:
        TPNOTRAN:该次调用不能在一个事务里;
        TPNOREPLY:不需要回应(reply);
        TPNOBLOCK:非阻塞;
        TPNOTIME:不超时,一直等待;
        TPSIGRSTRT:被信号中断的系统调用重启。
        成功返回一个非负的描述符,该描述符可用于后续的tpgetrply调用,出错返回-1。
       
        错误原因有:
        参数错;
        当前太多的tpacall处理存在,上限是50;
        事务错;
        超时(time-out);
        
       
        3.5 tpgetrply接收异步回应数据
        接收异步回应数据。
       
        int tpgetrply(int *handle, char **bufpp, long *length,
        long flags);
        参数说明:
       
        handle:tpacall返回的描述符;
        bufpp:接收buffer的地址的地址,原buffer会自动调整;
        length:接收的buffer的长度的地址;
        flags:接收选项。有如下值:
        TPNOBLOCK:非阻塞;
        TPNOTIME:不超时,一直等待;
        TPSIGRSTRT:被信号中断的系统调用重启;
        TPGETANY:接收任何回应;
        TPNOCHANGE:要求接收的回应与发送数据相同。
        成功返回0,失败返回-1。
       
        出错原因:
       
        参数错;
        错误的接收buffer类型;
        超时;
        其他错误;
        
       
        3.6 tpcancel取消由tpacall发送的请求的响应
        取消由tpacall发送的请求的响应,在没有事务未完时。不能取消一个已经处理的请求。
       
        int tpcancel(int handle);
        参数说明:
       
        handle:tpacall返回的描述符;
       
        出错返回-1。错误原因有:
       
        错误的描述符;
        当前在事务模式;
        其他错误;
        
       
        3.7 tpcall同步发送请求并接收回应数据
        同步发送请求并接收回应数据。
       
        int tpcall(char *service, char *sbufp, long slength, \
         char **rbufpp, long *rlength, long flags);
        参数说明:
       
        service:请求的service名;
        bufp:发送buffer的地址;
        slength:发送数据长度(只CARRAY使用,其他为0);
        rbufpp:响应buffer的地址的地址,可以与发送buffer为同一块区域;
        rlength:响应buffer的长度的地址(不能为NULL);
        flags:标志。有如下值(含义见tpacall和tpgetrply):
        TPNOTRAN:该次调用不能在一个事务里;
        TPNOREPLY:不需要回应(reply);
        TPNOBLOCK:非阻塞;
        TPNOTIME:不超时,一直等待;
        TPSIGRSTRT:被信号中断的系统调用重启。
        返回-1表示出错,其他返回值都表示成功。
       
        错误原因与tpacall和tpgetrply相同,除了描述符错。
       
        
       
        3.8 tpgprio获得上一次请求或接收的消息的优先级
        获得上一次请求或接收的消息的优先级。
       
        int tpgprio();
        成功返回的范围是1-100,值越大优先级越高。失败返回-1。
       
        使用举例:
       
        struct {
            int hdl; /* handle*/
            int pr; /* priority*/
        } pa[SIZE];
       
        for (i=0; i < requests; i++) {
            /* Determine service and data for request */
            pa [i].hdl = tpacall(Svc, buf, len, flags);
            /* Save priority used to send request */
            pa[i].pr = tpgprio();
        }
        /* Use qsort(3) routine to sort handles in priority order */
        qsort((char*) pa, requests, sizeof(pa[0]), cmpfcn);
        for (i=0; i< requests; i++) {
            tpgetrply(&pa[i].hdl, &rbufp, &rlen, rflags);
        }
       
        3.9 tpsprio设置下一个要发送的消息的优先级
        设置下一个要发送的消息的优先级。
       
        int tpsprio (int prio, long flags);
        参数说明:
       
        prio:要设置的优先级;
        flags:标志。有如下值:
        0:使用相对优先级,值改为(default+prio);
        TPABSOLUTE:绝对优先级,值改为prio;
        优先级的范围是1-100,超过次限制的值被改为相应的最大(小)值。
       
        失败返回-1。错误原因有TPEINVAL、TPEPROTO、TPESYSTEM、和TPEOS。
        
          

    Comments (2)

    Please wait...
    Sorry, the comment you entered is too long. Please shorten it.
    You didn't enter anything. Please try again.
    Sorry, we can't add your comment right now. Please try again later.
    To add a comment, you need permission from your parent. Ask for permission
    Your parent has turned off comments.
    Sorry, we can't delete your comment right now. Please try again later.
    You've exceeded the maximum number of comments that can be left in one day. Please try again in 24 hours.
    Your account has had the ability to leave comments disabled because our systems indicate that you may be spamming other users. If you believe that your account has been disabled in error please contact Windows Live support.
    Complete the security check below to finish leaving your comment.
    The characters you type in the security check must match the characters in the picture or audio.

    To add a comment, sign in with your Windows Live ID (if you use Hotmail, Messenger, or Xbox LIVE, you have a Windows Live ID). Sign in


    Don't have a Windows Live ID? Sign up

    xiaobo haowrote:
    没有哦。我只是搞明白如何做之后就记录下来,没有进一步去应用。
    Mar. 31
    霖 王wrote:
    请问楼主有对C#的Tuxedo客户端做过性能测试吗?比如10个线程中同时调用Tuxedo的TOUPPER服务.
    Mar. 29

    Trackbacks

    The trackback URL for this entry is:
    http://xiaobohao.spaces.live.com/blog/cns!D1C72860197EBF38!1442.trak
    Weblogs that reference this entry
    • None