cgo+gSoap+onvif学习总结:3、使用gSoap生成onvif协议代码实现设备搜索并使用cgo进行交互

26 篇文章 5 订阅

cgo+gSoap+onvif学习总结:3、使用gSoap生成onvif协议代码实现设备搜索并使用cgo进行交互


1. 前言

前面我们已经安装好gSoap,下面我们就根据官方的ONVIF examples尝试生成部分代码,首先实现设备搜索客户端,生成c代码后我们再结合cgo相互传参来做个初步的示例。

2. gSoap生成c/c++代码框架

2.1 准备相关文件

主要参考官网给到的examples:https://www.genivia.com/examples/onvif/index.html

示例以客户端为主,毕竟除了摄像头厂商需要开发服务端,大多情况下我们都是需要开发客户端。

wsdl地址:

https://www.onvif.org/ch/profiles/specifications/

我们可以在上述网址对应链接位置右键’另存为’保存文件到本地,也可以记录对应链接地址,后续使用gSoap工具转换onvif wsdl为c/c++时传递文件路径可以传递本地路径或链接路径。

2.2 创建项目并生成头文件和源文件

创建项目,比如onvif_cgo,然后执行下述命令:

wsdl2h -O4 -P -x -o onvif.h http://www.onvif.org/onvif/ver10/device/wsdl/devicemgmt.wsdl

wsdl2h是gSoap安装后的工具,用于将wsdl文件按照typemap.dat映射到c/c++类型,官话:wsdl2h 工具需要一个typemap.dat文件来将 XSD 模式类型映射到 C/C++ 类型,并将 XML 命名空间前缀绑定到要在项目中使用的短名称。

这个typemap.dat用来帮助映射的文件gSoap源码中已经有了,官方说需要拷贝到当前目录,但是我尝试直接执行也成功了。

其它的一些选项比如-O、-P等这里不多说了,感兴趣的可以了解官方的例子,上面有详细的解释。这里再提一下wsdl的路径,就是我们刚才说的可以使用本地路径或者命令中的链接路径。

结果:

在这里插入图片描述

还提示生成代码的命令:

soapcpp2 onvif.h

在这里插入图片描述

有多个wsdl的话则把链接地址加到后面生成头文件即可。

2.3 生成设备发现客户端C代码实例及测试

2.3.1 资料阅读

首先参考官方给到的服务发现的资料:https://www.genivia.com/doc/wsdd/html/wsdd_0.html#wsdd_2

对于服务发现相关概念不了解可以参考我们之前的学习总结:https://blog.csdn.net/weixin_39510813/article/details/115357339

2.3.2 生成服务发现客户端C代码框架

我这里使用CLion创建C可执行文件项目,并创建CMakeLists.txt,便于编译多平台代码,目前测试WSL(Linux)、Windows上测试都是可用的,整体如下:

在这里插入图片描述

然后wsl终端执行如下命令(前提wsl上的gsoap环境我们上节已经编译搭建好了),如下命令也可以写成脚本便于后续长时间维护:

//生成c格式的头文件,不添加-c默认是c++代码
wsdl2h -c -o onvif.h http://www.onvif.org/onvif/ver10/network/wsdl/remotediscovery.wsdl

//只生成客户端源文件,我们只实现客户端,不添加-C会多生成服务端代码
soapcpp2 -x -L -C onvif.h

//拷贝核心的stdsoap相关代码到soap生成代码下,比如(c++代码则拷贝stdsoap2.cpp):
cp ~/work/gsoap-2.8/gsoap/stdsoap2.c ./
cp ~/work/gsoap-2.8/gsoap/stdsoap2.h ./

3. 实现c代码实例并运行测试

3.1 c代码

客户端代码<参考:https://blog.csdn.net/xf8964/article/details/102980328>:

修改了c++部分,配合我们上面生成的c代码使用(其中的uuid可以借助一些库随机生成,demo中可以暂时写死):

//
// Created by admin on 2022/3/3.
//

#include "soap/soapStub.h"
#include "soap/wsdd.nsmap"
#include "soap/soapH.h"


//probe消息仿照 soap_wsdd_Probe 函数编写

int main(int argc, char *argv[])
{
    //soap环境变量
    struct soap *soap;

    //发送消息描述
    struct wsdd__ProbeType req;
    struct wsdd__ProbeType wsdd__Probe;

    struct __wsdd__ProbeMatches resp;

    //描述查找那类的Web消息
    struct wsdd__ScopesType sScope;

    //soap消息头消息
    struct SOAP_ENV__Header  header;

    //获得的设备信息个数
    int count = 0;

    //返回值
    int result = 0;

    //存放uuid 格式(8-4-4-4-12)
    char uuid_string[64];

    printf("%s: %d 000: \n", __FUNCTION__, __LINE__);
    sprintf(uuid_string, "464A4854-4656-5242-4530-110000000000");
    printf("uuid = %s \n", uuid_string);

    //soap初始化,申请空间
    soap = soap_new();
    if(soap == NULL)
    {
        printf("malloc soap error \n");
        return -1;
    }


    soap_set_namespaces(soap, namespaces);  //设置命名空间,就是xml文件的头
    soap->recv_timeout = 5;  //超出5s没数据就推出,超时时间

    //将header设置为soap消息,头属性,暂且认为是soap和header绑定
    soap_default_SOAP_ENV__Header(soap, &header);
    header.wsa__MessageID = uuid_string;
    header.wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
    header.wsa__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";
    //设置soap头消息的ID
    soap->header = &header;

    /* 设置所需寻找设备的类型和范围,二者至少设置一个
        否则可能收到非ONVIF设备,出现异常
     */

    //设置soap消息的请求服务属性
    soap_default_wsdd__ScopesType(soap, &sScope);
    sScope.__item = "onvif://www.onvif.org";
    soap_default_wsdd__ProbeType(soap, &req);
    req.Scopes = &sScope;

    /* 设置所需设备的类型,ns1为命名空间前缀,在wsdd.nsmap 文件中
       {"tdn","http://www.onvif.org/ver10/network/wsdl"}的tdn,如果不是tdn,而是其它,
       例如ns1这里也要随之改为ns1
    */
    req.Types = "ns1:NetworkVideoTransmitter";

    //调用gSoap接口 向 239.255.255.250:3702 发送udp消息
    result = soap_send___wsdd__Probe(soap,  "soap.udp://239.255.255.250:3702/", NULL, &req);

    if(result == -1)
    {
        printf("soap error: %d, %s, %s \n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
        result = soap->error;
    }
    else
    {
        do{
            printf("%s: %d, begin receive probematch... \n", __FUNCTION__, __LINE__);
            printf("count = %d \n", count);

            //接收 ProbeMatches,成功返回0,错误返回-1
            result = soap_recv___wsdd__ProbeMatches(soap, &resp);
            printf(" --soap_recv___wsdd__ProbeMatches() result=%d \n",result);
            if(result == -1)
            {
                printf("Find %d devices!\n", count);
                break;
            }
            else
            {
                //读取服务器回应的Probematch消息
                printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
                printf("Target EP Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
                printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
                printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
                printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
                printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
                count++;
            }
        }while(1);
    }
    //清除soap
    // clean up and remove deserialized data
    soap_end(soap);
    //detach and free runtime context
    soap_free(soap);

    return result;
}

3.2 项目结构

zy@LS2-R910CQQT:/mnt/d/code/onvif_cgo$ tree ./
./
├── CMakeLists.txt
├── build
│   ├── CMakeCache.txt
│   ├── CMakeFiles
│   │   ├── 3.16.3
│   │   │   ├── CMakeCCompiler.cmake
│   │   │   ├── CMakeCXXCompiler.cmake
│   │   │   ├── CMakeDetermineCompilerABI_C.bin
│   │   │   ├── CMakeDetermineCompilerABI_CXX.bin
│   │   │   ├── CMakeSystem.cmake
│   │   │   ├── CompilerIdC
│   │   │   │   ├── CMakeCCompilerId.c
│   │   │   │   ├── a.out
│   │   │   │   └── tmp
│   │   │   └── CompilerIdCXX
│   │   │       ├── CMakeCXXCompilerId.cpp
│   │   │       ├── a.out
│   │   │       └── tmp
│   │   ├── CMakeDirectoryInformation.cmake
│   │   ├── CMakeOutput.log
│   │   ├── CMakeTmp
│   │   ├── Makefile.cmake
│   │   ├── Makefile2
│   │   ├── TargetDirectories.txt
│   │   ├── cmake.check_cache
│   │   ├── gsoap-onvif-discover.dir
│   │   │   ├── C.includecache
│   │   │   ├── CXX.includecache
│   │   │   ├── DependInfo.cmake
│   │   │   ├── build.make
│   │   │   ├── client.c.o
│   │   │   ├── cmake_clean.cmake
│   │   │   ├── depend.internal
│   │   │   ├── depend.make
│   │   │   ├── flags.make
│   │   │   ├── link.txt
│   │   │   ├── progress.make
│   │   │   └── soap
│   │   │       ├── soapC.c.o
│   │   │       ├── soapClient.c.o
│   │   │       └── stdsoap2.c.o
│   │   └── progress.marks
│   ├── Makefile
│   ├── cmake_install.cmake
│   └── gsoap-onvif-discover
├── client.c
├── client.c.bak
└── soap
    ├── RemoteDiscoveryBinding.nsmap
    ├── libgsoap.a
    ├── onvif.h
    ├── soapC.c
    ├── soapClient.c
    ├── soapH.h
    ├── soapStub.h
    ├── stdsoap2.c
    ├── stdsoap2.h
    └── wsdd.nsmap

11 directories, 47 files
  • build目录下是cmake编译产生的相关内容,在该目录下执行:cmake ..,make即可,便于将代码和编译内容分开,最终生成的可执行文件也在该目录下;

  • gSoap框架产生的文件和拷贝的内容都放在soap文件夹下,执行上述命令即可;

3.3 CMakeLists.txt如下

cmake_minimum_required(VERSION 3.0)
project(onvif-cgo)

aux_source_directory(./soap/ SRC_LIST)
link_directories(~/)
add_executable(gsoap-onvif-discover  ${SRC_LIST} client.c)

3.4 整个过程的截图

在这里插入图片描述

关于生成c++代码或服务端代码这部分有需要的可以参考官方的示例做下尝试,基本和上面的流程是类似的。

4. 实现cgo代码实例并运行测试

可以看到我们已经成功利用gSoap生成框架代码并写了简单的服务发现客户端测试发现设备成功了,接下来我们使用cgo调用刚才的服务发现客户端C代码来做个简单的交互即可,后续的其它功能也基本都按照这个流程即可。

cgo除了我们在第一节用到的方式外,也可以像c/c++不同模块间引用头文件或者链接库的方式进行不同模块相互调用函数接口那样,使用go引入c/c++的头文件,然后调用对应模块的接口函数,所以这里我们稍微修改一下刚才的client.c,将main函数修改为普通的函数并提供client.h,然后后续都将C代码编译为静态库提供给go来调用。

4.1 c代码简单封装

这里我们先简单做下封装,实际项目中再做下优化:

client.h:

//
// Created by admin on 2022/3/3.
//

#ifndef ONVIF_CGO_CLIENT_H
#define ONVIF_CGO_CLIENT_H

extern int discovery();

#endif //ONVIF_CGO_CLIENT_H

client.c:

//
// Created by admin on 2022/3/3.
//

#include "soap/soapStub.h"
#include "soap/wsdd.nsmap"
#include "soap/soapH.h"
#include "client.h"

//probe消息仿照 soap_wsdd_Probe 函数编写
int discovery()
{
    //soap环境变量
    struct soap *soap;

    //发送消息描述
    struct wsdd__ProbeType req;
    struct wsdd__ProbeType wsdd__Probe;

    struct __wsdd__ProbeMatches resp;

    //描述查找那类的Web消息
    struct wsdd__ScopesType sScope;

    //soap消息头消息
    struct SOAP_ENV__Header  header;

    //获得的设备信息个数
    int count = 0;

    //返回值
    int result = 0;

    //存放uuid 格式(8-4-4-4-12)
    char uuid_string[64];

    printf("%s: %d 000: \n", __FUNCTION__, __LINE__);
    sprintf(uuid_string, "464A4854-4656-5242-4530-110000000000");
    printf("uuid = %s \n", uuid_string);

    //soap初始化,申请空间
    soap = soap_new();
    if(soap == NULL)
    {
        printf("malloc soap error \n");
        return -1;
    }

    soap_set_namespaces(soap, namespaces);  //设置命名空间,就是xml文件的头
    soap->recv_timeout = 5;  //超出5s没数据就推出,超时时间

    //将header设置为soap消息,头属性,暂且认为是soap和header绑定
    soap_default_SOAP_ENV__Header(soap, &header);
    header.wsa__MessageID = uuid_string;
    header.wsa__To = "urn:schemas-xmlsoap-org:ws:2005:04:discovery";
    header.wsa__Action = "http://schemas.xmllocal_soap.org/ws/2005/04/discovery/Probe";
    //设置soap头消息的ID
    soap->header = &header;

    /* 设置所需寻找设备的类型和范围,二者至少设置一个
        否则可能收到非ONVIF设备,出现异常
     */

    //设置soap消息的请求服务属性
    soap_default_wsdd__ScopesType(soap, &sScope);
    sScope.__item = "onvif://www.onvif.org";
    soap_default_wsdd__ProbeType(soap, &req);
    req.Scopes = &sScope;

    /* 设置所需设备的类型,ns1为命名空间前缀,在wsdd.nsmap 文件中
       {"tdn","http://www.onvif.org/ver10/network/wsdl"}的tdn,如果不是tdn,而是其它,
       例如ns1这里也要随之改为ns1
    */
    req.Types = "ns1:NetworkVideoTransmitter";

    //调用gSoap接口 向 239.255.255.250:3702 发送udp消息
    result = soap_send___wsdd__Probe(soap,  "soap.udp://239.255.255.250:3702/", NULL, &req);

    if(result == -1)
    {
        printf("soap error: %d, %s, %s \n", soap->error, *soap_faultcode(soap), *soap_faultstring(soap));
        result = soap->error;
    }
    else
    {
        do{
            printf("%s: %d, begin receive probematch... \n", __FUNCTION__, __LINE__);
            printf("count = %d \n", count);

            //接收 ProbeMatches,成功返回0,错误返回-1
            result = soap_recv___wsdd__ProbeMatches(soap, &resp);
            printf(" --soap_recv___wsdd__ProbeMatches() result=%d \n",result);
            if(result == -1)
            {
                printf("Find %d devices!\n", count);
                break;
            }
            else
            {
                //读取服务器回应的Probematch消息
                printf("soap_recv___wsdd__Probe: __sizeProbeMatch = %d \n", resp.wsdd__ProbeMatches->__sizeProbeMatch);
                printf("Target EP Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->wsa__EndpointReference.Address);
                printf("Target Type : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Types);
                printf("Target Service Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->XAddrs);
                printf("Target Metadata Version: %d \n", resp.wsdd__ProbeMatches->ProbeMatch->MetadataVersion);
                printf("Target Scope Address : %s \n", resp.wsdd__ProbeMatches->ProbeMatch->Scopes->__item);
                count++;
            }
        }while(1);
    }
    //清除soap
    // clean up and remove deserialized data
    soap_end(soap);
    //detach and free runtime context
    soap_free(soap);

    return result;
}

4.2 cmake修改

CMakeLists.txt修改,将上述c代码编译为静态库和动态库:

cmake_minimum_required(VERSION 3.0)
project(onvif-cgo)

aux_source_directory(./soap/ SRC_LIST)
link_directories(~/)
#add_executable(gsoap-onvif-discover  ${SRC_LIST} client.c)
ADD_LIBRARY(c_onvif SHARED ${SRC_LIST} client.c)
ADD_LIBRARY(c_onvif_static STATIC ${SRC_LIST} client.c)

4.3 cgo代码及编译

go代码:

package main

/*
#cgo CFLAGS: -I ./
#cgo LDFLAGS: -L ./build -lc_onvif_static
#include "client.h"
*/
import "C"
import (
    "fmt"
)

func main() {
    ret := C.discovery()
    fmt.Println(ret)
}

编译:

GOOS=linux GOARCH=amd64 CGO_ENABLE=1 go build -o onvif_cgo main.go

4.4 整个过程

在这里插入图片描述

5. 最后

接下来就比较简单了,主体流程走通了之后,之后其它的鉴权、连接、设备信息获取及设置、获取profileToken、ptz、获取rtsp流地址、抓拍地址、预置点、网络信息获取及设置等等都可以分模块尝试实现了并封装cgo交互接口了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昵称系统有问题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值