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测试的结果: