Calculator客户端和服务器示例

概述:
此示例在C中实现了一个简单的SOAP / XML API客户端和服务器。

Web服务规范示例
计算器XML Web服务calc.h在gSOAP soapcpp2工具要处理的文件中指定。该calc.h规范定义了五个计算器方法add,sub,mul,div和pow:

//gsoap ns service name:            calc Simple calculator service described at https://www.genivia.com/dev.html
//gsoap ns service protocol:        SOAP
//gsoap ns service style:           rpc
//gsoap ns service encoding:        encoded
//gsoap ns service namespace:       http://websrv.cs.fsu.edu/~engelen/calc.wsdl
//gsoap ns service location:        http://websrv.cs.fsu.edu/~engelen/calcserver.cgi

//gsoap ns schema namespace:        urn:calc

//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);

此示例calc.h文件是手动定义的。可以通过在一个或多个Web服务的WSDL上运行wsdl2h命令来自动获取具有Web服务规范的这些特殊“头文件”:

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

这是calc.h从指定的WSDL calc.wsdl文件生成的。

在运行wsdl2h之前,我们建议修改typemap.dat以将XML名称空间前缀绑定到wsdl2h将使用的名称空间URI。由于此服务使用URI“urn:calc”,并且我们希望将其绑定到ns前缀,因此我们应将以下行添加到typemap.dat:

ns = "urn:calc"

要从WSDL获取名称空间URI,请在WSDL上运行wsdl2h,然后从生成的头文件中检索它们(它们位于顶部)。然后在添加要与命名空间关联的前缀名称后重新运行wsdl2h命令typemap.dat。

关于Web服务规范语法的一些注释
gSOAP“头文件”中的Web服务规范定义了服务和数据绑定接口。该规范使用带有指令和注释的普通C / C ++语法。

C / C ++类型和成员的XML名称空间限定是通过带有双下划线的C / C ++标识符命名约定来完成的,就像ns__add将前缀绑定ns到add。也可以使用冒号ns:add(ns:用soapcpp2剥离)。
@ 用于声明应该序列化为XML属性的struct和class成员。
数据成员的缺省值可以=直接在struct或class声明中赋值。
XML元素和属性出现约束minOccurs:maxOccurs在结构或类成员声明的末尾声明为表单。
XML元素重复是STL容器或动态数组。动态数组是一对大小字段和数组指针成员,例如$int size; struct ns__employee_record *manages是size指向的长度数组manages。
XML Web服务操作被声明为函数,其中最后一个参数是指向结果值的指针或引用。使用结构或类返回多个值。
这些//gsoap指令用于绑定XML名称空间并定义SOAP Web服务属性:

//gsoap prefix service name:      <WSDLserviceName> <documentationText>
//gsoap prefix service style:     [rpc|document]
//gsoap prefix service encoding:  [literal|encoded]
//gsoap prefix service namespace: <WSDLnamespaceURI>
//gsoap prefix service location:  <WSDLserviceAddressLocationURI>

绑定Web服务操作属性:

//gsoap prefix service method-style:         <methodName> [rpc|document]
//gsoap prefix service method-encoding:      <methodName> [literal|encoded]
//gsoap prefix service method-action:        <methodName> <actionString>
//gsoap prefix service method-documentation: <methodName> <documentation>

要定义类型架构属性:

//gsoap prefix schema namespace:          <schemaNamespaceURI>
//gsoap prefix schema elementForm:        [qualified|unqualified]
//gsoap prefix schema attributeForm:      [qualified|unqualified]
//gsoap prefix schema documentation:      <documentationText>
//gsoap prefix schema type-documentation: <typeName> <documentationText>

这里prefix是C / C ++名称中使用的XML名称空间前缀,例如nsin ns__add。

具有服务和数据绑定接口的Web服务规范由soapcpp2处理以生成服务和数据绑定实现代码,请参阅下面的构建步骤。

实现客户端应用程序
客户端应用程序calcclient.c在命令行运行,并使用命令行参数调用计算器Web服务来添加,减去,乘,除或增加两个浮点数的幂:

#include "soapH.h"
#include "calc.nsmap"

/* the Web service endpoint URL */
const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";

int main(int argc, char **argv)
{
  struct soap *soap = soap_new1(SOAP_XML_INDENT); /* new context */
  double a, b, result;
  if (argc < 4)
  {
    fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num\n");
    exit(1);
  }
  a = strtod(argv[2], NULL);
  b = strtod(argv[3], NULL);
  switch (*argv[1])
  {
    case 'a':
      soap_call_ns__add(soap, server, "", a, b, &result);
      break;
    case 's':
      soap_call_ns__sub(soap, server, "", a, b, &result);
      break;
    case 'm':
      soap_call_ns__mul(soap, server, "", a, b, &result);
      break;
    case 'd':
      soap_call_ns__div(soap, server, "", a, b, &result);
      break;
    case 'p':
      soap_call_ns__pow(soap, server, "", a, b, &result);
      break;
    default:
      fprintf(stderr, "Unknown command\n");
      exit(1);
  }
  if (soap->error)
    soap_print_fault(soap, stderr);
  else
    printf("result = %g\n", result);
  soap_destroy(soap); /* delete deserialized objects */
  soap_end(soap);     /* delete heap and temp data */
  soap_free(soap);    /* we're done with the context */
  return 0;
}

请参阅有关如何使用HTTP代理和HTTPS 的教程。

构建客户端应用程序的步骤
为客户端生成服务和数据绑定接口:

soapcpp2 -c -r -CL calc.h

其中option -c生成C代码,option -r生成报告,option只-CL生成客户端而没有(未使用的)lib文件。

这会生成多个文件,包括:

soapStub.h 没有注释的普通C / C ++头文件语法规范的副本。
soapH.h 声明XML序列化程序。
soapC.c 实现XML序列化程序。
soapClient.c 实现客户端XML服务API。
calc.nsmap XML命名空间绑定表到#include
soapReadme.md 服务和数据绑定接口详细信息。

要构建示例客户端应用程序,我们还需要stdsoap2.h和stdsoap2.c:

gcc -o calcclient calcclient.c stdsoap2.c soapC.c soapClient.c

实现CGI服务器应用程序
CGI服务器应用程序calcserver.c实现服务操作并接受stdin soap_serve(soap)上的请求,将请求分派给这五个服务操作之一:

#include "soapH.h"
#include "calc.nsmap"

int main()
{
  struct soap *soap = soap_new(); /* new context */
  soap_serve(soap);   /* serve CGI request */
  soap_destroy(soap); /* delete deserialized objects */
  soap_end(soap);     /* delete heap and temp data */
  soap_free(soap);    /* we're done with the context */
  return 0;
} 
/* service operation function implementation */
int ns__add(struct soap *soap, double a, double b, double *result)
{
  *result = a + b;
  return SOAP_OK;
} 
/* service operation function implementation */
int ns__sub(struct soap *soap, double a, double b, double *result)
{
  *result = a - b;
  return SOAP_OK;
} 
/* service operation function implementation */
int ns__mul(struct soap *soap, double a, double b, double *result)
{
  *result = a * b;
  return SOAP_OK;
} 
/* service operation function implementation */
int ns__div(struct soap *soap, double a, double b, double *result)
{
  if (b)
    *result = a / b;
  else
    return soap_sender_fault(soap, "Division by zero", NULL);
  return SOAP_OK;
} 
/* service operation function implementation */
int ns__pow(struct soap *soap, double a, double b, double *result)
{
  *result = pow(a, b);
  if (soap_errno == EDOM) /* soap_errno is like errno, but portable */
    return soap_sender_fault(soap, "Power function domain error", NULL);
  return SOAP_OK;
}

使用下面进一步讨论的构建步骤构建服务应用程序后,将CGI应用程序安装在Web服务器的cgi-bin文件夹中。

实现独立的服务器应用程序
此示例显示了一个独立的迭代服务器,它接受主机端口上的传入请求。该程序与CGI服务相同,除了通过循环中的套接字调度服务请求:

#include "soapH.h"
#include "calc.nsmap"

int port = 8080;

int main()
{
  SOAP_SOCKET m;                               /* master socket */
  struct soap soap = soap_new();               /* new context */
  soap->send_timeout = soap->recv_timeout = 5; /* 5 sec socket idle timeout */
  soap->transfer_timeout = 30;                 /* 30 sec message transfer timeout */
  m = soap_bind(soap, NULL, port), 1);         /* backlog=1 for iterative servers, multi-threaded services should use 100 */
  if (soap_valid_socket(m))
  {
    while (soap_valid_socket(soap_accept(soap)))
    {
      soap_serve(soap);   /* serve request */
      soap_destroy(soap); /* delete deserialized objects */
      soap_end(soap);     /* delete heap and temp data */
    }
  }
  soap_print_fault(soap, stderr);
  soap_destroy(soap); /* delete deserialized objects */
  soap_end(soap);     /* delete heap and temp data */
  soap_free(soap);    /* we're done with the context */
  return 0;
}

注意:只要接受失败,此服务器就会退出。最好是在发生短期连接问题时添加一些逻辑以继续。

为了提高性能,我们应该实现一个多线程服务器,它可以同时处理请求,并且在服务操作变得耗时时不会阻止其他客户端请求:

#include "soapH.h"
#include "calc.nsmap"
#include "plugin/threads.h"

int port = 8080;

void *process_request(void *arg)
{
  struct soap *soap = (struct soap*)arg;
  THREAD_DETACH(THREAD_ID);
  if (soap)
  {
    soap_serve(soap);
    soap_destroy(soap);
    soap_end(soap);
    soap_free(soap);
  }
  return NULL;
}

int main()
{
  SOAP_SOCKET m;                                    /* master socket */
  struct soap *soap = soap_new1(SOAP_IO_KEEPALIVE); /* new context with HTTP keep-alive enabled */
  soap->send_timeout = soap->recv_timeout = 5;      /* 5 sec socket idle timeout */
  soap->transfer_timeout = 30;                      /* 30 sec message transfer timeout */
  m = soap_bind(soap, NULL, port), 100);
  if (soap_valid_socket(m))
  {
    while (soap_valid_socket(soap_accept(soap)))
    {
      THREAD_TYPE tid;
      void *arg = (void*)soap_copy(soap);
      /* use updated THREAD_CREATE from plugin/threads.h https://www.genivia.com/files/threads.zip */
      if (arg)
        while (THREAD_CREATE(&tid, (void*(*)(void*))process_request, arg))
          sleep(1);
    }
  }
  soap_print_fault(soap, stderr);
  soap_destroy(soap); /* delete deserialized objects */
  soap_end(soap);     /* delete heap and temp data */
  soap_free(soap);    /* we're done with the context */
  return 0;
}

此服务器对可生成的线程数没有限制,因此您可能希望保持受互斥锁保护的计数器不超过最大值。此外,使用线程池可以提高整体性能。最好是使用ISAPI和Apache模块来开发gSOAP用户指南中建议的强大而安全的服务。

请参阅有关如何使用线程池,启用HTTPS以及如何强化独立服务的教程。
请参阅有关如何使用ISAPI和Apache模块开发服务的文档。

构建服务器应用程序的步骤
使用以下命令为服务器端生成服务和数据绑定接口:

soapcpp2 -c -r -SL calc.h

其中option -c生成C代码,option -r生成报告,option只-SL生成服务器端而没有(未使用的)lib文件。

这会生成多个文件,包括:

soapStub.h 没有注释的普通C / C ++头文件语法规范的副本。
soapH.h 声明XML序列化程序。
soapC.c 实现XML序列化程序。
soapServer.c 实现服务器端XML服务API。
calc.nsmap XML命名空间绑定表到#include
soapReadme.md 服务和数据绑定接口详细信息。

要构建示例服务器应用程序,我们还需要stdsoap2.h和stdsoap2.c:

gcc -o calcserver calcserver.c stdsoap2.c soapC.c soapServer.c

运行示例
./calcclient add 2 3
result = 5
要在端口8080上对独立服务器运行该示例,请更改server[] = “http://localhost:8080”。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章