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不一樣則會被當成兩個裝置。
```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
通過ONVIF Device Test Tool測試的結果: