Go語言實現Onvif服務端:1、提供網路發現服務

語言: CN / TW / HK

本文已參與「新人創作禮」活動,一起開啟掘金創作之路。

1、前言

該功能我們之前學習Onvif協議和WS-Discovery時已經有了一定的基礎了,接下來我們就是根據學習到的協議進行服務實現即可。

基本思路如下:

  • 1、同一網段中維持一個固定地址值的UDP組播監聽;固定地址值:239.255.255.250,埠:3702
  • 2、當收到訊息後進行內容解析,判斷是否滿足協議規範;
  • 3、解析接收到的訊息,獲取客戶端的關鍵資訊;
  • 4、按照協議規範打包,傳送給客戶端

2、程式碼

讀取客戶端傳送的裝置探測訊息時,主要是獲取探測訊息的Message ID,也就是uuid,這個之前我們回顧WS-Discovery協議時已經有了判斷,還是不太明白的可以看一下Onvif2.0協議的中文版。回覆探測訊息時主要是注意是注意裝置型別以及回覆的uuid等,如果同一裝置兩次回覆的uuid不一樣則會被當成兩個裝置。

```go 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

urn:uuid:70b4a703-374c-45b6-859c-f889a5528f51 uuid:3b596bde-fa7b-4c68-a044-d6b483045c6c http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous http://schemas.xmlsoap.org/ws/2005/04/discovery/ProbeMatches urn:uuid:70b4a703-374c-45b6-859c-f889a5528f51 dn:NetworkVideoTransmitter onvif://www.onvif.org/type/NetworkVideoTransmitter http://40.40.40.102/onvif/device_service 1 ```

通過ONVIF Device Test Tool測試的結果:

在這裡插入圖片描述