(接上篇,本文给出gSOAP的实例,gSOAP的版本为2.8.8)
上节介绍了gSOAP的应用有两种,大部分介绍gSOAP的文章,都以第一种为主,其实第二种应用包含了第一种,所以文本只介绍第二种应用。
本文的例子中,前提是通讯协议(格式)已定,webservice名已定,我们需要自己编写xml schema及wsdl文件。
这部分内容不属于本文范围,其实,对xml schema和wsdl编写,最好的办法就是找一个接近的例子,修改一下来满足我们的要求。
一个wsdl通常由如下部分组成:
<wsdl:definitions> <wsdl:types/> <wsdl:message/> <wsdl:portType/> <wsdl:binding/> <wsdl:service/> </wsdl:definitions>其中
wsdl的定义是从小到大,一层一层的,从外部调用的角度来看,却是从大到小,由下往上理解:先有service,再有method(portType中的operation),然后有message,最后才有各种内容类型定义(schema),我们最终操作的起点与终点就是构造/解析这些类型。
由wsdl生成.h文件,使用的工具是wsdl2h,该工具的各种运行选项,见作者上篇文章。为使代码简洁,我们一般情况下需要采用如下的选项:
由wsdl2h生成的.h文件其实不是一个标准的C语言头文件,它是一个gsoap能识别的中间文件。
无论是wsdl生成的.h文件,还是自己写的一个.h文件,如果要生成相关的代码,必须使用soapcpp2,该工具的选项较多,因而生成的代码多样,一般情况,考虑如下选项:
用以上两个工具,会生成若干文件,建议服务器端和客户端分开生成,这样会比较清晰。要编译这个项目,还需要两个gSOAP包中提供的两个固定(我们不要修改)的文件:stdsoap2.h stdsoap2.c/stdsoap2.cpp(一个用于C,一个用于C++),这两个文件提供了通用的webservice实现功能。
如果以上步骤都没有错误,编译成功就是水到渠成的事情,采用VC或gcc都可以成功。
服务器端:
在生成的代码中,有一个叫做<servicebindingname>service的类,该类从struct soap继承(-i)或包含一个struct soap(-j),是服务实现的框架类。此类中有一些虚函数,是我们主要关注的东西。
一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成业务逻辑。
客户端:
与服务器端的情况非常类似,在生成的代码中,有一个叫做<servicebindingname>proxy的类,一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成调用远程方法的业务逻辑。
服务器端与客户端的接口,传入的是request的xml,传出的response的xml,客户端还有一种接口是将service方法调用重定位到其它的服务器。我们在继承类中的实现主要应该是xml的解析(对request)和xml的生成(对response),webservice业务的真正逻辑应单独放到其它文件中。
C/C++最大的麻烦,也是最大的优点是它要求用户自己管理内存。我们在实现web service方式时,同样需要考虑内存的分配与释放。
分配内存有两类:void*soap_malloc(struct soap *soap, size_t n)
Class*soap_new_Class(struct soap *soap) 一个类
Class*soap_new_Class(struct soap *soap, int n) n个类
这里的类是通讯xml中定义的元素,在response构造时,必然要创建若干此类元素。为简化类的创建,可定义如下宏:
#defineNEW_ELEMENT(classtype) soap_new_##classtype(GetSoapStruct(),-1) #defineNEW_ELEMENT_X(classtype,n) soap_new_##classtype(GetSoapStruct(),n)其中 GetSoapSturct()是返回继承的或包含的struct soap结构,对继承方式的代码,它的定义如下:
struct soap *GetSoapStruct() { return(struct soap*)this; }在我们的Web方法实现中,可以随意使用上面的new方法,在每次web方法完结后,调用soap_destroy(struct soap *soap) ,它会为我们清除掉这部分内存。
gsoap中有若干释放内存的方法,几个有用的函数(还有其它的,忽略)及其说明如下:
Function Call |
Description |
soap_destroy(struct soap *soap) |
释放所有动态分配的C++类,必须在soap_end()之前调用。 |
soap_end(struct soap *soap) |
释放所有存储临时数据和反序列化数据中除类之外的空间(soap_malloc的数据也属于反序列化数据)。 |
soap_done(struct soap *soap) |
Detach soap结构(即初始化化soap结构) |
soap_free(struct soap *soap) |
Detach 且释放soap结构 |
上表中,动态分配的C++类,指上面用"soap_new"分配的类;临时数据是指那些在序列化/反序列化过程中创建的例如hash表等用来帮助解析、跟踪xml的数据;反序列化数据是指在接收soap过程中产生的用malloc和new分配空间存储的数据。在gsoap中,纯数据空间与类空间管理不同,采用两个方法,可以保留soap的反序列化数据(这时你需要自己释放)。
前两个函数常用于每个调用完成后,后两个函数常用于不需要这个service前。
我们除了要完成webservice方法的具体实现外,还必须知道如何调用这些方法,在客户端要实现webservice方法的调用,在服务器端要实现webservice的侦听。客户端远程方法调用的实现较简单,直接调用即可,服务器端就比麻烦一些,好在gsoap的文档写得比较好,各方面的内容都已涉及,所以实现的细节及相关说明,最好看看其文档,这里大致介绍一下。
服务器端的的侦听,有单线程实现和多线程实现。
单线程实现的代码如下:
int webservice_single_thread(int port) { CWebService myservice; soap_set_imode(&myservice, SOAP_XML_TREE); myservice.accept_timeout = 5; printf("started to binding...\n"); if (!soap_valid_socket(myservice.bind(NULL, port, 100))) return myservice.error; printf("started to serve...\n"); unsigned int seq=0; do { if (!soap_valid_socket(myservice.accept())) { if( strncmp(myservice.fault->faultstring,"Timeout", 100) == 0) //timeout occur, donot return { continue; } else return myservice.error; //socket错误,退出方法 } printf("%d: accepted connection from IP=%d.%d.%d.%d", seq, (myservice.ip >> 24)&0xFF, (myservice.ip >> 16)&0xFF, (myservice.ip >> 8)&0xFF, myservice.ip&0xFF); int result = myservice.serve(); if(result != SOAP_OK) { char errmsg[1024]; soap_sprint_fault((struct soap *)&myservice,errmsg,1024); printf(" error occured(%d):%s\n",myservice.error,errmsg); myservice.destroy(); return myservice.error; //web method发生错误,退出方法 } myservice.destroy(); seq++; } while(1); }以上代码中,
SOAP_XML_TREE的设置是针对那些有重复xml节点却没有id属性的,即soap中的xml元素可以重复。
将侦听超时时间设为5秒,如果5秒以内我们没有收到消息,就可以有机会处理一下除侦听外的其他事情(上面代码中什么也没做)。
上面代码中,如果发生错误,将退出程序,如果不需要这种效果,可以修改代码。
如果要实现多线程侦听,思路如下:
accept()到一个客户端后,应将当前的soap保存一个备份,创建线程,在另一个线程使用这个备份soap及调用server()方法,大致的代码:
tsoap = soap_copy(&soap); // make a safe copy if (!tsoap) break; pthread_create(&tid, NULL, (void*(*)(void*))process_request, (void*)tsoap); //线程处理函数 void *process_request(void *soap) { pthread_detach(pthread_self()); soap_serve((struct soap*)soap); soap_destroy((struct soap*)soap); // dealloc C++ data soap_end((struct soap*)soap); // dealloc data and clean up soap_done((struct soap*)soap); // detach soap struct free(soap); return NULL; }