目录
gsoap是十多年前的协议框架了,挺老的,而且网上能搜到有用的资料不多,但是因为项目需要与第三方外部协议对接只能慢慢摸索,不过有个官方详细文档可以参考。
官方文档链接:https://www.genivia.com/doc/guide/html/index.html
gsoap下载地址:https://www.genivia.com/downloads.html
使用c++ gsoap构建服务端
我做的项目是把gsoap当作插件安装到apache服务中,也就是需要依赖apache httpd服务。
首先需要服务接口的.wsdl接口文件,这个文件由客户提供。在这个例如为testA.wsdl文件目录下执行如下命令
生成头文件:
wsdl2h -o testA.h testA.wsdl
生成c++服务端接口代码:
soapcpp2 -j -S testA.h
-j: 生成的服务代理类和对象包含struct soap
-S: 只生成服务器端代码
在该目录下会生成固定的.cpp、.h、.xml、.nsmap文件
其中soapxxxxxxxxxServiceSoapBindingService.h的头文件中,类的尾部有wsdl定义的接口虚函数,例如有一个testfun接口:
virtual int testfun(std::string *ns1__param_, std::string &ns1__paramResponse_) SOAP_PURE_VIRTUAL;
...
把接口拷贝出来到另一个.c文件中,例如testserver.c去实现这些类虚函数,这就是客户端调用的同步接口。例如:
#include <stdio.h>
#include <string.h>
#include "soapxxxxxxxxxServiceSoapBindingService.h"
#include "xxxxxxxServiceSoapBinding.nsmap"
//在gsoap源码目录:gsoap-2.8\gsoap\mod_gsoap\mod_gsoap-1.0\apache_20
#include "apache_gsoap.h"
IMPLEMENT_GSOAP_SERVER()
extern "C" int soap_serve(struct soap *soap)
{
xxxxxxServiceSoapBindingService service(soap);
int err = service.serve();
service.destroy();
return err;
}
//接口需要实现
int xxxxxxxxxServiceSoapBindingService::testfun(std::string *ns1__param_, std::string &ns1__funResponse_)
{
//todo
//业务逻辑...
//最后给ns1__funResponse_赋值结果,返回的时候客户端能收到
return SOAP_OK;
}
因为编译是c的所以把所有cpp文件改为c后缀了。
从gsoap源码目录中找到公共代码stdsoap2.h和stdsoap2.c,拷贝到和接口同一个目录下。
编译生成testserver.so库:
apxs -a -c -S CC=g++ testserver.c soapC.c soapxxxxxServiceSoapBindingService.c stdsoap2.c
需要在运行环境httpd/conf.d/中增加配置项test.conf:
LoadModule gsoap_module /usr/lib64/httpd/modules/mod_gsoap.so
<IfModule mod_gsoap.c>
<Location /testfun>
SetHandler gsoap_handler
SOAPLibrary /usr/lib64/testserver.so
Order allow,deny
Allow from all
</Location>
</IfModule>
testfun是接口名
mod_gsoap.so插件也需要拷贝到对应的环境目录
testserver.so是服务端接口库拷贝到对应的环境目录
接下来重启一下httpd服务,用postman调接口测试ok。
使用c++ gsoap构建多个客户端并设置命名空间隔离
客户端的例子在gsoap源码中附带了很多,而且官方文档也有详细说明。
构建一个客户端比较简单这里不再说明,因为开发的时候是多人合作开发,这里讲述多个wsdl生成的客户端代码如何合并成一个可执行文件,如何对c++客户端代码进行自定义命名空间隔离,在编译的时候不会报多重定义和未定义等错误。
官方文档链接:https://www.genivia.com/doc/guide/html/index.html
所在目录:How to build a client or server in a C++ code namespace
首先需要客户端的.wsdl文件,这个文件由客户提供。
在这个例如为testB.wsdl文件,有一个getfun方法,目录下执行命令
生成头文件:
wsdl2h -o testB.h testB.wsdl
然后手动修改testB.h,增加命名空间(使用-qname参数应该可以),例如如下:
namespace myclient { //增加自定义namespace,包住头文件所有代码内容
#include <vector>
template <class T> class std::vector;
int ns1__getfun(
std::string :_param,
std::string :&:_getFunReturn
);
}
//end
接着生成c++服务端接口代码:
soapcpp2 -C -L -n testB.h
-C: 只生成客户端代码
-n: 生成的服务函数重命名soap_serve为name_serve并将生成的命名空间表(nsmap文件)
-L: 不生成lib文件
在该目录下会生成固定的.cpp、.h、.xml、.nsmap文件
需要提取公共 SOAP Header 和 Fault 序列化程序 ,否则编译出现未定义错误。当前项目目录新建一个空env.h文件,执行:
soapcpp2 -penv env.h
生成的envC.cpp、envH.h、envStub.h是必要的文件,需要包含envH.h到客户端中,客户端伪代码如下:
#include "testBH.h"
#include "envH.h"
#include "testB.nsmap"
//命名空间声明 第一步(重要)
using namespace myclient;
//服务端IP
#define SERVER "https://127.0.0.1/"
int main(int argc, char **argv)
{
struct soap soap;
soap_init1(&soap, SOAP_ENC_XML); //初始化soap
if (soap_ssl_client_context(&soap,
SOAP_SSL_NO_AUTHENTICATION, //去掉ssl认证
NULL,
NULL,
NULL,
NULL,
NULL
))
{
soap_print_fault(&soap, stderr);
printf("client Soap init fail!!!\n");
exit(EXIT_FAILURE);
}
std::string getfunReturn;
//设置上下文的命名空间 第二步(重要)
soap_set_namespaces(&soap, myclient_namespaces); //myclient_namespaces定义在namap中
//接口调用
soap_call_ns1__getfun(&soap, SERVER "getfun", "", "test123", getfunReturn);
if (!soap.error)
{
printf("result = %s\n", getfunReturn.c_str());
}
//释放销毁soap
soap_destroy(&soap);
soap_end(&soap);
soap_done(&soap);
return 0;
}
其他客户端也可以这么生成,然后包含进来使用。
编译命令需要加上-DWITH_NONAMESPACES:
g++ -g -o myclient main.cpp testBClient.cpp testBC.cpp envC.cpp stdsoap2.cpp -DWITH_NONAMESPACES -lssl -lcrypto -DWITH_OPENSSL
使用c++ gsoap的MTOM协议传输文件
源码请参考:
(1)gsoap源码包里的dmeo:\gsoap_2.8.122\gsoap-2.8\gsoap\samples\mtom-stream\
(2)官方文档链接:https://www.genivia.com/doc/guide/html/index.html#MTOM
所在目录:MTOM attachments
建议可以直接编译测试源码附带的mtom-stream例子,然后抓包测试下发送文件是怎么样的过程。
代码里它的方法是使用XOP Include 元素xop:Include在接口头文件中定义为_xop__Include结构或类,然后把文件发送和接收的回调注册到soap,这样在调接口时发送和接收 MTOM XOP 附件的过程是完全自动化的。
//服务端侧
soap.fmimereadopen = mime_read_open; //这三个read是注册发送文件回调
soap.fmimereadclose = mime_read_close;
soap.fmimeread = mime_read;
soap.fmimewriteopen = mime_server_write_open; //这三个write是注册接收文件回调
soap.fmimewriteclose = mime_server_write_close;
soap.fmimewrite = mime_server_write;
//客户端侧
soap.fmimereadopen = mime_read_open; //这三个read是注册发送文件回调
soap.fmimereadclose = mime_read_close;
soap.fmimeread = mime_read;
soap.fmimewriteopen = mime_client_write_open; //这三个write是注册接收文件回调
soap.fmimewriteclose = mime_client_write_close;
soap.fmimewrite = mime_client_write;
soap初始化需要设置SOAP_ENC_MTOM参数,在调接口的时候,需要用户指定打开哪一个文件去发送,也就是需要给xop__Include赋值文件信息等。
但是客户给的wsdl里面接口没有定义 xop__Include没有办法赋值,也就触发不了传输文件的回调,这个时候发送端如何触发发送回调给接收端发文件呢?
这个问题也搞了很久,直到看到官方文档的这两函数soap_set_mime、oap_set_mime_attachment,需要在发送端的接口中手动启动和添加附件!例如:
//初始化并启用 MIME
soap_set_mime(soap, NULL, "test");
//添加附件,len是文件长度必填,发送哪个文件可以在soap->user中保存,然后在mime_read_open回调中去打开
if (soap_set_mime_attachment(soap, NULL, len, SOAP_MIME_BINARY , "*/*" , "" , NULL, NULL))
{
soap_clr_mime(soap);
printf("soap_set_mime_attachment error!\n");
return soap->error;
}
个人笔记,仅供学习,如有错漏欢迎指正~