转载自:http://blog.youkuaiyun.com/guxch/article/details/8490763
四、应用实例
上节介绍了gSOAP的应用有两种,大部分介绍gSOAP的文章,都以第一种为主,其实第二种应用包含了第一种,所以文本只介绍第二种应用。
本文的例子中,前提是通讯协议(格式)已定,webservice名已定,我们需要自己编写xml schema及wsdl文件。
1.xml schema
这部分内容不属于本文范围,其实,对xml schema和wsdl编写,最好的办法就是找一个接近的例子,修改一下来满足我们的要求。
2.wsdl
一个wsdl通常由如下部分组成:
- <wsdl:definitions>
- <wsdl:types/>
- <wsdl:message/>
- <wsdl:portType/>
- <wsdl:binding/>
- <wsdl:service/>
- </wsdl:definitions>
<wsdl:definitions>
<wsdl:types/>
<wsdl:message/>
<wsdl:portType/>
<wsdl:binding/>
<wsdl:service/>
</wsdl:definitions>
其中
- types也就是通讯内容的格式(上面的xml schema),一般情况下,所有的类型最终被包含到一个复杂类型中,作为下面message中数据包引用的类型;
- message定义了通讯的数据包(通讯一般是双向的,因此,一个调用过程有两个message定义,一般情况下用<methodname>in和<methodname>out分别表示进入和发出服务器的数据包),该数据包引用了上面types中的最终的复杂类型;
- portType包含了若干operation(对应于代码实现中的method),定义了该operation进出的message;
- binding是对上面operation的网络描述,也就是将operation与某一可以用soap形式进行网络访问的方式“绑定”在一起,binding指明了每个method的访问地址和方式。
- service是对整个web服务的描述(访问地址等)。
wsdl的定义是从小到大,一层一层的,从外部调用的角度来看,却是从大到小,由下往上理解:先有service,再有method(portType中的operation),然后有message,最后才有各种内容类型定义(schema),我们最终操作的起点与终点就是构造/解析这些类型。
3.生成.h
由wsdl生成.h文件,使用的工具是wsdl2h,该工具的各种运行选项,见作者上篇文章。为使代码简洁,我们一般情况下需要采用如下的选项:
- 允许stl类型(不加-s)
- 产生typedef定义(加-y)
- 指定自己的命名空间(加-N –n)
- 指定自己的输出文件名(加–o)
由wsdl2h生成的.h文件其实不是一个标准的C语言头文件,它是一个gsoap能识别的中间文件。
4.生成代码
无论是wsdl生成的.h文件,还是自己写的一个.h文件,如果要生成相关的代码,必须使用soapcpp2,该工具的选项较多,因而生成的代码多样,一般情况,考虑如下选项:
- 如果对自己编写的xml schema比较有把握,可以不用生成例子message文件,否则生成出来查看一下即可(-x)
- 指定代码生成的import目录,在使用stl时需要(-I)
- 生成的实现类到底采用继承struct soap还是包含struct soap,并无特别的差别,视个人使用习惯而定,采用继承时,代码似乎简洁一些(-i)
- 指定输出文件名(-o)
- 指定命名空间(-q)
- 指定文件名前缀(-p)
- 只生产客户端代码(-S)
- 只生产服务器端代码(-C)
5.编译项目
用以上两个工具,会生成若干文件,建议服务器端和客户端分开生成,这样会比较清晰。要编译这个项目,还需要两个gSOAP包中提供的两个固定(我们不要修改)的文件:stdsoap2.h stdsoap2.c/stdsoap2.cpp(一个用于C,一个用于C++),这两个文件提供了通用的webservice实现功能。
如果以上步骤都没有错误,编译成功就是水到渠成的事情,采用VC或gcc都可以成功。
6.编写自己的实现逻辑
服务器端:
在生成的代码中,有一个叫做<servicebindingname>service的类,该类从struct soap继承(-i)或包含一个struct soap(-j),是服务实现的框架类。此类中有一些虚函数,是我们主要关注的东西。
一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成业务逻辑。
客户端:
与服务器端的情况非常类似,在生成的代码中,有一个叫做<servicebindingname>proxy的类,一般情况下,应继承该类,在继承类中,实现那几个service方法的虚函数(其它几个虚函数的作用见下节),完成调用远程方法的业务逻辑。
服务器端与客户端的接口,传入的是request的xml,传出的response的xml,客户端还有一种接口是将service方法调用重定位到其它的服务器。我们在继承类中的实现主要应该是xml的解析(对request)和xml的生成(对response),webservice业务的真正逻辑应单独放到其它文件中。
五、gsoap应用的一些讨论
1. 内存管理
C/C++最大的麻烦,也是最大的优点是它要求用户自己管理内存。我们在实现web service方式时,同样需要考虑内存的分配与释放。
分配内存有两类:
- 分配n个字节,采用
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)
#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; }
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前。
2. 服务器端侦听的实现
我们除了要完成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);
- }
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;
- }
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;
}
3. 其他特性
gsoap有许多功能,例如支持SSL,压缩,JSON等,请参阅其文档。