C++语言实现的WebService开发库—gsoap应用实例

(接上篇,本文给出gSOAP的实例,gSOAP的版本为2.8.8)

 

四、应用实例

上节介绍了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>
其中

  • 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)
其中 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前。

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);
}	  
以上代码中,

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; 
}


3. 其他特性

gsoap有许多功能,例如支持SSL,压缩,JSON等,请参阅其文档。

你可能感兴趣的:(C++语言实现的WebService开发库—gsoap应用实例)