Go语言实现Onvif服务端:1、提供网络发现服务

2023-11-04

Go语言实现Onvif服务端:1、提供网络发现服务


1、前言

该功能我们之前学习Onvif协议和WS-Discovery时已经有了一定的基础了,接下来我们就是根据学习到的协议进行服务实现即可。

基本思路如下:

  • 1、同一网段中维持一个固定地址值的UDP组播监听;固定地址值:239.255.255.250,端口:3702
  • 2、当收到消息后进行内容解析,判断是否满足协议规范;
  • 3、解析接收到的消息,获取客户端的关键信息;
  • 4、按照协议规范打包,发送给客户端

2、代码

读取客户端发送的设备探测消息时,主要是获取探测消息的Message ID,也就是uuid,这个之前我们回顾WS-Discovery协议时已经有了判断,还是不太明白的可以看一下Onvif2.0协议的中文版。回复探测消息时主要是注意是注意设备类型以及回复的uuid等,如果同一设备两次回复的uuid不一样则会被当成两个设备。

package main

import (
	"fmt"
	"github.com/beevik/etree"
	UUID "github.com/satori/go.uuid"
	"net"
	"time"
)

func main() {
	addr, err := net.ResolveUDPAddr("udp", "239.255.255.250:3702")
	if err != nil {
		fmt.Println(err)
	}

	go func() {
		listener, err := net.ListenMulticastUDP("udp", nil, addr)
		if err != nil {
			fmt.Println(err)
			return
		}
		fmt.Printf("Local: <%s> \n", listener.LocalAddr().String())
		urnUUID := UUID.Must(UUID.NewV4()).String()
		metaVersion := "1"

		go udpReadWrite(listener, urnUUID, metaVersion)
		time.Sleep(3 * time.Second)
	}()

	select {}
}

func readFromXml(message string) string {
	doc := etree.NewDocument()
	if err := doc.ReadFromString(message); err != nil {
		return ""
	}
	root := doc.SelectElement("Envelope")
	if root == nil {
		return ""
	}
	header := root.FindElements("./Header/MessageID")
	uuid := ""
	for _, node := range header {
		fmt.Println(node.Text())
		uuid = node.Text()
	}

	return uuid
}

/**
 * @Description: 读取发送到组中的地址并进行判断返回
 * @time: 2021-04-01 16:59:05
 * @param listener
 */
func udpReadWrite(listener *net.UDPConn, urn string, metaVersion string) {
	data := make([]byte, 1024)
	messageNum := 0
	for {
		n, remoteAddr, err := listener.ReadFromUDP(data)
		if err != nil {
			fmt.Printf("error during read: %s", err)
			return
		}
		fmt.Printf("<%s>\n", remoteAddr)
		uuid := readFromXml(string(data[:n]))
		if uuid == "" {
			fmt.Println("uuid is nil.")
			return
		}
		messageNum++

		str := "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
			"<env:Envelope xmlns:env=\"http://www.w3.org/2003/05/soap-envelope\"\n" +
			"              xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\"\n" +
			"              xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\"\n" +
			"              xmlns:wsadis=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">\n" +
			"  <env:Header>\n" +
			"    <wsadis:MessageID>urn:uuid:" + urn + "</wsadis:MessageID>\n" +
			"    <wsadis:RelatesTo>" + uuid + "</wsadis:RelatesTo>\n" +
			"    <wsadis:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsadis:To>\n" +
			"    <wsadis:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsadis:Action>\n" +
			"    <d:AppSequence InstanceId=\"1617190918\"\n" +
			"                   MessageNumber=\"1\" />\n" +
			"  </env:Header>\n" +
			"  <env:Body>\n" +
			"    <d:ProbeMatches>\n" +
			"      <d:ProbeMatch>\n" +
			"        <wsadis:EndpointReference>\n" +
			"          <wsadis:Address>urn:uuid:" + urn + "</wsadis:Address>\n" +
			"        </wsadis:EndpointReference>\n" +
			"        <d:Types>dn:NetworkVideoTransmitter</d:Types>\n" +
			"        <d:Scopes>onvif://www.onvif.org/type/NetworkVideoTransmitter</d:Scopes>\n" +
			"        <d:XAddrs>http://" + getIP() + "/onvif/device_service</d:XAddrs>\n" +
			"        <d:MetadataVersion>" + metaVersion + "</d:MetadataVersion>\n" +
			"      </d:ProbeMatch>\n" +
			"    </d:ProbeMatches>\n" +
			"  </env:Body>\n" +
			"</env:Envelope>"
		fmt.Println(str)
		_, err = listener.WriteToUDP([]byte(str), remoteAddr)
		if err != nil {
			fmt.Println("write to udp failed: ", err)
		}
	}
}

/**
 * @Description: 获取本机IPV4地址
 * @time: 2021-04-01 16:55:16
 * @return string
 */
func getIP() string {
	netInterfaces, err := net.Interfaces()
	if err != nil {
		fmt.Println("net.Interfaces failed, err:", err.Error())
		return ""
	}

	for i := 0; i < len(netInterfaces); i++ {
		if (netInterfaces[i].Flags & net.FlagUp) != 0 {
			addrs, _ := netInterfaces[i].Addrs()

			for _, address := range addrs {
				if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
					if ipnet.IP.To4() != nil {
						return ipnet.IP.String()
					}
				}
			}
		}
	}

	return ""
}

3、结果

发送的xml如下,主要是通过抓包摄像头的回复报文进行的修改,这里的Types有协议标准,无法随意修改:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"
              xmlns:dn="http://www.onvif.org/ver10/network/wsdl"
              xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery"
              xmlns:wsadis="http://schemas.xmlsoap.org/ws/2004/08/addressing">
  <env:Header>
    <wsadis:MessageID>urn:uuid:70b4a703-374c-45b6-859c-f889a5528f51</wsadis:MessageID>
    <wsadis:RelatesTo>uuid:3b596bde-fa7b-4c68-a044-d6b483045c6c</wsadis:RelatesTo>
    <wsadis:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsadis:To>
    <wsadis:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches</wsadis:Action>
    <d:AppSequence InstanceId="1617190918"
                   MessageNumber="1" />
  </env:Header>
  <env:Body>
    <d:ProbeMatches>
      <d:ProbeMatch>
        <wsadis:EndpointReference>
          <wsadis:Address>urn:uuid:70b4a703-374c-45b6-859c-f889a5528f51</wsadis:Address>
        </wsadis:EndpointReference>
        <d:Types>dn:NetworkVideoTransmitter</d:Types>
        <d:Scopes>onvif://www.onvif.org/type/NetworkVideoTransmitter</d:Scopes>
        <d:XAddrs>http://40.40.40.102/onvif/device_service</d:XAddrs>
        <d:MetadataVersion>1</d:MetadataVersion>
      </d:ProbeMatch>
    </d:ProbeMatches>
  </env:Body>
</env:Envelope>

通过ONVIF Device Test Tool测试的结果:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go语言实现Onvif服务端:1、提供网络发现服务 的相关文章