• C#访问gsoap的服务--可用


    问题来源:


    C++开发一个webservice,然后C#开发客户端,这样就需要C#的客户端访问gsoap的服务端。(大家都知道gsoap是C/C++开发webservice的最佳利器) 为什么不考虑直接用wcf开发webservice?那是考虑到以后有可能要移植到Linux平台。

    gsoap实现C#(wpf)客户端调用C++服务器端

    首先建立C++服务器端,这里需要使用到gsoap
    第一步:自己在桌面建立一个文件夹,命名为add,打开add文件夹,新建txt文档,命名为add.txt,拷贝下面的代码到add.txt,点击保存,然后修改add.txt后缀名为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 );

    第二步:从gsoap文件里面拷贝soapcpp2.exe到add文件夹里面,然后打开cmd命令行,输入:soapcpp2.exe add.h会生成许多文件,如图

    这里写图片描述
    这里写图片描述
    第三步:新建Win32控制台应用程序,命名为:gsoapApplication_1_S;
    添加wsock32.lib引用;
    从add文件夹中拷贝如下图所示文件到该项目中,其中黑圈里面的两个项目来自gsoap拷贝:
    这里写图片描述
    第四步:修改gsoapApplication_1_S.cpp,其代码如下:
    // gsoapApplication_1_S.cpp : 定义控制台应用程序的入口点。

    //
    #include "add.h"
    #include "add.nsmap"
    int main(int argc, char* argv[])
    {  
        int m, s;
        /* master and slave sockets */    
        struct soap add_soap;    
        soap_init(&add_soap);    
        //soap_set_namespaces(&add_soap, add_namespaces);    
        m = soap_bind(&add_soap, NULL, 4567, 100);        
        if (m < 0)        
        {            
            soap_print_fault(&add_soap, stderr);            
            exit(-1);        
        }                
        fprintf(stderr, "Socket connection successful: master socket = %d/n", m);        
        for ( ; ; )        
        {            
            s = soap_accept(&add_soap);            
            if (s < 0)            
            {                
            soap_print_fault(&add_soap, stderr);                
            exit(-1);            
            }            
            fprintf(stderr, "Socket connection successful: slave socket = %d/n", s);                        
            soap_serve(&add_soap);//该句说明该server的服务            
            soap_end(&add_soap);         }
        return 0; }
    //server端的实现函数与addmethod.h中声明的函数相同,但是多了一个当前的soap连接的参数
    int ns__add(struct soap *add_soap, int num1, int num2, int *sum)
    {    
        *sum = num1 + num2;    
        return SOAP_OK;
    }

    第五步:编译成功,打开cmd命令行,输入gsoapApplication_1_S.exe 4567打开主服务,如图
    这里写图片描述
    第六步:打开IE浏览器,输入:http://localhost:4567 回车,如图所示,服务器端已经配置成功
    这里写图片描述

    配置C#(wpf)客户端,使用到wsdl.exe,在本地搜索一下,没有的话需要在网上下载一个,我是在网上下载的,下载很简单
    第一步:拷贝wsdl.exe到add文件夹,打开vs自带的黑屏命令行工具,输入:wsdl /out add.wsdl回车,会生成.cs文件,文件里面包含多线程、代理等信息,如下图
    这里写图片描述

    第二步:新建WPF应用程序,命名为gsoapApplication_1_C_WPF,拷贝add.cs,add.wsdl两个文件到该工程中,并把add.cs加入工程,(主要针对用别人的服务没法修改,亦可这样操作:右键点击C#窗口项目的“引用”选择“添加服务引用”,就会出现一个填写wsdl所在地址的对话框,可以选择是远端webservice服务地址下的wsdl,当然也可以是本地的。我选择的是本地地址,也就是C:add.wsdl,点击“前往”。就会找到服务。这时下面的名字空间最好写的和当初gsoap建立时指定的命名空间一样。其实不一样也可以,但是在项目中使用的时候用的就是这个新的名字了。--wsj建议采用直接拷贝add.cs加入工程使用的方法)修改add.cs文件代码的”http://localhost/add.cgi“为”http://localhost:4567/add.cgi“,如图所示:
    这里写图片描述

    第三步:MainWindow.xaml界面代码如下:

    <Window x:Class="gsoapApplication_1_C_WPF.MainWindow"         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
    Title="MainWindow" Height="350" Width="525">    
    <Grid>        
    <GroupBox Header="Add" HorizontalAlignment="Left" Margin="52,43,0,0" VerticalAlignment="Top" Height="236" Width="412">            
    <Grid>                
    <Grid.RowDefinitions>                    
    <RowDefinition/>                    
    <RowDefinition/>                    
    <RowDefinition/>                    
    <RowDefinition/>                
    </Grid.RowDefinitions>                
    <Grid.ColumnDefinitions>                    
    <ColumnDefinition/>                    
    <ColumnDefinition/>                
    </Grid.ColumnDefinitions>                
    <Grid Grid.Row="0" Grid.Column="0">                    
    <Label Content="number 1:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
    <Grid Grid.Row="0" Grid.Column="1">                    
    <TextBox Name="num1" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
    </Grid>                
    <Grid Grid.Row="1" Grid.Column="0">                    
    <Label Content="number 2:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
    <Grid Grid.Row="1" Grid.Column="1">                    
    <TextBox Name="num2" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
    </Grid>                
    <Grid Grid.Row="2" Grid.Column="0">                    
    <Label Content="result----:" HorizontalAlignment="Right"  VerticalAlignment="Center"/>                 </Grid>                
    <Grid Grid.Row="2" Grid.Column="1">                    
    <TextBox Name="result" HorizontalAlignment="Left" VerticalAlignment="Center" Width="150" Height="30"/>                
    </Grid>                
    <Grid Grid.Row="3" Grid.Column="1">                    
    <Button Name="submitAdd" Content="计算" Click="submitAdd_Click" Width="80" Height="30"/> </Grid>            
    </Grid>                    
    </GroupBox>
    </Grid>
    </Window>

    第四步:MainWindow.xaml.cs代码如下:

    using System;
    using System.Collections.Generic;
    using System.Linq; using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    namespace gsoapApplication_1_C_WPF
    {    
        /// <summary>    
        /// Interaction logic for MainWindow.xaml    
        /// </summary>    
        public partial class MainWindow : Window    
        {        
            public MainWindow()        
            {            
                InitializeComponent();                    
            }
            private void submitAdd_Click(object sender, RoutedEventArgs e)        
            {            
                add myadd = new add();            
                int num1 = Convert.ToInt32(this.num1.Text);            
                int num2 = Convert.ToInt32(this.num2.Text);            
                int? sum = 0;            
                bool sumSpecified = false;                         
                myadd.Calladd(num1, num2,out sum, out sumSpecified);            
                this.result.Text = sum.ToString();        
            }    
        }
    }

    第五步:编译,生成,结果如图所示(不要忘了打开主服务哦~):
    这里写图片描述

    源码地址:https://files.cnblogs.com/files/liushui-sky/gsoapApplication_1_C_WPF.rar

                     https://files.cnblogs.com/files/liushui-sky/gsoapApplication_1_S.rar

    发现新的问题

    对于接口.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 );
    typedef struct ns__point
    {
        int x;
        int y;
    };
    int ns__addPt(struct ns__point num1, struct ns__point num2, struct ns__point* result );

    这里写图片描述

    1、C#提供的WebService的URL一般形如:http://localhost/WebService.asmx,但是,C++能提供的只能是:http://localhost/。C++做客户端的时候调用没有问题,但是当C#做客户端的时候,引用C++提供的RUL时,会提示没用执行方法(HTTP GET method not implemented)。做C#开发的大部分会认为C++方提供的不是WebService,或者说提供的WebService根本就不全,都不带.asmx文件。做C++开发的会认为他传输的数据符合soap协议,靠http传输数据,他就是WebService。
    2、当我们解决了第一步后,紧接着会发现另外一个问题。当我们需要传输自定义数据类型时(在C++中称结构体,在C#中称实体),从C++返回的信息中,C#无法构建出实体类。

    3、当传输的信息中带有中文字符时,乱码满天飞。

    为了解决这些问题,我们先简单了解一下WebService。
    Web Service互操作协议栈:
    、服务发现 (UDDI)
    、服务描述(WSDL)
    、服务调用(SOAP)
    、消息编码 (XML)
    、传输网络层(HTTP, TCP/IP)
    其中WSDL描述WebService都有什么方法、方法有什么参数,什么返回值等。SOAP(简单对象访问协议(Simple Object Access Protocol)是一种轻量的、简单的、基于XML的协议。传输的数据就需要遵循这个协议。我比较简单得认为传输的数据需要遵循这种格式。

    借用微软的这个图描述下WebService的调用过程:
    这里写图片描述

    C++、C写的WebService相互调用martin新浪博客 - mylovejsj - 宋孝先的博客

    开始解决问题。作为.NET开发人员,我们根本就接触不到底层的东西,全被封装了。

    C++做的确实是WebService,只是他们需要给提供一个描述文档,即.WSDL文件。使用.NET提供的wsdl.exe工具,使用命令:wsdl /o: c:webservice.cs c:webservice.wsdl。通过webservice.wsdl文档,生成代理类,将代理类写入webservice.cs文件中。我们拷贝这个cs文件到项目中,将URL指向http://localhost/,就能像以往那样使用WebService了。

    当出现无法传递复杂类型数据时,是因为使用gsoap生成的wsdl文件与.Net中生成的wsdl文件不一样。具体代码如下:

    <!-- operation response element -->
    <element >
    <complexType>
    <sequence>
    <element type="xsd:int" minOccurs="1" maxOccurs="1"/>
    <element type="xsd:int" minOccurs="1" maxOccurs="1"/>
    </sequence>
    </complexType>
    </element>

    以上为gsoap生成的。返回实体result,实体有两个属性:a,b。

    <s:element >
    <s:complexType>
    <s:sequence>
    <s:element minOccurs="0" maxOccurs="1" type="tns:result" />
    </s:sequence>
    </s:complexType>
    </s:element>
    <s:complexType >
    <s:sequence>
    <s:element minOccurs="1" maxOccurs="1" type="s:int" />
    <s:element minOccurs="1" maxOccurs="1" type="s:int" />
    </s:sequence>
    </s:complexType>

    以上是.NET生成的。
    在下面的文件中,多出

    <s:element >
    <s:complexType>
    <s:sequence>
    <s:element minOccurs="0" maxOccurs="1" type="tns:result" />
    </s:sequence>
    </s:complexType>
    </s:element>

    这个便是.NET中用来构造实体的。当我们出现情况4.2时,gsoap中尽量使用.NET生成的wsdl文档,生成.h文件,以避免C++中的结构无法在C#中转换成实体。

    第三个问题,我们是通过将中文转换成16进制后传输过来,然后再转换成中文。下面提供C#转换的代码:

    /// <summary>
    /// 从16进制转换成汉字
    /// </summary>
    /// <param ></param>
    /// <returns></returns>
    public static string GetChsFromHex(string hex)
    {
        if (hex == null)
        throw new ArgumentNullException("hex");
        if (hex.Length % 2 != 0)
        {
            hex += "20";//空格
            //throw new ArgumentException("hex is not a valid number!", "hex");
        }
        // 需要将 hex 转换成 byte 数组。
        byte[] bytes = new byte[hex.Length / 2];
    
        for (int i = 0; i < bytes.Length; i++)
        {
            try
            {
                // 每两个字符是一个 byte。
                bytes[i] = byte.Parse(hex.Substring(i * 2, 2),
                System.Globalization.NumberStyles.HexNumber);
            }
            catch
            {
                // Rethrow an exception with custom message.
                throw new ArgumentException("hex is not a valid hex number!", "hex");
            }
        }
    
        // 获得 GB2312,Chinese Simplified。
        System.Text.Encoding chs = System.Text.Encoding.GetEncoding("gb2312");
    
    
        return chs.GetString(bytes);
    }
    
    /// <summary>
    /// 从汉字转换到16进制
    /// </summary>
    /// <param ></param>
    /// <returns></returns>
    public static string GetHexFromChs(string s)
    {
        if ((s.Length % 2) != 0)
        {
            s += " ";//空格
            //throw new ArgumentException("s is not valid chinese string!");
        }
    
        System.Text.Encoding chs = System.Text.Encoding.GetEncoding("gb2312");
    
        byte[] bytes = chs.GetBytes(s);
    
        string str = "";
    
        for (int i = 0; i < bytes.Length; i++)
        {
            str += string.Format("{0:X}", bytes[i]);
        }
    
        return str;
    }

    注:以上来转换代码源于网络,C++中转换的代码也可以在网上找到。

    例子解决代码

    //gsoap ns service name: calc
    //gsoap ns service style: rpc
    //gsoap ns service encoding: encoded
    //gsoap ns service namespace: http://127.0.0.1:8089/calc.wsdl
    //gsoap ns service location: http://127.0.0.1:8089/cal.cgi
    //gsoap ns schema  namespace:    urn:calc
    typedef struct ns__point
    {
        int x;
        int y;
    };
    
    struct ns__pointResponse
    {
        int ret;
        struct ns__point pt;
    };
    
    int ns__add(int num1, int num2, int* result );
    int ns__addPt(struct ns__point num1, struct ns__point num2, struct ns__pointResponse* result );

    这里写图片描述
    图中会写下calc.cs,就说明成功了。
    至于为什么这么写就解决问题了,那就要扯到如何生成接口头文件了。

    由wsdl文件生成头文件

    开发客户端程序
    (1)客户端服务端通信必不可少的一个东西:接口定义。
    gSoap使用中,需要此接口文件(也就是以后常提到的头文件)此文件可以两种方式得到:
    1: 由现有的wsdl文件转换得到(本文使用这种方式)
    2: 自己编写
    (2)两个工具的用途
    wsdl2h.exe——–名字就能看出来,完成 wsdl文件(.wsdl)到(2)头文件(.h)的转换,即由写好的wsdl格式的接口定义文件转换为C/C++格式的头文件
    soapcpp2.exe——根据接口头文件生成应用程序客户端和服务端的框架文件(soapClient.cpp、soapServer.cpp等).

    结合例子说明过程
    示例:引用向导中的例子–计算器
    由wsdl文件生成接口头文件
    wsdl文件URL地址:http://www.cs.fsu.edu/~engelen/calc.wsdl
    操作流程
    a) 程序->运行 输入cmd 启动命令行;
    b) 打开wsdl2h.exe所在文件路径(不打开的话下面输入时要记着输入全路径,不然找不着了);
    c) 输入:wsdl2h.exe -o cal.h http://www.cs.fsu.edu/~engelen/calc.wsdl,
    其中
    “wsdl2h.exe”,因为第二步中打了wsdl2h文件所在路径,所以此处可以直接用了,若没打开要输入所在完整路径
    “cal.h”为生成的头文件名,此文件的保存位置在wsdl2h.exe同一文件目录下(要修改的话要请参照命令行语法规则,此处不多说了,有点脱离主题)
           “-o”是生成选项,表示输出,也可以增加其它选项(向导中出现 “-s -o”、”-c -o”等),具体选项说明请参照向导中介绍。
    http://www.cs.fsu.edu/~engelen/calc.wsdl“是wsdl文件的位置,此处是在网络地址(这个地址是可以用的),如果wsdl文件在本地的也可以,路径设置好就OK了
    注意:以上四项中每项间都有空格分开 不要手误啊!
    d) 回车 运行吧,看提示信息,没有提示故障的话,查找cal.h吧(cal.h的保存路径要记好啊,自己设置的);
    e) 得到cal.h后,打开可以看到接口定义;
    f) 在命令行中 输入:soapcpp2.exe cal.h
    其中
    “soapcpp2.exe”,此处把soapcpp2.exe和wsdl2h.exe放在同一目录下了,且前面已打开此路径;
    “cal.h”即由wsdl生成的头文件,此文件保存位置与soapcpp2.exe相同,故此处可这样输入,若不同还须设置文件路径;
    同样两项之间也有空格!
    可以增加控制选项如”soapcpp2.exe -C cal.h”则只生成客户端所需文件, “soapcpp2.exe -S calc.h”则只生成服务端所需文件
    g) 回车 运行。
    如果提示找不到文件 stlvector.h,因为cal.h文件中引用了文件stlvector.h;解决方法:开发包gsoapimport目下有此文件,复制一份到cal.h所在目录。
    注意:此过程中要检查生成的编译信息,确保没有警告信息出现(警告信息可能是gSoap不支持的内容,如果不去除,程序运行故障很难找到根本原因),另不能只看编译结果成功就认为是正确的,存在警告信息也显示为编译成功!!
    h) 编译成功,生成的框架文件保存位置与cal.h在同一目录下,包括:calc.nsmap、soapC.cpp、soapcalcObject.h、soapcalcProxy.h、soapClient.cpp、soapClient.lib
    、soapH.h、soapServer.cpp、soapServer.Lib、soapStub.h和接口的请求应答xml文件。
        其中
          calc.nsmap 为命名空间定义文件;
          soapClient.cpp 客户端文件
          soapServer.cpp 服务端文件
          soapcalcProxy.h 代理用到
          soapStub.h 此文件中为程序中要用到的接口的声明(将cal.h中的接口经过处理后,在程序中供调用的接口)
          xml文件 为接口的xml格式,可以据此帮助调试等。。。
    其它文件作用还没完全搞明白,向导中有说明,此处不说了以免误导^_^
    i) 编译过程完成。下面开始程序(客户端)
    VC6中新建空的win32控制台工程,新建源文件calcClient.cpp;
    复制编译生成的文件(包括cal.h和stlvector)到程序目录中;
    在工程中添加文件calc.nsmap、soapC.cpp、soapClient.cpp、soapH.h、soapStub.h(使用代理添加soapcalcProxy.h);
    编译,提示无法找到文件stdsoap2.h、stdsoap2.cpp文件,这两个文件为gSoap的源码文件,在gsoap文件夹中找到这两个文件,复制到程序目录下并添加到工程中;
    在calcClient.cpp文件中 添加语句#include “soapH.h”, #include “calc.nsmap”
    ///////////////////////////calcClient.cpp源码//

    #include <stdio.h>
    #include "soapH.h"
    #include "calc.nsmap"
    const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";
    int main(int argc, char **argv)
    {
     struct soap soap;
     double a, b, result;
     if (argc < 4)
     { 
         fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num
    ");
         exit(0);
     }
     soap_init(&soap);
     a = strtod(argv[2], NULL);
     b = strtod(argv[3], NULL);
     switch (*argv[1])
     { case 'a':
       soap_call_ns2__add(&soap, server, "", a, b, result);
       break;
     case 's':
       soap_call_ns2__sub(&soap, server, "", a, b, result);
       break;
     case 'm':
       soap_call_ns2__mul(&soap, server, "", a, b, result);
       break;
     case 'd':
       soap_call_ns2__div(&soap, server, "", a, b, result);
       break;
     case 'p':
       soap_call_ns2__pow(&soap, server, "", a, b, result);
       break;
     default:
       fprintf(stderr, "Unknown command
    ");
       exit(0);
     }
     if (soap.error)
     { 
         soap_print_fault(&soap, stderr);
         exit(1);
     }
     else
     printf("result = %g
    ", result);
     soap_destroy(&soap);
     soap_end(&soap);
     soap_done(&soap);
     return 0;
    }

    其中soap_call_ns2__add、soap_call_ns2__sub、soap_call_ns2__mul、soap_call_ns2__div、soap_call_ns2__pow为接口函数,在soapStub.h中可以找到函数声明。

    自定义接口头文件

    接口头文件的格式在向导中没有看到明确的说明性的内容,但通过看开发包中示例程序中头文件定义和通过wsdl生成的头文件的内容,可以发现,头文件中都会出现以下几行信息
    //gsoap ns service name: calc Simple calculator service
    //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 service method-style: add rpc
    //gsoap ns service method-encoding: add http://schemas.xmlsoap.org/soap/encoding/
    //gsoap ns service method-action: add “”
    这些都是头文件中的说明性信息,编译时会用到这里的信息,这些信息有命名空间的和服务名称的名字,有些信息为默认参数信息,如service location 如果在客户端调用接口方法时,没有输入服务端地址,方法执行时将自动调用此处的地址做为服务器地址。后三行是对add方法的说明,在写头文件时可以修改一下这些说明信息如下:
    //gsoap ns service name: calc Simple calculator service
    //gsoap ns service style: rpc
    //gsoap ns service namespace: urn:calc
    //gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
    这些信息是很重要的,如果是已定义好的接口,一定要注意命名空间名字统一。

    注:--wsj

        关于红色部分说明--还是有点模糊

        

    二 命名空间
    
    <?xml version="1.0" encoding="UTF-8"?>
    
    <SOAP-ENV:Envelope
    
     xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
    
     xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
    
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    
     xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    
     xmlns:ns="urn:calc">
    
     <SOAP-ENV:Body SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    
      <ns:add>
    
       <a>0.0</a>
    
       <b>0.0</b>
    
      </ns:add>
    
     </SOAP-ENV:Body>
    
    </SOAP-ENV:Envelope>
    
    
    
      由于标签是我们自定义的,多个XML文件中可能存在重复命名,但表示的含义却不一样,为了解决XML文档中命名的冲突问题 就出现了命名空间。所以需要加上一个namespace来区分多个xml文件之间相同的标签。
    
    使用语法:xmlns:namespace-prefix="namespaceURI"
    
    xmlns的意思是XML Namespace 
    
    URI是Uniform Resource Identifier 的缩写,作用是赋予命名空间一个惟一的名称用来表示URI 一个是URL,一个是URN,gSOAP中URL和URN都使用了:xmlns:ns="urn:calc 
    
    其中calc是一个接口文件的文件名。这句是gsoap定义的默认命名空间有了默认的命名空间,直接是<a>0.0</a>就可以,而不用<ns:a>0.0</ns:a>
    
    转自:https://blog.csdn.net/u013898698/article/details/55511284
    

      


    下面开始主要工作:接口定义
    gSoap对“”和“_”(下划线、双下划线)有特殊用法,接口定义时函数名前要加上命名空间名和双下划线
    举例:加法运算
    int add(int num1, int num2, int &num3);
    在接口头文件定义时要写成如下格式
    int ns__add(int num1, int nmu2, int &num3);
    其中
    “ns”是命名空间名称,此名称可以自定义成其它名字 但其后”urn:calc”不可随意更改,这个是要与服务端统一的
    “__”是编译时识别符号,如果没有的话xml文件是编译不出来的

    “_”(单下划线)定义名称中要慎用,自己定义的变量或函数名称中使用下划线的地方要在下划线后加上”USCORE”;
    例 定义 ns__add_sum(int num_1, int num2, int &num_3);
    要写成如下形式
    ns__add_USCOREsum(int num_USCORE1, int num2, int &num_USCORE3);
    “add”是接口函数名 这个是客户端与服务器统一的接口名字
    函数内参数变量类型和变量名 为客户端与服务器统一的接口
    函数中只有最后一个参数是输出参数,前面的都是输入参数
    若有多个输出信息,可定义结构体。
    若没有输入参数 可将输入参数类型定义为void (将忽略void 类型的参数)
    函数返回类型必须为 int,可以通过返回值判断接口函数执行情况
    至此接口定义要注意的地方完毕,开始写接口定义文件。
    本例中使用自定义的头文件,编写客户端程序,但服务器仍与上篇中服务器相同,以便说明两种方式的结果,接口可以从上篇中由wsdl生成的头文件中获得。
    源码如下:

    ///////////////////mycalc.h///////////////////////////////
    //gsoap ns service name: mycalc
    //gsoap ns service style: rpc
    //gsoap ns service namespace: urn:calc
    //gsoap ns service location: http://websrv.cs.fsu.edu/~engelen/calcserver.cgi
    int ns__add(double a, double b, double &result);
    int ns__sub(double a, double b, double &result);
    int ns__mul(double a, double b, double &result);
    int ns__div(double a, double b, double &result);
    int ns__pow(double a, double b, double &result);
    ///////////////////mycalc.h///////////////////////////////

    上面文件中一定要注意 //gsoap ns service namespace: urn:calc 由于服务器中接口命名空间为calc 故此处必须是calc! ---这句还是没明白wsj
    开始编译此头文件
    运行 soapcpp2.exe mycalc.h
    编译成功,写客户端程序
    新建工程mycalcClient, 添加生成的文件,新建源文件mycalcClient.cpp
    源码如下

    //////////////////////mycalcClient.cpp/////////////////
    #include <stdio.h>
    #include "soapH.h"
    #include "calc.nsmap"
    const char server[] = "http://websrv.cs.fsu.edu/~engelen/calcserver.cgi";
    int main(int argc, char **argv)
    {
     struct soap soap;
     double a, b, result;
     if (argc < 4)
     { fprintf(stderr, "Usage: [add|sub|mul|div|pow] num num
    ");
     exit(0);
     }
     soap_init(&soap);
     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
    ");
       exit(0);
     }
     if (soap.error)
     { soap_print_fault(&soap, stderr);
     exit(1);
     }
     else
     printf("result = %g
    ", result);
     soap_destroy(&soap);
     soap_end(&soap);
     soap_done(&soap);
     return 0;
    }
    //////////////////////mycalcClient.cpp/////////////////

    测试一下吧^_^
    对比两种生成接口头文件的方法,选择那一种还要视情况而定
    如果是已有写好的wsdl文档,当然是选择用wsdl2h工具来生成头文件了,毕竟这个方便的多,但在编译时,一定要检查警告信息,有些情况下生成的接口头件存在编译异常,例如复杂数据结构时就有可能出现结构体命名不符合gSoap命名规则(实际应用中遇至过,此时要手动修改这些命名)
    简单说一下soap结构体初始化及清理
    soap_init(&soap);//初始化
    …………
    soap_destroy(&soap);//结束清理操作
    soap_end(&soap);
    soap_done(&soap);
    向导中这方面讲的比较细^_^
    两篇都只是写了客户端的程序,因为是一直在与现成的服务器端通信,在此就没列出自己的服务器程序;

    接口定义

    ,可参考《GSoap接口定义》。这里我将给出C#引用这个webserver所对应的接口形式。
    gsoap是根据我们定义好的.h文件,然后用工具产生了我们所需的.c文件。所以我们必须根据gsoap的要求编写.h。
    1. 单个参数的传出:
    int ns__add( int a, int b, int *c );
    需要说明的是,这里的ns__是必须的,必须以开始注释中的ns加两个下划线开始。返回值必须是int
    但是这里的int并不是接口的返回值,而是gsoap内部的返回值。真正的返回值是int *c。

     C#中对应的接口:  int add( int a, int b );  返回值就是上述的int *c参数。

    2. 多个参数传出,在接口中必须使用结构体

     typedef char * xsd__string;
         typedef long   xsd__int;
         struct ns__personResponse{
             xsd__int age;
             xsd__string name;
             xsd__string address;
         };
         int ns__person( xsd__string buf_in, struct ns__personResponse * buf_out );
     在C#中,并不是我们所声明的这样。而是:int person( string buf_in, out string name, out string address );
     即,结构体中的第一个域会变成返回值,其他的变成一个个的输出参数。--wsj 待验证

    3. 返回结构体。如果要返回结构图,那么必须在结构体中再套一层结构体:

     typedef char * xsd__string;
         typedef long   xsd__int;
         struct ns__person{
             xsd__int age;
             xsd__string name;
             xsd__string address;     
         };
         struct ns__personResponse{
             xsd__int ret;
             struct ns__person person;
         };
         int ns__person( xsd__string buf_in, struct ns__personResponse * buf_out );
     那么在C#中,看到的接口是这样的:int person( string buf_in, person对应的结构类 );

    4. 接口中的下划线,如果接口中的交易名有下划线,必须这么声明:
    int ns__echo_USCOREreverse( char * buf_in, char ** buf_out );

     那么,C#中实际上的接口名就是:string echo_reverse( string buf_in );

    gsoap中返回字符串
    1. 下面是一个逆转字符串的函数。

    int ns__echo_USCOREreverse( char * buf_in, char ** buf_out );
         int ns__echo_USCOREreverse( struct soap *add_soap, char *buf_in, char **buf_out )
         {
            int i, j, len;
            printf( "ns__interface: in=[%s]
    ", buf_in );
    
            len = strlen(buf_in);
            *buf_out = (char*)soap_malloc( add_soap, len+1 );
            for( i=len-1, j=0; i>=0; i--, j++ ){
                (*buf_out)[j] = buf_in[i];
            }
            (*buf_out)[j] = 0;
    
            return 0;
         }
     其中调用soap_malloc申请空间,并且将他赋给返回参数buf_out。这个空间会在调用soap_end时被释放。

    gsoap传输中文。我使用utf-8编码格式来支持汉字的传输。
    1. 设置gsoap为utf-8传输数据
    soap_set_mode( &SmsWBS_soap, SOAP_C_UTFSTRING ); //设置编码
    SmsWBS_soap.mode|=SOAP_C_UTFSTRING;

    1. 使用下面得函数转换我们的传输内容,即将我们的数据转成UTF-8编码:
    int conv_charset( const char *dest, const char *src, char *input, size_t ilen, char *output, size_t olen )
         {
             int convlen = olen;
             iconv_t conv = iconv_open( dest, src );
             if( conv == (iconv_t) -1 )
                 return -1;
    
              memset( output, 0, olen );
             if( iconv( conv, &input, &ilen, &output, &olen ) ){
                 iconv_close(conv);
                 return -1;
             }
    
             iconv_close(conv);
             return convlen-olen;
         }
     例子: conv_charset( "UTF-8", "GBK", "林学任.linxr", strlen("林学任.linxr"),  buf_out->name, 100 );

    webserver发布
    1. 在C#中,可以直接引用一个webserver,但是我们写得webserver如何能用被其引用呢。其实只要实现gsoap的fget回调函数即可:
    SmsWBS_soap.fget = http_get;
    2. http_get函数实现

    int http_get(struct soap * soap)
         { 
             FILE *fd = NULL;
    
             char *s = strchr( soap->path, '?' );
             if( !s || strcmp( s, "?wsdl" ) ){
                 return SOAP_GET_METHOD;
             }
    
             fd = fopen( "SmsWBS.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;
         }

    Reference:[gsoap使用总结](http://www.cnblogs.com/linxr/archive/2011/10/17/2215285.html)

    转自:https://blog.csdn.net/caowei880123/article/details/49129211?locationNum=3&fps=1

  • 相关阅读:
    cookie简介
    Javaweb
    apache-tomcat-7.0.92
    学javaweb 先学Servlet 应用理论很重要
    DataGuard切换(主库为Rac+备库为Rac)
    Oracle 迁移 序列
    oracle SQL 执行进度
    ORA-20011 ORA-29913 KUP-11024 GATHER_TABLE_STATS
    盘点 Oracle 11g 中新特性带来的10大性能影响
    查询近一个月的表空间使用情况
  • 原文地址:https://www.cnblogs.com/liushui-sky/p/9722217.html
Copyright © 2020-2023  润新知