gsoap从多个服务器获取函数接口

1.gsoap就是服务器中有一些API,我们通过gsoap来得到这些API

2.其中涉及到两个文件,wsdl2h和soapcpp2

3.wsdl2h是用来产生头文件的。用法是wsdl2h -o 头文件名 WSDL文件名或URL。URL就是服务器的地址。服务器的相关函数就写在这个地址中。

产生的头文件就包含了这些函数。但是这些函数的名称是在原来的基础上加了一部分。soapStub.h中可以找到接口函数的声明。

4.soapcpp2生成客户端和服务器端的代码框架。可以理解为获取源码。

5.执行上面两个文件后,在客户端就可以使用服务器中的接口了。但是一般我们还需要对我们得到的接口进行再次封装。

 

本文针对的是服务器端已经写好了函数接口,本端只是从服务器端调用接口。本篇获取的是纯C的代码,然后用C++封装成自己的函数。

 

实际使用

1.执行wsdl2h

./wsdl2h  -c -ntest -NTest -o Test.h http://websrv.cs.fsu.edu/~engelen/calc.wsdl

-c 表示生成纯C代码,不然则产生C++

-n name 表示用name来作为命名空间的前缀,取代缺省的ns

-N name 表示用name来指定服务命名空间的前缀

最后会生成一个.h文件

如果有多个服务器地址,就执行多次。同时会产生多个.h头文件。没有添加-n和-N的属性也可以产生头文件,但是如果有多个服务器地址,一定要加上,以便区别。

2.执行soapcpp2

如果有多个.h文件,就把多个.h文件手动复制粘贴合并到一个.h文件中。

如果需要执行成功,还需要soap将源文件中的soap12.h放入当前目录。

./soapcpp2 -CLx Test.h

-C只生成客户端代码

-L表示不生成soapClientLib/soapServerLib

-x不生成XML形式的传输消息文件

最后会生成一堆文件。

虽然生产了一堆文件,但是我们要仔细看一下最后的执行结果。会提示多少警告,多少错误。警告可以忽略。如果有错误,找一下错误的提示信息,一般是在将几个头文件合并之后,存在一些变量的重复定义。只需要将提示中重复的代码删除即可。

重新执行soapcpp2.

 

soapStub.h 根据输入的.h文件生成的数据定义文件,一般我们不直接引用它

soapH.h 客户端和服务器端应该包含该头文件,它包含了soapStub.h。

soapC.c 针对soapStub.h中的数据类型,.c文件实现了序列化和反序列方法

soapClient.c 是客户端调用webservice的框架文件,我们的代码主要在此实现或从它继承

.nsmap 命名空间的定义

3.编译

将上述几个文件全部加入IDE中。第一步中最终合并的.h,soapC.c,soapClient.c,soapH.h,soapStub.h,所有的.nsmap,soap12.h,还有两个是soap源文件中的stdsoap2.c,stdsoap2.h。

如果直接编译会报错,关于namespace的错误。我们需要新建一个头文件,在里面包含.nsmap。再次编译,就可以编译成功了。

4.调用接口函数

1.我们在soapClient.c中可以看到我们得到的soap接口,接下里就可以使用这些接口了

int LoginSystem(para1, para2, &msg) {
    int ret;
    struct soap *soap = soap_new();

    if (NULL == soap) {
        cout << "soap_fail" << endl;
    }

    struct _NAME__INPUT NAME__INPUT;        //gsoap函数中除非参数很简单,不然一般都是通过结构体的方式来传参
//包括我们传给函数的,也包括函数传给我们的。这个结构体具体的设置可以自己跟踪一下
    struct _NAME__OUTPUT NAME__OUTPUT;
    
    
    soap_set_namespaces(soap, namespacesXXX);  //设置namespace。这个namespace需要注意
    soap_set_mode(soap,SOAP_C_UTFSTRING);          //对于参数及回传的数据是中文,让gSOAP使用UTF8方式传送以防止乱码
    
    NAME__INPUT.1 = 1;    //设置我们传给函数的结构体
    NAME__INPUT.2 = 2;
    NAME__INPUT.3 = 3;
        
    string endpoint = "http://192.168.0.1";         //设置gsoap服务器的地址
    
    if (soap_call___FUN(soap,endpoint.c_str(),NULL,&NAME__INPUT,&NAME__OUTPUT) == SOAP_OK) {
    //也有可能__any被包含在NAME__OUTPUT中结构体中的结构体,按照正常结构体中的引用就行xxx.yyy->__any,这边也需要注意
        msg = NAME__OUTPUT->__any;    
        ret = MCC_OK;
    } else {
        //错误处理,这点在后面的文章中有提到
    }
    
    soap_end(soap);    //一共三步释放soap,这是根据gsoap官网的例子来的;
    soap_destroy(soap);
    soap_free(soap);
    return ret;
}

上面的代码的注释中,提到了2个注意点,一个是namespace(命名空间)的注意点,还有一个是关于输出结构体(gsoap从服务器取得值传给我们的)的注意点。

2.关于namespace的注意点

如果我们只有一个服务器地址,即wsdl2h只需要运行一次,那么我们会只得到一个*.nsmap(产生的*12.nsmap可以直接丢弃,这样算一个)。那么我们可以不需要在代码中设置soap_set_namespaces(soap, namespacesXXX);这一行代码。这个namespaceXXX的名字就在.nsmap中写着。

如果我们有多个服务器地址,即wsdl2h需要运行多次。通过将产生的多个.h头文件合并,执行soapcpp2会得到多个*.nsmap(同样可以把*12.namap丢弃)。我们可以看一下每一个.nsmap里面的内容都是一样的。gsoap自动的把命名空间合并了,但这对我们并不友好。我尝试只将其中一个命名空间放入我的工程中,然后在自建的mytest.cpp中包含这个.nsmap,然后使用gsoap中的接口,发现只有排在第一位的命名空间中的函数接口是可以正确得到数据的,别的命名空间中的函数接口无法得到数据。我又尝试将第一位的命名空间和第二位的命名空间调换位置,将第二位的命名空间排在了第一位,发现,只有第二位的命名空间中的函数接口可以正确得到数据。所以,可以得出结论,当多个命名空间存在于一个.nsmap中时,函数接口只会寻找第一个命名空间,如果不匹配就会报错,我这边报的错误为"Error 200: HTTP 200 OK"(关于如何取得这个错误信息,后面有介绍)。

那么我们的解决办法就是,将各个命名空间分开来。

在生成的多个.nsmap中只保留公共的部分(我这边是开头的四行代码“soap-ENV”,"soap-ENC","xsi","xsd")和自己的那部分,其余都删除,当然最后的{NULL,NULL,NULL,NULL}也需要保留。需要注意的是,不同的命名空间需要不同的名字,不能重名。最后在自己封装的mytest.cpp中需要将所有的.nsmap都包含进来。

也可以在同一个.nsmap中将命名空间拆分开来,相当于和上面一样,只是合并在了同一个文件中而已。

如果按照上面的做法,将各自的namespace拆开来,赋予各自的名字,最终编译的时候会报关于"namespace"的错误。这是因为gsoap必须要一个"namespace"的命名空间,这个命名空间中的内容无所谓,只需要存在即可。(如果你在上一步中偷懒了一下,将其中的一个"namespace"保留,改变其余的n-1个"namespace",那么你可能会避开这个问题)。

这样,关于namespace的注意点就完成了。

3.关于输出结构体的注意点。

gsoap提供的接口函数形式一般为

soap_call___NAME__funXXX(soap, soap_endpoint, soap_action, struct  _input *input, struct _output *output);

但是呢我们在用官方提供的calc那个例子的时候看到的不是上面的这种形式,而是

soap_call_ns2__add(soap,soap_endpoint, soap_action, double a, double b, double *result);

先解释一下共同的部分

soap:soap结构体

soap_endpoint: url地址,不变的可以传NULL,为什么gsoap中谢了如果为NULL,那么这个地址就是使用wsld2h这个工具时产生的地址。

soap_action:传NULL

再来分析不同的部分

先看一下官方例子中add这个函数内部的实现方法

gsoap从多个服务器获取函数接口_第1张图片

看一下红色框框中的内容,说明gsoap内部使用的就是用结构体来传参。所以,函数接口中的参数如果是结构体则为一般情况,不为结构体,则为特殊情况,接口内部会自动的将非结构体转换为结构体。至于为什么要用结构体传参,本人的理解为,当输入输出参数较多时,可以很好的封装函数,形参的接口结构会比较简单。

接下里就用输入结构体和输出结构体来讲解。

struct _input 和 struct _output 的具体结构可以在soapStub.h中找到。不同的接口之间是不同的。

struct _input :输入结构体,里面的成员就是需要传给gsoap的,需要赋值的就给里面的成员赋值

struct _output:

在正确调用函数接口的前提下(包括上面提到的命名空间),在能够正确编译的前提下,我们就可以从output中的成员得到我们想要的数据。

但是在本人实际的项目中却在其中遇到了点问题。

本人用SoapUI这个工具,可以得到使用gsoap接口时的结果。但是前面的结果和我用在代码中调用接口得到的结果不一样。接下里就开始分析这种不一样。(关于SoapUI这工具的使用请自行baidu)

gsoap从多个服务器获取函数接口_第2张图片

可以将这个结果信息看成4部分,如上图所示。

soap头部信息:这部分信息是gsoap为了传输数据自己添加的

返回结果结构体:这部分就是自己定义的struct _output这个结构体的名字

返回结果中的两个成员:这部分就是struct _output中的成员

对应的结束标志:这部分是有XML语法结构决定(自行查找XML格式)

---------------------------------------------------------------分割线-----------------------------------------------------------------

schema和diffgram节点里面的内容就是我们要得到的数据。(schema前面的xs只是命名空间)

但是在实际使用代码时,我们得到的只有schema这个节点的信息。

我们先来看一下由wsdl.h头文件生成的test.h头文件

struct _NAME__Output
    {
        /// @todo 
        /// @todo Schema extensibility is user-definable.
        ///       Consult the protocol documentation to change or insert declarations.
        ///       Use wsdl2h option -x to remove this element.
        ///       Use wsdl2h option -d for xsd__anyType DOM (soap_dom_element):
        ///       wsdl2h maps xsd:any to xsd__anyType, use typemap.dat to remap.
        _XML        __any           0;    ///< Catch any element content in XML string.
    }

注意注释中的一句话"Catch any element content in XML string":捕获XML字符串中的任何元素内容。也就是__any会捕获XML中的所有数据,那么为什么我们只得到了schema这个节点的信息呢?本人估计是因为gsoap的解析XML格式的时候将schema当成了一个根节点,自然遇到也就停止了(关于这部分,可以参考我的其他博客expat解析XML)。就无法继续解析了。

解决办法就是再增加一个根节点。

struct _NAME__Output
    {
        _XML        __any           0; 
        /// @todo 
        /// @todo Schema extensibility is user-definable.
        ///       Consult the protocol documentation to change or insert declarations.
        ///       Use wsdl2h option -x to remove this element.
        ///       Use wsdl2h option -d for xsd__anyType DOM (soap_dom_element):
        ///       wsdl2h maps xsd:any to xsd__anyType, use typemap.dat to remap.
        _XML        __any           0;    ///< Catch any element content in XML string.
    }

我们让第一个__any对应schema,让第二个__any对应diffgram。然后又会出现一个问题,一个结构体中怎么能出现相同的变量名呢。先不管,直接使用soapcpp2执行上面修改过得头文件。执行过程中出现warning

warning:__any已经被定义了,我们可以用__any_,不然可以会出错。

好,我们就按照他说的更改

struct _NAME__Output
    {
        _XML        __any_           0; 
        /// @todo 
        /// @todo Schema extensibility is user-definable.
        ///       Consult the protocol documentation to change or insert declarations.
        ///       Use wsdl2h option -x to remove this element.
        ///       Use wsdl2h option -d for xsd__anyType DOM (soap_dom_element):
        ///       wsdl2h maps xsd:any to xsd__anyType, use typemap.dat to remap.
        _XML        __any           0;    ///< Catch any element content in XML string.
    }

再次使用soapcpp2工具,得到.c和.h。

调用接口,发现Output.__any_ 对应schema的信息。Output.__any对应diffgram的信息。

根据我参考的博客,还有一种方法也可以增加一个根节点,就是将__any_替换为xsd_schema,可能是注释中提到了"xsd__anyType"。

-------------------------------------

原来是我傻了。

只要是增加一个节点,任意名字都可以,但是__前面不能加东西。

可以是      _XML        __fuck      0;

也可以是      _XML        __wow       0;

-------------------------------------

至此,我们就完成了使用gsoap调用服务器的接口从而得到具体数据的过程。

当然,我们得到的只是字符串,从字符串中提炼出需要的数据才是我们的最终目的。

请移驾expat解析XML。

 

5.错误处理

如果gsoap接口调用的返回值 不等于 SOAP_OK,那么就需要得到错误信息。

gsoap提供了一类错误处理的函数,相关函数如下

struct soap *soap = soap_new();    //也可以是struct soap soap;后面就需要&soap
//--------------------
    调用gsoap接口
//--------------------
if(soap_call__XXX() == SOAP_OK)
{
    //正确处理
}
else
{
    //得到完整的错误信息
    char faultmsg[512];
    soap_sprint_fault(soap, faultmsg, sizeof(faultmsg));
    cout << faultmsg << endl;
    //得到错误信息中的文本部分
    const char ** fd;
    fd = soap_faultstring(soap);
    cout << *fd << endl;
    //得到错误信息中的代号部分
    const char ** fd;
    fd = soap_faultcode(soap);
    cout << *fd << endl;
    //得到错误信息中的代号部分
    const char ** fd;
    fd = soap_faultdetail(soap);
    cout << *fd << endl;
}

如果出错,一般报的错误都是关于namespace的,方法可以看第四点中的namespace那一节。

还需要检查一下url的地址是否设置正确。

struct _input中的结构体中的成员设置是否正确。

 

感谢:

https://blog.csdn.net/qq_31930499/article/details/79592198

http://www.cnblogs.com/gvlthu23061/p/7316219.html

你可能感兴趣的:(第三方库)