2017年06月11日16:42:58
这几天做了一个售电接口(windows平台下),包括webservice服务(C#)、webservice动态库、客户端dll(C++)、客户端gsoap代理。软件结构如下:
clientDLL(客户端机器)---->gsoapProxy(客户端机器)------>webservice(服务器)--->webserviceDLL(服务器)---->HSM(加密机)
【强调一下前提,webservice服务是用vs2010创建的web工程,而client是C/C++写的,这里主要介绍如何使用gsoap代理client请求】
由于客户端和服务器不是在同一台机器上,所以使用直接动态调用dll已不可能。C/C++要通过网络去连接服务器,将函数参数传输到webservice的对应函数接口中,就必然要使用某种工具来将函数参数转换到网络格式传输到server,gsoap就是这种强大的工具。gsoap不仅可以代理客户端请求,还可以代理服务端(本次只使用其作为客户端代理)。虽然其已经尽可能做到简单易用,但是我在网络上搜索了好多文章,都是教到生成出文件为止,而如何使用生成出的文件,都是不甚了了。所以我将整个使用过程记录下来,方便以后借鉴。
(一) 下载gsoap软件,网上搜一下,去官方下,然后解压出来,一定要看是否解压完全,在其根目录下有3个文件stdsoap2.h、stdsoap2.cpp 、stdsoap2.c,这个后面要用到的。(我的就是解压过程中电脑好像死机了,但是我没注意,然后没有解压完,害的找了半天stdsoap2这个文件)
(二)生成中间文件
在解压出来的目录下找到wsdl2h.exe soapcpp2.exe两个文件。
2.1 首先生成中间头文件,作用就是将你的服务端暴露出来的接口生成按网络格式的函数形式。打开命令行窗口,进入到gsoap目录下:【我用的C++方式】
wsdl2h -s -o test.h http://xxxxx:xxxx/xxx.wsdl
wsdl2h常用选项
- -o 文件名,指定输出头文件
- -n 名空间前缀 代替默认的ns
- -c 产生纯C代码,否则是C++代码
- -s 不要使用STL代码
- -t 文件名,指定type map文件,默认为typemap.dat
- -e 禁止为enum成员加上名空间前缀
后面的***.wsdl是服务端接口说明。如何找到它呢?就我的项目而言,是这样的:
我的webservice是用c#写的,直接使用vs2010创建的web工程,【运行该工程】,就会启动一个web服务了,会弹出浏览器打开一个页面,点击" *****.asmx ",会看到所有的web接口,然后页面上有一个“查看说明”字样的链接,点击应该会进入到 " ****.wsdl "了,对,就是它。
2.2 根据头文件生产其他文件
soapcpp2 -j test.h
soapcpp2常用选项
- -C 仅生成客户端代码
- -S 仅生成服务器端代码
- -L 不要产生soapClientLib.c和soapServerLib.c文件
- -c 产生纯C代码,否则是C++代码(与头文件有关)
- -I 指定import路径(见上文)
- -x 不要产生XML示例文件
- -i生成C++包装,客户端为xxxxProxy.h(.cpp),服务器端为xxxxService.h(.cpp)。
- -j 和-i类似,区别在于生成的代理类不继承于soap struct,而是包含了一个soap的指针。此种方式生成的代理类便于相互通信
将生成下面这些文件
- soapStub.h// soap的存根文件,定义了test.h里对应的远程调用模型
- soapC.csoapH.h //soap的序列和反序列代码,它已经包含了soapStub.h,服务器端与客户端都要包含它
- soapClient.csoapClientLib.c //C客户端代码,soapClientLib.c文件则只是简单地包含soapClient.c和soapC.c
- soapServer.csoapServerLib.c //C服务器端代码,soapServerLib.c文件则只是简单地包含soapServer.c和soapC.c
- ServiceSoap.nsmapServiceSoap12.nsmap // 名空间定义,服务器端与客户端都要包含它
- soapServiceSoapProxy.hsoapServiceSoap12Proxy.h //客户端的C++简单包装(如果头文件是纯C代码,这两个文件就不会生成)
综上所述
- 如果编写服务器端C方式,需要的文件就有:soapStub.h、soapC.cpp soapH.h soapServer.c soapServerLib.c ServiceSoap.nsmap ServiceSoap12.nsmap soapServiceSoapProxy.h soapServiceSoap12Proxy.h
- 如果编写客户端C++方式,需要的文件就是:soapStub.h soapC.cpp soapH.h soapXXXXSoapProxy.cpp soapXXXXSoapProxy.h soapXXXXSoapService.cpp soapXXXXSoapService.h XXXXSoap.nsmap
- 当然,还要加入gsoap库里的stdsoap2.cpp文件(如果是写C代码,则加入stdsoap2.c)【在gsoap根目录下,不同版本的gsoap对应的该文件是不同的,不可以混用】
- 到这里test.h已经没用了
如果看到soapcpp2提示:”Criticalerror: #import: Cannot open file "stlvector.h" forreading.“, 那是因为我们的头文件使用了STL(wsdl2h没用-s选项),这时要使用-I选项指定gSOAP的import文件路径,这个路径是"$gsoap\gsoap\import":
soapcpp2 -j test.h -I D:\gsoap-2.7\gsoap\import
(三) 创建代理,入参出参转换,接收返回值
gsoap会将你的服务端接口进行名字转换,而且分成请求和响应两个class(我使用的是C++方式,用起来感觉更简单一些)。
例如:服务端接口为 int EncryptPurse(char* cardNum, char* fileMoney, char* dataOut);
gsoap会将该接口转换为:
_ns1__EncryptPurse 【请求类,传递入参】
_ns1__EncryptPurseResponse 【响应类,获取出参和返回值】
创建soap结构体和gsoap代理:
struct soap soap;
HSM_USCOREEPSaleSoapProxy soapProxy; // 类型名字会有不同
定义soap的数据传输格式:
soap_init(&soap); //Initializes a runtime context
soap_set_mode(&soap, SOAP_C_MBSTRING); //设置数据模式
soapProxy.HSM_USCOREEPSaleSoapProxy_init(SOAP_C_MBSTRING, SOAP_C_MBSTRING);//初始化数据传输模式
然后进行参数转换,注意入参和出参分别在两个类中:
_ns1__EncryptPurse reqEncryptPurse; //创建请求类对象
_ns1__EncryptPurseResponse respEncryptPurse; //创建响应类对象
reqEncryptPurse.cardNum = IncardNo; // IncardNo为实际传入的参数
reqEncryptPurse.fileMoney = InfileMoney; //InfileMoney为实际传入的参数
respEncyptPurse.dataOut = Outdata;// Outdata为接收出参的参数
接下来调用接口:
int ret = soap.EncryptPurse("http://localhost:1132/EPSaleWebService.asmx", NULL, &reqEncryptPurse, respEncryptPurse);
接收返回值和出参
ret = respResponse.EncryptPurseResult; //接收EncryptPurse的返回值
strcpy(dataOut, respEncryptPurse.dataOut); //接收出参的值
关闭soap,清理内存:
soap_close(&soap);
soap_end(&soap);
soap_done(&soap);
到这里,一个函数接口的请求和相应就完成了.
遇到的问题:
1.最开始编写gsoap工程用的vc6,在编译时报错: sockaddr_storage未定义的类型
由于winsock的版本不一致导致,改到vs2010里再编译就没有该问题了。如果非要在vc6里编译,请继续百度,应该有其他解决方案.
2. error LNK2001: 无法解析的外部符号 _namespaces
工程--属性--配置属性---C/C++---预处理器, 添加 WITH_NONAMESPACES