最近要利用C++调用WebService接口,中间工具是gSoap,IDE:VS2010,WebService接口:http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl
关于Soap,它是一种简单对象访问协议,是一种轻量的、简单的、基于 XML 的协议,它被设计成在 WEB 上交换结构化的和固化的信息。它是一种通信协议,用于Web应用程序之间的因特网通信,它提供一组不受平台和语言限制的方法使应用程序得以相互之间进行Intertnet通信。而gSoap可以看做是对soap的一个实现封装,它隐藏了在调用WebService中的大量实现细节,使得C++调用WebService变得较为简单。它提供编译工具(实际就是针对接口的WSDL生成类文件),然后引入工程操作生成类即可达到访问WebService接口的目的。仔细查看生成的文件就会发现生成的类与WebService接口的WSDL文件实际相对应的。
WSDL,最初弄的WSDL的时候被这个WSDL弄的焦头烂额的。这个文件实际上就是WebService的对外接口文件,它是一个XML文件,描述了WebService接口的外部方法和抽象方法,以及各协议下的相关调用,是一个用来描述Web服务和说明如何与Web服务通信的XML语言,为用户提供详细的接口说明书。对于WSDL文件,只需要在浏览器中打开,即可查看到WebService接口的外部供调用的方法。
对于Soap,gSoap,WSDL就大致介绍到这里,这里记录如何在C++中调用WebService接口,以http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl为例,一个查询手机号码归属地的WebService接口。
下载gSoap-2.8版本解压放置D盘,在命令行下进入gSoap-2.8/gsoap/bin/win32目录。网络上有不少gSoap的下载,读者可自行下载。
然后运行命令:wsdl2h -s -o service1.h http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl,这时在wsdl2h.exe目录下生成service1.h头文件,然后运行命令:soapcpp2 -C -x service1.h生成其他的文件,在我的目录下生成了这些文件:
第一个红色圈圈里面都是生成的文件,要全部引入工程,其中soapClientLib.cpp文件无用,不引入,引入会报错。然后将gSoap-2.8/目录喜爱的stdsoap2.h/cpp也要引入工程。
取消soapC.cpp,soapClient.cpp,stdsoap2.cpp这三个文件的预编译头,在stdafx.h文件中增加#include"MobileCodeWSSoap.nsmap"
从webService获取数据:
我们可以查看http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx?wsdl,查看该xml文件可看到字符编码是UTF8的,因此传递给接口的参数需要转换到UTF8才可,ConvAnsiToUtf8()是转换函数。gsoap 的 核心内部是入和出都是UTF8编码的,由于中文是多字节的,所以将多字节的按照UTF8转换,最终结果就是乱码。正确的方式应该是:soap_set_mode(&soap,SOAP_C_MBSTRING);这样中文便不会乱码,可正常返回。
WebService服务基本概念:就是一个应用程序,它向外界暴露出一个可以通过web进行调用的API,是分布式的服务组件。本质上就是要以标准的形式实现企业内外各个不同服务系统之间的互调和集成。
soap概念:简单对象访问协议,是一种轻量的、简单的、基于 XML 的协议,它被设计成在WEB 上交换结构化的和固化的信息。
从这里的概念可以看得出来,soap是一个基于xml格式的web交互协议,而webservice是一种使用web方式实现的功能。就好像是网络视频服务器和http的关系,就是这里的webservice服务器和soap的关系。其实从历史上来说,先有的soap这种协议,然后微软用基于这种协议制作了webservice这种服务。
gsoap概念:是一种能够把C/C++语言的接口转换成基于soap协议的webservice服务的工具。
步骤1:首先下载gsoap的工具。这里下载了gsoap_2.7.10.解压之后,在里面会发现两个exe可执行文件。soapcpp2.exe和wsdl2h.exe。另外还有两个比较重要的源文件:stdsoap2.h和stdsoap2.cpp。为了使用方便,可以把两个exe可执行文件所在的问价设置到环境变量中。
步骤2:新建一个.h文件用来声明所要暴露给外界的接口。这里以一些运算的接口来作为例子。头文件命名为calculator.h。注意虽然是.h文件,但是千万不能有注释。内容如下:
//gsoap ns service name: calculate
//gsoap ns service style: rpc
//gsoap ns service namespace: http://10.64.57.10/calculate.wsdl
//gsoap ns service location: http://10.64.57.10/calculate.cgi
//gsoap ns schema namespace: urn:calculate
int ns__add(int num1, int num2, int* result );
int ns__sub(int num1, int num2, int* result );
int ns__mult( int num1, int num2, int *result);
int ns__divid( int num1, int num2, int *result);
步骤3:使用soapcpp2.exe工具来生成相关的源文件。使用方法是soapcpp2 calculator.h
这里没有带选项,那么生成的是最全的源文件,包括客户端的和服务器的以及其它一些文件。当然也可以使用选项来生成需要的文件,可以再命令行下面使用soapcpp2 –help来参看选项。
另外在gsoap的安装目录下面有两个重要的源文件:stdsoap2.h和stdsoap2.cpp。在编译工程的时候要使用。这里罗列一下生成的重要源文件(包括stdsoap2.h和stdsoap2.cpp),它们是:stdsoap2.h、soapStub.h、soapH.h、calculate.nsmap以及stdsoap2.cpp、soapC.cpp、soapClient.cpp soapServer.cpp。
stdsoap2.h和stdsoap2.cpp是gsoap最底层的实现,它们和每次编写的头文件(calculate.h)无关。soapStub.h(存根头文件),包含了stdsoap2.h,里面声明了定义在calculate.h中的接口,包括客户端的和服务器的。还有一个最上层的soapH.h包含了soapStub.h。它是具体实现的声明。和soapC.cpp对应。calculate.nsmap这个文件应该是soap协议相关的一个东西。在编程序时include “calculate.nsmap”就可以了。再来介绍一下源文件,stdsoap2.cpp是和stdsoap2.h对应的,实现最底层的运作,和calculate.h无关;soapC.cpp是和soapH.h对应的,是接口相关的。但是它提供的仅仅是服务器和客户端共有部分的实现。soapClient.cpp也是接口相关的,它里面实现的仅仅是客户端需要的代码。同理soapServer.cpp也是只和服务器相关的。
好了介绍完了这些源文件,再来梳理一遍。工程中需要的头文件有stdsoap2.h、soapStub.h、soapH.h、calculate.nsmap。但是因为soapStub.h包含了stdsoap2.h;soapH.h包含了soapStub.h,所以工程中只需要包含soapH.h、calculate.nsmap。对于源文件,若是服务器端,需要stdsoap2.cpp、soapC.cpp和soapServer.cpp;客户端则需要stdsoap2.cpp、soapC.cpp和soapClient.cpp;
为了偷懒,编写了一个脚本来实现调用soapcpp2和拷贝stdsoap2.h、stdsoap2.cpp的功能。名字为AutoGsoap.bat。使用方法是把它放到你定义的calculate.h目录下,然后运行,输入你要操作的头文件名字即可。脚本内容如下:
@echo off
echo "请输入头文件"
set /p headfile=
soapcpp2.exe %headfile%
copy D:\MyApp\ProfessionalApp\gsoap_2.7.10\gsoap-2.7\gsoap\stdsoap2.h .\
copy D:\MyApp\ProfessionalApp\gsoap_2.7.10\gsoap-2.7\gsoap\stdsoap2.cpp .\
pause
步骤4:建立服务器端的工程。这里以CalculateServer为例 。新建工程后,添加“wsock32.lib”库,gsoap需要用到sock套接字通信。
然后编写服务器端的主函数,名称为main.cpp,内容如下:
#include "soapH.h"
#include "calculate.nsmap"
#include "stdio.h"
int main( int argc, char *argv[])
{
struct soap *CalculateSoap = soap_new(); //创建一个soap
int iSocket_master = soap_bind(CalculateSoap, NULL, 10000, 100); //绑定到相应的IP地址和端口()NULL指本机,
//10000为端口号,最后一个参数不重要。
if (iSocket_master< 0) //绑定出错
{
soap_print_fault(CalculateSoap, stderr);
exit(-1);
}
printf("SoapBind success,the master socket number is:%d\n",iSocket_master); //绑定成功返回监听套接字
while(1)
{
int iSocket_slaver = soap_accept(CalculateSoap);
if (iSocket_slaver < 0)
{
soap_print_fault(CalculateSoap, stderr);
exit(-2);
}
printf("Get a new connection,the slaver socket number is:%d\n",iSocket_slaver); //绑定成功返回监听套接字
soap_serve(CalculateSoap);
soap_end(CalculateSoap);
}
soap_done(CalculateSoap);
free(CalculateSoap);
return 0;
}
/*加法的具体实现*/
int ns__add(struct soap *soap, int num1, int num2, int* result )
{
if (NULL == result)
{
printf("Error:The third argument should not be NULL!\n");
return SOAP_ERR;
}
else
{
(*result) = num1 + num2;
return SOAP_OK;
}
return SOAP_OK;
}
/*减法的具体实现*/
int ns__sub(struct soap *soap,int num1, int num2, int* result )
{
if (NULL == result)
{
printf("Error:The third argument should not be NULL!\n");
return SOAP_ERR;
}
else
{
(*result) = num1 - num2;
return SOAP_OK;
}
return SOAP_OK;
}
/*乘法的具体实现*/
int ns__mult(struct soap *soap, int num1, int num2, int *result)
{
if (NULL == result)
{
printf("Error:The third argument should not be NULL!\n");
return SOAP_ERR;
}
else
{
(*result) = num1 * num2;
return SOAP_OK;
}
return SOAP_OK;
}
/*除法的具体实现*/
int ns__divid(struct soap *soap, int num1, int num2, int *result)
{
if (NULL == result || 0 == num2)
{
printf("Error:The second argument is 0 or The third argument is NULL!\n");
return SOAP_ERR;
}
else
{
(*result) = num1 / num2;
return SOAP_OK;
}
return SOAP_OK;
}
编译,运行服务器端程序,从IE浏览器访问10.64.57.10:10000 (该服务器运行的本机IP地址为10.64.57.10)如能看到如下信息,说明服务器运行正常:
步骤5:新建客户端程序进行验证。新建工程如下,同样要包含wsock32.lib。
/*客户端主程序*/
#include "soapH.h"
#include "calculate.nsmap"
#include "stdio.h"
int main( int argc, char *argv[])
{
printf("The Client is runing...\n");
int num1 = 110;
int num2 = 11;
int result = 0;
struct soap *CalculateSoap = soap_new();
//soap_init(CalculateSoap);
char * server_addr = "http://10.64.57.10:10000";
int iRet = soap_call_ns__add(CalculateSoap,server_addr,"",num1,num2,&result);
if ( iRet == SOAP_ERR)
{
printf("Error while calling the soap_call_ns__add");
}
else
{
printf("Calling the soap_call_ns__add success。\n");
printf("%d + %d = %d\n",num1,num2,result);
}
iRet = soap_call_ns__sub(CalculateSoap,server_addr,"",num1,num2,&result);
if ( iRet == SOAP_ERR)
{
printf("Error while calling the soap_call_ns__sub");
}
else
{
printf("Calling the soap_call_ns__sub success。\n");
printf("%d - %d = %d\n",num1,num2,result);
}
iRet = soap_call_ns__mult(CalculateSoap,server_addr,"",num1,num2,&result);
if ( iRet == SOAP_ERR)
{
printf("Error while calling the soap_call_ns__mult");
}
else
{
printf("Calling the soap_call_ns__mult success。\n");
printf("%d * %d = %d\n",num1,num2,result);
}
iRet = soap_call_ns__divid(CalculateSoap,server_addr,"",num1,num2,&result);
if ( iRet == SOAP_ERR)
{
printf("Error while calling the soap_call_ns__divid");
}
else
{
printf("Calling the soap_call_ns__divid success。\n");
printf("%d / %d = %d\n",num1,num2,result);
}
soap_end(CalculateSoap);
soap_done(CalculateSoap);
free(CalculateSoap);
return 0;
}
开启服务器之后,运行客户端程序,打印信息如下:
此时服务器端也检测到了四个连接:
soap: 非托管c++ 客户端的开发(gsoap)
webservice的服务器是c#写的,现在要用c++访问此webservice.有以下方法:
1,托管c++,缺点猜想,部署项目的时候需要包含.net库,讨厌这种拖泥带水的。
2,用c#访问webservice,重新包装一个接口,生成dll, 供c++使用;缺点,还是需要公共语言运行库支持,也即托管c++
3, 用gsoap
开发步骤:
1,安装gsoap win32版本。官网是http://gsoap2.sourceforge.net/
2,利用gsoap的bin目录下的两个可执行文件+webservice的wsdl生成一些文,c++直接调用就okay了。
有两种方法:利用soap,利用代理类
我这里用的soap。
wsdl2h常用选项
soapcpp2.exe 的使用:
wsdl2h -c -o NxService.h http://192.168.1.104/SendService/SendMessageService.svc?wsdl
soapcpp2 -c -C NxService.h
3,新建项目win32 console。
#include <WinSock2.h>
#pragma comment(lib, "wsock32.lib")
#include "utility.h"
#include "soapH.h"
#include <string>
using namespace std;
int main()
{
struct soap clientSoap;
soap_init(&clientSoap);
soap_set_mode(&clientSoap, SOAP_C_UTFSTRING);
_ns1__SendMsgSvc request;
_ns1__SendMsgSvcResponse response;
//request
xsd__boolean bIsLimiteRtxMsgLen=xsd__boolean__true_;
entity.IsLimitRtxMsgLen = &bIsLimiteRtxMsgLen;
//response
xsd__boolean SendMsgSvcResult;
response.SendMsgSvcResult=&SendMsgSvcResult;
int bret=soap_call___ns1__SendMsgSvc(&clientSoap, NULL, NULL, &request, &response);
if(bret!=SOAP_OK)
{
//err
soap_print_fault(&clientSoap, stderr);
}
return 0;
}
//不直接用BasicHttpBinding_USCOREISendMessageService.nsmap, 因为服务器wsdl文件生成的soap1.2,用gsoap生成1.2,发送的http的contenttype不支持。
//手动修改namespace, 生成soap1.1.
struct Namespace namespaces[] =
{
{"SOAP-ENV", "http://schemas.xmlsoap.org/soap/envelope/", NULL, NULL},
{"SOAP-ENC", "http://schemas.xmlsoap.org/soap/encoding/", NULL, NULL},
{"xsi", "http://www.w3.org/2001/XMLSchema-instance", NULL, NULL},
{"xsd", "http://www.w3.org/2001/XMLSchema", NULL, NULL},
//{"SOAP-ENV", "http://www.w3.org/2003/05/soap-envelope", "http://www.w3.org/2003/05/soap-envelope", NULL},
//{"SOAP-ENC", "http://www.w3.org/2003/05/soap-encoding", "http://www.w3.org/2003/05/soap-encoding", NULL},
//{"xsi", "http://www.w3.org/2001/XMLSchema-instance", "http://www.w3.org/*/XMLSchema-instance", NULL},
//{"xsd", "http://www.w3.org/2001/XMLSchema", "http://www.w3.org/*/XMLSchema", NULL},
{"ns4", "http://schemas.datacontract.org/2004/07/SnailGame.Toolkits.MessageSendService", NULL, NULL},
{"ns3", "http://schemas.microsoft.com/2003/10/Serialization/", NULL, NULL},
{"ns1", "http://tempuri.org/", NULL, NULL},
{NULL, NULL, NULL, NULL}
};
总结:刚开始用老是返回错误415, 直接include的nsmap的命名空间。错误是content type错误。花了一天时间,寻找gsoap 415的错误,没找到。
后来看了下soap协议,基于http协议的,用截包工具截获一个http/xml包,一看, content type错误。服务器wsdl文件生成的soap1.2, 而服务器只支持soap1.1, 把命名空间改了下就好了。
还有中文的话,先转成utf8格式的。
综上,用别人的库之前,先了解基本的协议。gsoap先了解soap协议,libcurl先了解ftp协议。磨刀不误砍柴工。
服务端客户端soap协议不统一:http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/1ff173ee-c371-45b1-986b-fbb67ea94de8/
soap: http://www.w3school.com.cn/soap/soap_httpbinding.asp
Reference:
标准C/C++程序通过gSOAP调用WebService