gSOAP 初体验

由于工作调动关系,需要了解 gSOAP 的使用,写个文章记录一下学习的心得,免得以后忘记。

安装

由于本人使用的是 Mac OS 系统,故以 Mac OS 为例说明如何安装 gSOAP。

1)下载 gSOAP

可以在 https://sourceforge.net/projects/gsoap2 下载最新版本的 gSOAP。

2)安装 flex, bison, openssl

可以使用brew install进行安装:

1
brew install flex bison openssl

3)编译安装 gSOAP

解压上面下载的 gSOAP,然后执行下面的命令:

1
2
3
4
cd gsoap-2.8
./configure --with-openssl=/usr/local/opt/openssl
make
sudo make install

安装完成,会出现以下的提示:

安装中如果出现 fatal error: 'openssl/bio.h' file not found 的错误,可以通过尝试重新安装 openssl 和使用最新的 gsoap-2.8 版本的方法来解决,具体解决办法也可以 google 一下。

其他平台的安装教程可以参考官方文档:https://www.genivia.com/downloads.html

gSOAP 工具

gSOAP 提供了两个工具来方便开发人员使用 C/C++ 语言快速开发Web 服务应用,通过 gSOAP 提供的这两个工具,开发人员可以快速生成服务端与客户端代码框架,接下来开发人员只需要实现具体的接口函数即可。

wsdl2h

wsdl2h 工具根据 WSDL 文件生成 C/C++ .h 头文件。
WSDL(Web Service Description Language)即 Web 服务描述语言,它使用 XML 来对 Web 服务进行描述。
wsdl2h 的用法:

1
wsdl2h -o 头文件名 WSDL文件名或URL

例如:

1
wsdl2h -o calc.h http://www.genivia.com/calc.wsdl

wsdl2h 根据 URL 指定的 WSDL 生成calc.h头文件。calc.h对 Web 服务接口进行定义。

wsdl2h 支持额外的参数:

  • -s 生成的头文件不使用 STL
  • -o 文件名,指定输出头文件的名称
  • -c 产生纯 C 代码,否则是 C++ 代码
  • -t 文件名,指定 type map 文件,默认是 typemap.dat

soapcpp2

soapcpp2 工具则从上面生成的头文件生成 SOAP 服务端和客户端框架代码。例如对于上面的cacl.h,使用 soapcpp2 命令:

1
soapcpp2 -i -Iimport calc.h

soapcpp2 也支持额外的参数:

  • -i 生成 C++ 包装类,客户端为 xxxProxy.h(.cpp),服务端为xxxService.h(.cpp)
  • -I 指定 import 的路径,比如需要引入stlvector.h文件来支持 STL vector 的序列化
  • -C 仅生成客户端代码
  • -S 仅生成服务端代码
  • -c 产生纯 C 代码,否则是 C++ 代码
  • -x 不要产生 XML 示例文件
  • -L 不要产生soapClientLib.csoapServerLib.c文件

例子

gSOAP 中包含了大量的例子以便让开发人员快速了解如何使用 gSOAP 开发 Web 服务。
我们以 gSOAP 的 samples 目录下的 calc++ 的代码为例,说明如何使用 gSOAP 来编写客户端和的服务端代码。

calc++ 目录已经包含了 calc.h 头文件,这个头文件跟上面我们使用 wsdl2h 生成的 calc.h 头文件并不完全相同,为了实验的方便,我们使用 calc++ 目录的calc.h 头文件进行实验。

calc.h头文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//gsoap ns service method: add Sums two values
int ns__add(double a, double b, double *result);

//gsoap ns service method: sub Subtracts two values
int ns__sub(double a, double b, double *result);

//gsoap ns service method: mul Multiplies two values
int ns__mul(double a, double b, double *result);

//gsoap ns service method: div Divides two values
int ns__div(double a, double b, double *result);

//gsoap ns service method: pow Raises a to b
int ns__pow(double a, double b, double *result);

然后,我们使用 soapcpp2 工具来生成客户端和服务端的框架代码:

1
soapcpp2 -i -Iimport calc.h

客户端代码

calcclient.c++ 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "soapcalcProxy.h"
#include "calc.nsmap"

const char server[] = "http://127.0.0.1:8080";

int main(int argc, char **argv)
{ if (argc < 4)
{ fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");
exit(0);
}
double a, b, result;
a = strtod(argv[2], NULL);
b = strtod(argv[3], NULL);
calcProxy calc;
calc.soap_endpoint = server;
switch (*argv[1])
{ case 'a':
calc.add(a, b, &result);
break;
case 's':
calc.sub(a, b, &result);
break;
case 'm':
calc.mul(a, b, &result);
break;
case 'd':
calc.div(a, b, &result);
break;
case 'p':
calc.pow(a, b, &result);
break;
default:
fprintf(stderr, "Unknown command\n");
exit(0);
}
if (calc.error)
calc.soap_stream_fault(std::cerr);
else
printf("result = %g\n", result);
return 0;
}

由于代码使用 STL,为了顺利编译通过,需要将 gSOAP 中的stdsoap2.cppstdsoap2.h文件拷贝到客户端和服务端代码所在的目录。
改写好客户端代码后,使用 g++ 进行编译:

1
g++ -o calcclient calcclient.cpp soapC.cpp soapcalcProxy.cpp stdsoap2.cpp

编译顺利通过。

服务端代码

calcserver.cpp代码如下,其中可以指定服务端的端口号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include "soapcalcService.h"
#include "calc.nsmap"

int main(int argc, char **argv)
{ calcService calc;
if (argc < 2)
calc.serve(); /* serve as CGI application */
else
{ int port = atoi(argv[1]);
if (!port)
{ fprintf(stderr, "Usage: calcserver++ <port>\n");
exit(0);
}
/* run iterative server on port until fatal error */
if (calc.run(port))
{ calc.soap_stream_fault(std::cerr);
exit(-1);
}
}
return 0;
}

int calcService::add(double a, double b, double *result)
{ *result = a + b;
return SOAP_OK;
}

int calcService::sub(double a, double b, double *result)
{ *result = a - b;
return SOAP_OK;
}

int calcService::mul(double a, double b, double *result)
{ *result = a * b;
return SOAP_OK;
}

int calcService::div(double a, double b, double *result)
{ if (b)
*result = a / b;
else
{ char *s = (char*)soap_malloc(this, 1024);
(SOAP_SNPRINTF(s, 1024, 100), "<error xmlns=\"http://tempuri.org/\">Can't divide %f by %f</error>", a, b);
return soap_senderfault("Division by zero", s);
}
return SOAP_OK;
}

int calcService::pow(double a, double b, double *result)
{ *result = ::pow(a, b);
if (soap_errno == EDOM) /* soap_errno is like errno, but compatible with Win32 */
{ char *s = (char*)soap_malloc(this, 1024);
(SOAP_SNPRINTF(s, 1024, 100), "<error xmlns=\"http://tempuri.org/\">Can't take power of %f to %f</error>", a, b);
return soap_senderfault("Power function domain error", s);
}
return SOAP_OK;
}

然后使用 g++ 来对服务端代码进行编译:

1
g++ -o calcserver calcserver.cpp soapC.cpp soapcalcService.cpp stdsoap2.cpp

编译同样顺利通过。

测试

运行上面编译好的calcservercalcclient可执行文件
来对 Web 服务进行测试,测试结果如下:

参考资料

  1. https://www.cs.fsu.edu/~engelen/soapdoc2.html
  2. https://www.genivia.com/downloads.html
  3. https://www.genivia.com/dev.html
  4. http://blog.csdn.net/yangjun1115/article/details/29360389
  5. https://www.cs.fsu.edu/~engelen/calc.html
  6. http://commandos.blog.51cto.com/154976/130652
  7. http://www.cppblog.com/qiujian5628/archive/2008/10/11/54019.html
  8. http://blog.sina.com.cn/s/blog_5ee9235c0100de3g.html