前言
Web Service基于可编程的平台是独立的、低耦合的web可以使用开放的应用程序XML(标准通用标记语言下的子集)标准描述、发布、发现、协调和配置这些应用程序,以开发分布式交互操作的应用程序。(来自百度百科全书)。 优点:跨平台、跨语言调用。 因为项目需要调用webservice网上大部分接口都是java调用,c 部分很少,或者系统不够。所以我决定写一个比较系统的调用过程。
步骤
第一步:生成头文件
webservice接口通常有外部接口文档。http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL 问号后面的参数表示WSDL文一个文档XML不懂文档配置也没关系。接下来,我们将通过这个文档生成它c 头文件。
(1) 下载gsoap工具
gsop工具下载地址 下载后解压,进入gsoap\bin\win目录下有两份文件wsdl2h.exe和soapcpp2.exe。wsdl2h.exe用于生成头文件。光生成头文件是不够的。此时应使用它soapcpp2.exe生成对应的c 可用于项目用文件结构(第二步介绍)。先来看看如何生成头文件。 在gsoap\bin\win32目录下打开cmd命令。
wsdl2h.exe -o head.h http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL
执行完成后,可以看到当前目录下多了一个head.h头文件。可以打开看,里面有一些接口函数。
方法一:以后调用时在代码中加一句话(具体加在哪里,以后介绍):
soap_set_mode(&m_soap, SOAP_C_UTFSTRING);
方法二:默认使用根目录typemap.dat编译成窄字符。此时,我们不适用它,而是在当前目录下创建一个新的mytypemap.dat。内容如下:xsd__string = | std::wstring | wchar_t* 。然后重新执行cmd命令。
wdsl2h.exe -o head.h -t mytypemap.dat http://www.webxml.com.cn/WebServices/WeatherWebService.asmx?WSDL
重生后可以发现head.h中文字符串的类型都变成了wchar_t * 或者wstring类型。
第二步:生成可调用API
在当前目录下执行cmd命令:
soapcpp2.exe -C -x -I ..\..\import head.h
其中-C只生成客户端代码。-x表示不生成xml(我们只需要c 代码)-I 是指定import目录。 实施成功后,目前目录中有更多文件:
第三步:导入项目
- 将上图中红色框中的文件添加到您的项目中。还需要添加两个文件。gsoap根目录下的stdsoap2.h和stdsoap2.cpp还需要添加到项目中。
- 添加的三个cpp文件右击->属性->所有配置->c/c ±>选择不使用预编译头的预编译头。如下所示。
配置完成后,您可以开始编写代码。
第四步:编写代码
新建一个main.cpp,引入头文件
#include "WeatherWebServiceSoap.nsmap" #include "soap.h"
当我在项目中引用这两个头文件时,我疯狂地疯狂的错误报告。仔细检查,都是冲定义,因为头文件 WeatherWebServiceSoap.nsmap在项目中命名空间和原始空间socket解决库冲突的办法是#include "WeatherWebServiceSoap.nsmap"
写在前面,如果使用预编译头,最好写在前面 stdafx.h
文件中。第一份文件引入后,再次编译,不报错,继续。 在soapClient.cpp服务的所有接口都可以看到。函数名是soap_call __ns1_XXX形式。 首先创建soap对象和初始化:
struct soap m_soap; //SOAP初始化 soap_init(&m_soap); soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);
其中soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);
刚开始说的是适应中文字符。如果一开始就用。wstring或者wchar_t * 可以忽略。然后定义。reqXml调用接口函数的字符串。xml字符串中的双引号需要转换。
char* _reqXml = "<root>...</root>"; char* _Return;
soap_call_ns1__XXX(&m_soap, NULL, NULL, G2U(_reqXml), _Return);
string ret = U2G(_Return);
由于上面代码中使用的是窄字符作为参数,即char *,需要转换成UTF-8格式。具体函数如下:
char* U2G(const char* utf8)
{
int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wstr, len);
len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wstr, -1, str, len, NULL, NULL);
if (wstr) delete[] wstr;
return str;
}
char* G2U(const char* gb2312)
{
int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
if (wstr) delete[] wstr;
return str;
}
此时编译应该可以通过了。但是,现实是残酷的。返回的_Return值为NULL,说明出错了。我的地址是https的,如果,我直接用http请求的话会返回301,查找资料说301表示重定向,意思是这里不能像浏览器那样地址栏输入http,浏览器会帮你重定向到https。此时还是得改成https。再次编译。 额~还是报错。。。错误码可以在调试期间,查看m_soap结构体中error字段的值,如果一直是0,说明没问题。具体错误代码可以上网查一下。 改成https再调试可以看到m_soap->error的值是30,查看文档,发现30代表没有进行SSL安全认证。好吧~ 在soap_init(&m_soap)后面添加如下代码:
soap_ssl_init();
if (soap_ssl_server_context(&m_soap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL, NULL, NULL)) {
soap_print_fault(&m_soap, stderr);
exit(-1);
}
其中soap_ssl_server_context参数中如果有SSL证书,和密码就填进去。没有全NULL就完事儿。 再次编译,还是无法通过~检查代码,发现在stdsoap2.h
头文件中WITH_OPENSSL没有定义。下面是灰色的。 解决方法:右击项目属性->C/C+±>预处理器->预处理器定义中添加WITH_OPENSSL 添加之后,发现灰色没有了。 编译之后再次报错~~~~好吧,深不见底,一一解决吧 ,错误信息是,报错是soap_ssl_server_context
函数无法解析的外部符号。啊,很明显,该函数的实现没有定义。忘记引入openssl库了。下载openssl库,解压,将lib文件夹解压到项目中。 接着在vs中右击项目->属性->配置属性->VC++目录中包含目录和库目录添加进去,如下图。 好了,万事大吉了吧~再次编译。。 OK成功啦。
发现一个bug,当再次调用的时候,会产生一个错误: Error 30 fault: SOAP-ENV:Server [no subcode] “SSL error” Detail: Can’t setup context 网上查了一下资料,没有相关错误,最后还是google到了。。 https://communities.vmware.com/thread/260000 如果无法访问,我把大致原因和解决方案粘贴出来:
The problem was not in the module (using web service sdk) but in the other module. The problem was in the initialization and cleanup of curl library. Rearranging the initialization and cleanup calls from module level to application level fixed the issue. Previously when the faulty module was unloaded, it releases all the libraries and therefore, VMware module was not able to setup the ssl context.
大致意思是在初始化之前就释放了资源,他通过重新调整初始化和释放资源的顺序就解决了。但是在我的代码中,我发现,并没有对openssl library进行初始化。网上代码,甚至是gsopa的ssl sample里都没有相关说明,不知道为什么。具体分析如下。 打开stdsoap2.cpp直接CTRL+F搜索报Can't setup context
的位置,如下图,可以看到在调用SSL_CTX_NEW(SSLV23_method())
函数的时候出错了,调试发现,第二次调用的返回NULL,这当然不行。 解决方法很简单,在调用soap_ssl_client_context()
之前对openssl library进行初始化SSL_library_init()
。问题解决。
部分源码
/******************************************************************************\ * * OpenSSL * \******************************************************************************/
#ifdef WITH_OPENSSL
struct CRYPTO_dynlock_value
{
MUTEX_TYPE mutex;
};
static MUTEX_TYPE *mutex_buf = NULL;
static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
{
struct CRYPTO_dynlock_value *value;
(void)file; (void)line;
value = (struct CRYPTO_dynlock_value*)malloc(sizeof(struct CRYPTO_dynlock_value));
if (value)
MUTEX_SETUP(value->mutex);
return value;
}
static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
{
(void)file; (void)line;
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(l->mutex);
else
MUTEX_UNLOCK(l->mutex);
}
static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
{
(void)file; (void)line;
MUTEX_CLEANUP(l->mutex);
free(l);
}
static void locking_function(int mode, int n, const char *file, int line)
{
(void)file; (void)line;
if (mode & CRYPTO_LOCK)
MUTEX_LOCK(mutex_buf[n]);
else
MUTEX_UNLOCK(mutex_buf[n]);
}
static unsigned long id_function()
{
return (unsigned long)THREAD_ID;
}
int CRYPTO_thread_setup()
{
int i;
mutex_buf = (MUTEX_TYPE*)malloc(CRYPTO_num_locks() * sizeof(MUTEX_TYPE));
if (!mutex_buf)
return SOAP_EOM;
for (i = 0; i < CRYPTO_num_locks(); i++)
MUTEX_SETUP(mutex_buf[i]);
CRYPTO_set_id_callback(id_function);
CRYPTO_set_locking_callback(locking_function);
CRYPTO_set_dynlock_create_callback(dyn_create_function);
CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
return SOAP_OK;
}
void CRYPTO_thread_cleanup()
{
int i;
if (!mutex_buf)
return;
CRYPTO_set_id_callback(NULL);
CRYPTO_set_locking_callback(NULL);
CRYPTO_set_dynlock_create_callback(NULL);
CRYPTO_set_dynlock_lock_callback(NULL);
CRYPTO_set_dynlock_destroy_callback(NULL);
for (i = 0; i < CRYPTO_num_locks(); i++)
MUTEX_CLEANUP(mutex_buf[i]);
free(mutex_buf);
mutex_buf = NULL;
}
#else
/* OpenSSL not used, e.g. GNUTLS is used */
int CRYPTO_thread_setup()
{
return SOAP_OK;
}
void CRYPTO_thread_cleanup()
{
}
#endif
int main(){
struct soap m_oSoap;
SSL_library_init();
/* set up lSSL ocks */
if (CRYPTO_thread_setup())
{
LOG(LogLevel::INFO_OPT, STRINGIZE(__FILE__), STRINGIZE(__LINE__), "Cannot setup thread mutex for OpenSSL\n");
return;
}
/* Init SSL (can skip or call multiple times, engien inits automatically) */
soap_init(&m_oSoap);
soap_set_mode(&m_oSoap, SOAP_C_UTFSTRING);//设置中文
if (soap_ssl_client_context(&m_oSoap, SOAP_SSL_NO_AUTHENTICATION, NULL, NULL, NULL, NULL, NULL))
{
soap_print_fault(&m_oSoap, stderr);
return;
}
m_oSoap.connect_timeout = 60; /* try to connect for 1 minute */
m_oSoap.send_timeout = m_oSoap.recv_timeout = 30; /* if I/O stalls, then timeout after 30 seconds */
char* _Return;
string _reqXml = "<root>......</root>"
soap_call_ns1__XXXX(&m_oSoap, NULL, NULL, G2U(_reqXml.c_str()), _Return)
//close
soap_destroy(&m_oSoap);
soap_end(&m_oSoap);
soap_done(&m_oSoap);
CRYPTO_thread_cleanup();
}
总结
问题还是比较多的,需要一步步解决。