用gsoap实现面向Java客户端的WebService

13 篇文章 0 订阅

用gsoap实现面向Java客户端的WebService

环境

操作系统:Windows 7
gsoap版本:2.8.6
axis2版本:1.6.1
C++编译器/开发环境:Visual Studio 2008
JDK版本:1.6.0_22
ant版本:1.7.0
注:安装gsoap,你需要先安装openssl-devel,byacc-devel,bison-devel等开发包
一、简单示例

利用gsoap和axis,常规的WebService(C++实现)以及java访问客户端的开发过程为:

第一步:编写一个头文件,定义WebService中使用的数据类型和消息,例如:

示例1:add.h,一个实现简单的加法运算的服务

//gsoap ns service name: add
//gsoap ns service namespace: http://localhost/add.wsdl
//gsoap ns service location: http://localhost
//gsoap ns service executable: add.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:add

int ns__add( int num1, int num2, int* sum);

示例2:array.h,返回一个使用复杂数据结构为元素的数组的服务

//gsoap ns service name: ArrayDemo
//gsoap ns service namespace: http://localhost/add.wsdl
//gsoap ns service location: http://localhost
//gsoap ns service executable: ArrayDemo.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:ArrayDemo

class ns3__NVElement {
public:
    int ID;
    char *name;
    char *value;
};

class NVSet {
public:
    ns3__NVElement *__ptr; // points to array elements
    int __size; // number of elements pointed to
    
    NVSet();
    ~NVSet();
    void print();
};

int ns__getAllNamedValues(NVSet &return_);

注意ns和ns3这样的前缀后面都是两个下划线,这是gsoap的约定,前缀ns和ns3都是命名空间。

第二步:生成wsdl和服务端代码

这一部要使用gsoap提供的soapcpp2命令行工具,例如,若gsoap的安装目录为C:\gsoap-2.8,自定义的头文件为add.h,则命令格式为:

c:\>soapcpp2 -S -IC:\gsoap-2.8\gsoap\import add.h

此命令会生成包括wsdl和.h、.cpp文件在内的一系列文件,例如若自定义头文件为add.h,生成的文件清单为:

add.nsmap
soapServer.cpp
soapStub.h
soapaddObject.h
soapC.cpp
add.wsdl
soapH.h
soapServerLib.cpp
ns.xsd
add.add.req.xml
add.add.res.xml

第三步:建立一个VC工程

仍然以add.h及其生成文件为例,在Visual Studio 2008中建立一个空的win32控制台程序的工程,并且在创建时去掉预编译头支持,然后将文件add.nsmap、soapServer.cpp、soapStub.h、soapaddObject.h、soapC.cpp、soapH.h都添加到工程中,将gsoap安装目录中(例如安装目录为C:\gsoap-2.8,则将该目录下gsoap子目录中的stdsoap2.h和stdsoap2.cpp也拷贝到工程目录下并加入到工程中。修改该工程的属性,在链接器的输入选项中加入对ws2_32.lib。

第四步:实现WebService

为此要在工程中加入一个含有main函数的文件,例如webservice.cpp,编辑该文件以实现服务中提供的方法,仍以add.h生成的项目为例,此文件的内容类似于:

#include "soapH.h"
#include <windows.h>

#define BACKLOG (100)    /* Max. request backlog */
DWORD WINAPI process_request(LPVOID*);
int http_get(struct soap * soap);

int main(int argc, char **argv) {
    struct soap soap;
    struct soap *tsoap;
    const char* fmt = "accepts socket %d connection from IP %d.%d.%d.%d\n";

    soap_init(&soap);
    
    if ( argc < 2 ) {
        soap_serve(&soap);
        soap_destroy(&soap);
        soap_end(&soap);
    }
    else {
        soap.send_timeout = 60;
        soap.recv_timeout = 60;
        soap.accept_timeout = 3600;
        soap.max_keep_alive = 100;

        soap.fget = http_get;

        DWORD tid;
        HANDLE hThread;
        
        int port = atoi(argv[1]); // first command-line arg is port
        SOAP_SOCKET m, s;

        m = soap_bind(&soap, NULL, port, BACKLOG);
        if ( !soap_valid_socket(m) ) exit(1);
        
        printf("Socket connection successful %d\n", m);
        for ( ; ; ) {
            s = soap_accept(&soap);
            if ( !soap_valid_socket(s) ) {
                if ( soap.errnum ) {
                    soap_print_fault(&soap, stderr);
                    exit(1);
                }

                printf("server timed out\n");
                break;
            }
            
            printf(fmt, s, (soap.ip>>24)&0xFF, (soap.ip>>16)&0xFF, (soap.ip>>8)&0xFF, soap.ip&0xFF);

            tsoap = soap_copy(&soap); // make a safe copy
            if ( !tsoap ) break;

            hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)process_request, tsoap, 0, &tid);
            if ( hThread == NULL ) {
                printf("can not create a thread for SOAP request processing.\n");
                exit(-1);
            }
        }
    }
    
    soap_done(&soap);
    return 0;
}

DWORD WINAPI process_request(LPVOID* soap) {
    soap_serve((struct soap*)soap);
    soap_destroy((struct soap*)soap);
    soap_end((struct soap*)soap);
    soap_done((struct soap*)soap);
    free(soap);
    return 0;
}

// 服务消息的实现代码
int ns__add(struct soap *add_soap, int num1, int num2, int *sum) {
    *sum = num1 + num2;
    return SOAP_OK;
}

int http_get(struct soap * soap) {
    FILE *fd = NULL;

    printf("call http_get.\n");
    char *s = strchr(soap->path, '?');
    if ( !s || strcmp(s, "?wsdl") ) return SOAP_GET_METHOD;

    fd = fopen("add.wsdl", "rb");
    if ( !fd ) return 404;

    soap->http_content = "text/xml";
    soap_response(soap, SOAP_FILE);

    for ( ; ; ) {
        size_t r = fread(soap->tmpbuf, 1, sizeof(soap->tmpbuf), fd);
        if ( !r ) break;
        if ( soap_send_raw(soap, soap->tmpbuf, r) ) break;
    }

    fclose(fd);
    soap_end_send(soap);

    return SOAP_OK;
}

上述代码实现了一个多线程的消息处理,而http_get函数则允许用户通过诸如http://hostname:port?wsdl的方式从浏览器中访问此WebService的wsdl文件,当然要注意需要将该文件放置到此WebService的exe可执行文件的同一目录下。

编译此代码,就可以得到WebService的可执行文件了,例如若编译结果输出为add.exe,则在命令行下输入命令:

C:\>add 8090

WebService已经在8090端口上启动,可以在浏览器地址栏输入http://localhost:8090或http://localhost:8090?wsdl进行验证。注意,在启动WebService之前一定要确保windows操作系统的防火墙上已经开放了8090端口。在启动WebService时,可以通过命令行指定任意的端口。

第五步:生成客户端java代理代码

生成java代理代码可以使用apache axis2提供的wsdl2java工具,而编译则需要jdk和ant工具,为使用此这些工具,确保axis2、jdk和ant已经安装(axis2和ant解压缩即可,jdk需要安装),打开操作系统的控制面板,在系统设置中增加两个环境变量AXIS2_HOME和JAVA_HOME,分别指向axis2和jdk的安装目录,将wsdl2java.bat所在的bin目录如C:\axis\axis2-1.6.1\bin,java、javac等可执行文件的bin目录如C:\Java\jdk1.6.0_22\bin以及ant命令所在的bin目录如c:\apache-ant-1.7.0\bin添加到系统环境变量中,然后执行命令(以上面生成的add.wsdl为例):

C:\>wsdl2java -uri .\add.wsdl -p com.client -o .

或者

C:\>wsdl2java -uri http://localhost:8090?wsdl -p com.client -o .

若使用第一个命令,需要确保add.wsdl位于当前的目录,而运行第二个命令应,则确保WebService正在运行。此命令会生成一个src目录和一个build.xml文件,进入src目录,则可以见到java类的包目录com\client,其下有客户端代理类AddStub.java文件。

第六步:编译客户端java代码

为编译客户端代理类,可以使用ant,命令为:

C:\>ant

这个命令应当在wsdl2java生成的build.xml相同的目录下执行,此命令会在当前目录下生成一个build文件夹,其下的classes目录包含有生成的java类,而lib目录则包含有打包好的jar文件。

第七步:编写java客户端并编译运行,java客户端源代码示例如下:

// filename: AddClient.java

import javax.xml.namespace.QName;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.rpc.client.RPCServiceClient;

import com.client.AddStub;

public class AddClient {
    public static void main(String[] args) throws Exception {
        AddStub stub = new AddStub("http://localhost:8090");
        AddStub.Add req = new AddStub.Add();
        AddStub.AddResponse resp = null;
        
        req.setNum1(15);
        req.setNum2(50);
        
        resp = stub.add(req);
        
        System.out.println("15 + 50 = "+resp.getSum());
    }
}

要编译此类,需要将axis的jar包和此前用ant生成的jar包文件设置到CLASSPATH环境变量中。由于axis中的jar包很多,为了简化CLASSPATH的设置命令,可以使用如下两个脚本:

脚本1:cpappend.bat

set _LIBJARS=%_LIBJARS%;%1

脚本2:axis_env.bat

@echo off
 
set AXIS_LIBDIR=%AXIS2_HOME%\lib
set _LIBJARS=.
for %%i in (%AXIS_LIBDIR%\*.jar) do call cpappend.bat %%i
set CLASSPATH=%_LIBJARS%;%CLASSPATH%
echo %CLASSPATH%
 
echo on

编写上述两个脚本后,假定此前用ant生成的Stub的jar包位于当前目录下,则执行如下命令编译:

C:\>axis_env
C:\>javac AddClient.java

可以在当前目录看到生成的AddClient.class类,执行如下命令即可看到java编写的WebService的客户端的运行输出:

C:\>java AddClient


二、复杂一点的WebService带来的java与gsoap的兼容性问题

按照以上步骤,如果WebService中涉及的消息参数都是基本类型(如char,short,int,long,float,double,bool,const char*等),则不会有什么问题,但如果参数类型复杂一点,特别是带有数组的情况下(例如此前给出的头文件示例array.h),会出现很多兼容性相关的问题。

为说明这些问题,首先用创建add服务同样的方法创建ArrayDemo的服务端代码和wsdl文件:

C:\>soapcpp2 -S -IC:\gsoap-2.8\gsoap\import array.h

创建一个新的VisualStudio 2008控制台程序项目,将相关文件拷贝并添加到项目中,并添加主程序文件webservice.cpp:

#include "array.h"
#include "ArrayDemo.nsmap"
#include "soapH.h"
#include <windows.h>
#include <iostream>
 
using namespace std; 
#define BACKLOG (100)    /* Max. request backlog */ 
DWORD WINAPI process_request(LPVOID*);
 
int http_get(struct soap * soap);
 
char* names[] = {"霍去病", "李斯", "李世民"};
char* values[] = {"大将", "丞相", "皇帝"};
 
int main(int argc, char **argv) {
    struct soap soap;
    struct soap *tsoap;
    const char* fmt = "accepts socket %d connection from IP %d.%d.%d.%d\n";
 
    soap_init(&soap);
     
    if ( argc < 2 ) {
        soap_serve(&soap);
        soap_destroy(&soap);
        soap_end(&soap);
    }
    else {
        soap.send_timeout = 60;
        soap.recv_timeout = 60;
        soap.accept_timeout = 3600;
        soap.max_keep_alive = 100;
 
        soap.fget = http_get;
 
        DWORD tid;
        HANDLE hThread;
         
        int port = atoi(argv[1]); // first command-line arg is port
        SOAP_SOCKET m, s;
 
        m = soap_bind(&soap, NULL, port, BACKLOG);
        if ( !soap_valid_socket(m) ) exit(1);
         
        printf("Socket connection successful %d\n", m);
        for ( ; ; ) {
            s = soap_accept(&soap);
            if ( !soap_valid_socket(s) ) {
                if ( soap.errnum ) {
                    soap_print_fault(&soap, stderr);
                    exit(1);
                }
 
                printf("server timed out\n");
                break;
            }
             
            printf(fmt, s, (soap.ip>>24)&0xFF, (soap.ip>>16)&0xFF, (soap.ip>>8)&0xFF, soap.ip&0xFF);
 
            tsoap = soap_copy(&soap); // make a safe copy
            if ( !tsoap ) break;
 
            hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)process_request, tsoap, 0, &tid);
            if ( hThread == NULL ) {
                printf("can not create a thread for SOAP request processing.\n");
                exit(-1);
            }
        }
    }
     
    soap_done(&soap); // detach soap struct
    return 0;
}
 
DWORD WINAPI process_request(LPVOID* soap) {
    soap_serve((struct soap*)soap);
    soap_destroy((struct soap*)soap);
    soap_end((struct soap*)soap);
    soap_done((struct soap*)soap); // detach soap struct
    free(soap);
    return 0;
}

// 服务消息的实现代码
int ns__getAllNamedValues(struct soap* add_soap, NVSet &return_) {
    return_.__ptr = new ns3__NVElement[3];
    return_.__size = 3;
 
    for ( int i = 0; i < 3; i++ ) {
        return_.__ptr[i].ID = i+1;
        return_.__ptr[i].name = names[i];
        return_.__ptr[i].value = values[i];
    }
 
    return_.print();
 
    return 0;
}
 
int http_get(struct soap * soap) {
    FILE *fd = NULL;
 
    printf("call http_get.\n");
 
    char *s = strchr(soap->path, '?');
    if ( !s || strcmp(s, "?wsdl") ) return SOAP_GET_METHOD;
 
    fd = fopen("ArrayDemo.wsdl", "rb");
    if ( !fd ) return 404;
 
    soap->http_content = "text/xml";
    soap_response(soap, SOAP_FILE);
 
    for ( ; ; ) {
        size_t r = fread(soap->tmpbuf, 1, sizeof(soap->tmpbuf), fd);
        if ( !r ) break;
        if ( soap_send_raw(soap, soap->tmpbuf, r) ) break;
    }
 
    fclose(fd);
    soap_end_send(soap);
 
    return SOAP_OK;
}

注意,此代码中使用了array.h头文件,该头文件的部分定义会和soapcpp2工具生成的文件冲突,需要手工编辑,注释掉部分内容,即将作为参数类型的两个类ns3__NVElement和NVSet的详细定义注释掉,仅仅留空的class声明,编辑完成后的文件如下所示:

#ifndef __ARRAY_H__
#define __ARRAY_H__
 
//gsoap ns service name: ArrayDemo
//gsoap ns service namespace: http://localhost/add.wsdl
//gsoap ns service location: http://localhost
//gsoap ns service executable: ArrayDemo.cgi
//gsoap ns service encoding: encoded
//gsoap ns schema namespace: urn:ArrayDemo
 
class ns3__NVElement;
class NVSet;
int ns__getAllNamedValues(NVSet &return_);
 
#endif

注意和最初编写的array.h的区别,可以用其它方式避免这个繁琐的编辑,在后面的例子会讲述。现在,在项目中设置ws2_32.lib库后编译,可以得到服务端的代码,然后用如下命令启动服务端:

C:\>ArrayDemo 8090

现在来编写客户端代码,会看到诸多问题。

问题1:

用wsdl2java无法从gsoap提供的wsdl文件生成客户端的java代理类文件,例如对于上面生成的ArrayDemo.wsdl文件,若通过gsoap提供的工具用如下命令生成wsdl文件和服务端代码,应执行命令:

C:\>wsdl2java -uri .\ArrayDemo.wsdl -p com.client -o .

此命令会失败,错误信息为:

Exception in thread "main" org.apache.axis2.wsdl.codegen.CodeGenerationException: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at org.apache.axis2.wsdl.codegen.CodeGenerationEngine.generate(CodeGenerationEngine.java:293)
        at org.apache.axis2.wsdl.WSDL2Code.main(WSDL2Code.java:35)
        at org.apache.axis2.wsdl.WSDL2Java.main(WSDL2Java.java:24)
Caused by: java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at org.apache.axis2.wsdl.codegen.extension.SimpleDBExtension.engage(SimpleDBExtension.java:53)
        at org.apache.axis2.wsdl.codegen.CodeGenerationEngine.generate(CodeGenerationEngine.java:246)
        ... 2 more
Caused by: java.lang.reflect.InvocationTargetException
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at org.apache.axis2.wsdl.codegen.extension.SimpleDBExtension.engage(SimpleDBExtension.java:50)
        ... 3 more
Caused by: org.apache.axis2.schema.SchemaCompilationException: can not find type {http://schemas.xmlsoap.org/soap/encoding/}Array from the parent schema urn:ArrayDemo
        at org.apache.axis2.schema.SchemaCompiler.copyMetaInfoHierarchy(SchemaCompiler.java:1370)
        at org.apache.axis2.schema.SchemaCompiler.processComplexContent(SchemaCompiler.java:1332)
        at org.apache.axis2.schema.SchemaCompiler.processContentModel(SchemaCompiler.java:1227)
        at org.apache.axis2.schema.SchemaCompiler.processComplexType(SchemaCompiler.java:1171)
        at org.apache.axis2.schema.SchemaCompiler.processNamedComplexSchemaType(SchemaCompiler.java:1091)
        at org.apache.axis2.schema.SchemaCompiler.processSchema(SchemaCompiler.java:1005)
        at org.apache.axis2.schema.SchemaCompiler.processElement(SchemaCompiler.java:644)
        at org.apache.axis2.schema.SchemaCompiler.processElement(SchemaCompiler.java:603)
        at org.apache.axis2.schema.SchemaCompiler.process(SchemaCompiler.java:2063)
        at org.apache.axis2.schema.SchemaCompiler.processParticle(SchemaCompiler.java:1946)
        at org.apache.axis2.schema.SchemaCompiler.processComplexTypeSchemaCompiler.java:1155)
        at org.apache.axis2.schema.SchemaCompiler.processAnonymousComplexSchemaType(SchemaCompiler.java:1054)
        at org.apache.axis2.schema.SchemaCompiler.processSchema(SchemaCompiler.java:1008)
        at org.apache.axis2.schema.SchemaCompiler.processElement(SchemaCompiler.java:644)
        at org.apache.axis2.schema.SchemaCompiler.processElement(SchemaCompiler.java:614)
        at org.apache.axis2.schema.SchemaCompiler.compile(SchemaCompiler.java:422)
        at org.apache.axis2.schema.SchemaCompiler.compile(SchemaCompiler.java:291)
        at org.apache.axis2.schema.ExtensionUtility.invoke(ExtensionUtility.java:102)
        ... 8 more

错误信息中“can not find type {http://schemas.xmlsoap.org/soap/encoding/}Array”是关键信息,说明有关数组的定义在wsdl2java工具的执行过程中出线了异常。查看ArrayDemo.wsdl文件可知,关于数组的定义有如下语句:

  <complexType name="ArrayOfNVElement">
   <complexContent>
    <restriction base="SOAP-ENC:Array">
     <sequence>
      <element name="item" type="ns3:NVElement" minOccurs="0" maxOccurs="unbounded" nillable="false"/>
     </sequence>
     <attribute ref="SOAP-ENC:arrayType" WSDL:arrayType="ns3:NVElement[]"/>
    </restriction>
   </complexContent>
  </complexType>

这一类型在后面的消息定义中被用到:

  <element name="getAllNamedValuesResponse">
   <complexType>
    <sequence>
     <element name="return" type="ns:ArrayOfNVElement" minOccurs="1" maxOccurs="1"/><!-- ns__getAllNamedValues::return_ -->
    </sequence>
   </complexType>
  </element>

查阅SOAP有关标准可知,此处用的SOAP-ENC:Array方式定义的数组并不能被普遍兼容,要解决这个问题,有几点需要注意:

1)不要使用soapenc:Array扩展(extend)或限定(restrict),如此例中的<restriction base="SOAP-ENC:Array">;
2)在类型声明中不要使用wsdl:arrayType属性,如此例中的<attribute ref="SOAP-ENC:arrayType" WSDL:arrayType="ns3:NVElement[]"/>
3)不要使用ArrayOfXXX约定来命名元素,如此例中的ArrayOfNVElement
4)在SOAP消息的ENVELOP中不要包含soapenc:arrayType属性(本例中可能是return元素中的ns:ArrayOfNVElement会引起此问题,但未证实)

根据上面的原则,对gsoap生成的wsdl文件分别做了两处改动,改动后的wsdl文件相应定义如下:

第一处改动:用于定义数组类型的complextType节点,注意用NVSet名称替换了ArrayOfElement

  <complexType name="NVSet">
   <sequence>
    <element name="item" type="ns3:NVElement" minOccurs="0" maxOccurs="unbounded" nillable="false"/>
   </sequence>
  </complexType>

第二处改动:用于定义response元素,使用了新的NVSet

  <element name="getAllNamedValuesResponse">
   <complexType>
    <sequence>
     <element name="return" type="ns:NVSet" minOccurs="1" maxOccurs="1"/><!-- ns__getAllNamedValues::return_ -->
    </sequence>
   </complexType>
  </element>

将改动后的wsdl文件存盘,再次执行命令:

C:\>wsdl2java -uri .\ArrayDemo.wsdl -p com.client -o .

此时,命令执行成功,java代码可以成功生成并用ant命令编译。

问题2:

java客户端代码无法解析gsoap生成的SOAP消息。例如在上面的例子通过修改wsdl文件,使得wsdl2java工具能够正常运行并生成客户端的代理代码,此时,利用生成的代码编写一个java客户端:

// filename: Client.java
import javax.xml.namespace.QName;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.rpc.client.RPCServiceClient;

import com.client.ArrayDemoStub;

public class Client {
    public static void main(String[] args) throws Exception {
        ArrayDemoStub stub = new ArrayDemoStub("http://localhost:8090");
        ArrayDemoStub.GetAllNamedValues req = new ArrayDemoStub.GetAllNamedValues();
        ArrayDemoStub.GetAllNamedValuesResponse resp = null;
        
        ArrayDemoStub.NVElement[] result = null;
        
        resp = stub.getAllNamedValues(req);
        result = resp.get_return().getItem();
        
        int len = result.length;
        for ( int i = 0; i < len; i++ ) {
            System.out.println("NVSet elements "+result[i].getID()+":");
            System.out.println(result[i].getName()+" = "+result[i].getValue());
        }
    }
}

借助在编译add服务客户端时编写的CLASSPATH设置脚本,用如下命令编译:

C:\>axis_env
C:\>javac Client.java

键入如下命令运行客户端程序:

C:\>java Client

此时,客户端会报告一系列异常,错误信息为:

Exception in thread "main" org.apache.axis2.AxisFault: org.apache.axis2.databinding.ADBException: Unsupported type http://schemas.xmlsoap.org/soap/encoding/ Array
        at org.apache.axis2.AxisFault.makeFault(AxisFault.java:430)
        at client.ArrayDemoStub.fromOM(ArrayDemoStub.java:2287)
        at client.ArrayDemoStub.getAllNamedValues(ArrayDemoStub.java:190)
        at Client.main(Client.java:16)
Caused by: java.lang.Exception: org.apache.axis2.databinding.ADBException: Unsupported type http://schemas.xmlsoap.org/soap/encoding/ Array
        at client.ArrayDemoStub$NVSet$Factory.parse(ArrayDemoStub.java:1587)
        at client.ArrayDemoStub$GetAllNamedValuesResponse$Factory.parse(ArrayDemoStub.java:661)
        at client.ArrayDemoStub.fromOM(ArrayDemoStub.java:2281)
        ... 2 more
Caused by: org.apache.axis2.databinding.ADBException: Unsupported type http://schemas.xmlsoap.org/soap/encoding/ Array
        at client.ArrayDemoStub$ExtensionMapper.getTypeObject(ArrayDemoStub.java:1076)
        at client.ArrayDemoStub$NVSet$Factory.parse(ArrayDemoStub.java:1506)
        ... 4 more

从出错信息看,出现此错误的原因仍然是由于Array的解析引起,仔细分析,可以推断产生问题的原因是:gsoap的服务端在生成response消息时,仍然没有遵循前面所描述的SOAP的四项兼容性约定,因此在应答消息的编码中使用了soap-enc:Array,要解决这个问题,需要设法使gsoap生成的服务端代码避开这个约定,最有效的方法是利用修改后的ArrayDemo.wsdl文件重新生成一个array.h,这样gsoap就会按照修改后的ArrayDemo.wsdl中的相关定义来进行服务消息应答中的Array编码,为达此目的需使用gsoap提供的另一个工具wsdl2h,命令格式为:

C:\>wsdl2h -s -o array.h ArrayDemo.wsdl

此时可以看到在当前目录下生成了一个新的array.h,和原来的array.h已经大不相同。现在使用这个新的array.h来生成服务端的代码,执行命令:

C:\>soapcpp2 -S -IC:\gsoap-2.8\gsoap\import array.h

此时会生成一系列新的文件,创建一个新的VisualStudio 2008控制台程序工程,将ArrayDemo.nsmap、soapArrayDemoObject.h、soapH.h、soapStub.h、soapC.cpp、soapServer.cpp加入项目中,将gsoap安装目录中gsoap子目录下的stdsoap2.h和stdsoap2.cpp也拷贝到工程目录下并加入到项目中。修改该工程的属性,在链接器的输入选项中加入对ws2_32.lib,然后编写一个新的主程序,内容与前面列出的webservice.cpp代码基本相同,唯有二处改动:1)去掉第一行对array.h的包含语句(#include "array.h"),2)编写一个新的服务消息实现,代码主体如下:


#include "ArrayDemo.nsmap"
#include "soapH.h"
#include <windows.h>
#include <iostream>

using namespace std;

// ... 此处代码与前例相同 ... //

// 新编写的服务消息实现,注意参数与前例中实现的区别
int __ns1__getAllNamedValues(struct soap* add_soap, 
    _ns3__getAllNamedValues*            ns3__getAllNamedValues,    ///< Request parameter
    _ns3__getAllNamedValuesResponse*    ns3__getAllNamedValuesResponse    ///< Response parameter
) {
    ns3__getAllNamedValuesResponse->return_ = new ns3__NVSet();
    ns3__getAllNamedValuesResponse->return_->item = new ns2__NVElement*[3];
    ns3__getAllNamedValuesResponse->return_->__sizeitem = 3;

    for ( int i = 0; i < 3; i++ ) {
        ns3__getAllNamedValuesResponse->return_->item[i] = new ns2__NVElement();
        ns3__getAllNamedValuesResponse->return_->item[i]->ID = i+1;
        ns3__getAllNamedValuesResponse->return_->item[i]->name = names[i];
        ns3__getAllNamedValuesResponse->return_->item[i]->value = values[i];
    }

    return 0;
}

int http_get(struct soap * soap) {
// ... 此处代码与前例相同 ... //
}

编译此工程可得到新的服务端程序,仍然用如下命令启动:

C:\>ArrayDemo 8090

另开一个命令行窗口启动java客户端:

C:\>axis_env
C:\>java Client

此时,不再有Array的解析异常,客户端程序能正确调用服务端的消息,但还有遗憾:输出的中文是乱码。

问题3

java和gsoap的C++程序对字符串的处理并不一致,中文很容易在客户端显示为乱码。

为解决此问题,一个比较好的方法是在gsoap生成的C++代码中用wchar_t*代替char*,这需要在运行wsdl2h生成array.h文件时指定必要的类型映射,这可以利用wsdl2h命令的-t选项来实现。

1)从gsoap的安装目录(例如我的是C:\gsoap-2.8)中找到gsoap子目录,可以在其中看到一个typemap.dat文件,这是缺省的类型映射文件
2)将该文件拷贝到当前目录,并更名为mytypemap.dat
3)用任意的文本编辑器打开该文件,找到如下两行(对于2.8版本,位于最后几行):

#    To use regular char* strings instead of std::string, use:
# xsd__string    = | char* | char*

去掉xsd__string前的注释,并将char改为wchar_t

4)保存更改后的文件,执行命令:

C:\>wsdl2h -s -t mytypemap.dat -o array.h ArrayDemo.wsdl

可以看到,新生成的array.h文件中,字符串的声明变成了wchar_t*。

按照前面的方法用soapcpp2重新生成服务端文件,建立并设置好VisualStudio 2008项目,将webservice.cpp中的两个全局数组修改成wchar_t*数组:

wchar_t* names[] = {L"霍去病",L"李斯",L"李世民"};
wchar_t* values[] = {L"大将",L"丞相",L"皇帝"};

重新编译服务端后,启动服务程序,用java客户端测试,此时一切都已正常。


文章出处:http://blog.csdn.net/tiewen/article/details/8890096


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
gSOAP是一个C/C++的SOAP (Simple Object Access Protocol)开发工具箱,可以用来实现Web Services以及SOAP消息的编解码。通过使用gSOAP,可以方便快捷地实现ONVIF服务。 以下是实现ONVIF服务的步骤: 1. 下载和安装gSOAP工具箱。 2. 生成服务端代码。使用gSOAP提供的命令行工具wsdl2h,将ONVIF的WSDL文件转换为gSOAP的头文件。例如: ``` wsdl2h -o onvif.h http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl ``` 3. 生成客户端代码。使用gSOAP提供的命令行工具soapcpp2,将ONVIF的WSDL文件转换为gSOAP客户端代码。例如: ``` soapcpp2 -i -C onvif.h ``` 4. 实现服务端程序。在服务端程序中,需要实现ONVIF的各个接口函数,并使用gSOAP提供的API进行SOAP消息的编解码和发送。例如: ```c++ #include "soapH.h" #include "onvif.nsmap" int main(int argc, char** argv) { struct soap soap; soap_init(&soap); // 实现ONVIF的各个接口函数 soap_serve(&soap); soap_destroy(&soap); soap_end(&soap); soap_done(&soap); return 0; } ``` 5. 实现客户端程序。在客户端程序中,需要使用gSOAP提供的API进行SOAP消息的编解码和发送,并调用ONVIF的各个接口函数。例如: ```c++ #include "soapH.h" #include "onvif.nsmap" int main(int argc, char** argv) { struct soap soap; soap_init(&soap); // 调用ONVIF的各个接口函数 soap_destroy(&soap); soap_end(&soap); soap_done(&soap); return 0; } ``` 6. 编译和运行程序。使用gSOAP提供的命令行工具soapcpp2,将生成的客户端和服务端代码编译成可执行程序。例如: ``` soapcpp2 -i -C onvif.h gcc -o onvif_server onvif_server.cpp onvif_serverC.cpp soapC.cpp stdsoap2.cpp gcc -o onvif_client onvif_client.cpp onvif_clientC.cpp soapC.cpp stdsoap2.cpp ``` 7. 测试程序。运行服务端程序,并使用客户端程序调用ONVIF的各个接口函数进行测试。例如: ``` ./onvif_server & ./onvif_client ``` 以上就是使用gSOAP实现ONVIF服务的基本步骤。需要注意的是,gSOAP提供了丰富的API和工具,可以根据具体的需求进行定制和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值