You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用Java基于WS-Discovery规范发现ONVIF设备并获取详情

你的方向完全正确!咱们一步步来解决Java实现ONVIF设备发现的问题:

先给你吃个定心丸:你的认知没有偏差

ONVIF设备发现确实遵循WS-Discovery协议,核心流程就是向多播地址 239.255.255.259:3702 发送Probe SOAP消息,设备返回的ProbeMatch中的XAddrs字段就是你要的设备服务地址(注意:这不是单纯的IP,是包含服务路径的完整URL,比如http://192.168.1.100/onvif/device_service,后续调用设备管理WSDL生成的代码时就用这个地址)。

另外你写的Probe SOAP消息基本正确,只有一个小细节要修正:w:Action标签里的a:mustUnderstand应该改成e:mustUnderstand(因为你没有声明a命名空间,而e是SOAP信封的命名空间),修正后的完整消息如下:

<e:Envelope xmlns:e="http://www.w3.org/2003/05/soap-envelope" xmlns:w="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:d="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:dn="http://www.onvif.org/ver10/network/wsdl">
<e:Header>
<w:MessageID>uuid:84ede3de-7dec-11d0-c360-f01234567890</w:MessageID>
<w:To e:mustUnderstand="true">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>
<w:Action e:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>
</e:Header>
<e:Body>
<d:Probe>
</d:Probe>
</e:Body>
</e:Envelope>

Java实现具体步骤

下面是完整的Java代码实现,分为发送Probe消息接收解析响应两部分:

1. 发送UDP多播消息

Java中用MulticastSocket处理多播通信,步骤很清晰:

  • 创建MulticastSocket并配置超时(避免无限等待)
  • 加入目标多播组239.255.255.259
  • 生成唯一的MessageID(用UUID保证唯一性)
  • 将SOAP消息转为字节数组,发送到多播地址和端口
import java.net.*;
import java.io.IOException;
import java.util.UUID;

public class ONVIFDiscovery {
    private static final String MULTICAST_ADDRESS = "239.255.255.259";
    private static final int PORT = 3702;
    private static final int TIMEOUT = 5000; // 5秒超时,可根据网络调整

    public static void main(String[] args) throws IOException {
        // 生成唯一的MessageID,每次发送都要换
        String messageId = "uuid:" + UUID.randomUUID();
        
        // 构造修正后的SOAP消息
        String soapMessage = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
                "<e:Envelope xmlns:e=\"http://www.w3.org/2003/05/soap-envelope\" " +
                "xmlns:w=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " +
                "xmlns:d=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " +
                "xmlns:dn=\"http://www.onvif.org/ver10/network/wsdl\">" +
                "<e:Header>" +
                "<w:MessageID>" + messageId + "</w:MessageID>" +
                "<w:To e:mustUnderstand=\"true\">urn:schemas-xmlsoap-org:ws:2005:04:discovery</w:To>" +
                "<w:Action e:mustUnderstand=\"true\">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</w:Action>" +
                "</e:Header>" +
                "<e:Body>" +
                "<d:Probe>" +
                "</d:Probe>" +
                "</e:Body>" +
                "</e:Envelope>";

        // 初始化多播Socket,使用try-with-resources自动关闭资源
        try (MulticastSocket socket = new MulticastSocket()) {
            socket.setSoTimeout(TIMEOUT);
            InetAddress group = InetAddress.getByName(MULTICAST_ADDRESS);
            socket.joinGroup(group);

            // 发送Probe消息
            DatagramPacket packet = new DatagramPacket(
                    soapMessage.getBytes("UTF-8"),
                    soapMessage.getBytes("UTF-8").length,
                    group,
                    PORT
            );
            socket.send(packet);
            System.out.println("Probe消息已发送,等待设备响应...");

            // 循环接收响应,直到超时
            byte[] buffer = new byte[4096];
            while (true) {
                try {
                    DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
                    socket.receive(responsePacket);
                    String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");
                    
                    // 解析响应中的设备服务地址
                    parseDeviceServiceAddress(response);
                } catch (SocketTimeoutException e) {
                    System.out.println("\n超时,设备发现结束");
                    break;
                }
            }

            socket.leaveGroup(group);
        }
    }

2. 解析响应中的设备服务地址

收到的响应是ProbeMatch格式的SOAP消息,我们需要从中提取XAddrs字段。这里用Java的DOM解析实现(支持命名空间):

private static void parseDeviceServiceAddress(String soapResponse) {
        try {
            javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(true); // 必须开启命名空间支持,否则找不到对应标签
            javax.xml.parsers.DocumentBuilder builder = factory.newDocumentBuilder();
            org.w3c.dom.Document doc = builder.parse(new java.io.ByteArrayInputStream(soapResponse.getBytes("UTF-8")));

            // 定义WS-Discovery的命名空间
            String discoveryNs = "http://schemas.xmlsoap.org/ws/2005/04/discovery";
            org.w3c.dom.NodeList probeMatchList = doc.getElementsByTagNameNS(discoveryNs, "ProbeMatch");

            for (int i = 0; i < probeMatchList.getLength(); i++) {
                org.w3c.dom.Element probeMatch = (org.w3c.dom.Element) probeMatchList.item(i);
                org.w3c.dom.Node xAddrsNode = probeMatch.getElementsByTagNameNS(discoveryNs, "XAddrs").item(0);
                if (xAddrsNode != null) {
                    String xAddrs = xAddrsNode.getTextContent().trim();
                    // XAddrs可能包含多个地址(空格分隔),取第一个即可
                    String serviceAddress = xAddrs.split(" ")[0];
                    System.out.println("\n找到设备服务地址:" + serviceAddress);
                }
            }
        } catch (Exception e) {
            System.err.println("解析响应出错:");
            e.printStackTrace();
        }
    }
}
关键注意事项
  • 防火墙权限:确保你的程序有权限发送/接收UDP多播消息,防火墙要允许UDP 3702端口的通信
  • UUID唯一性:每次发送Probe消息必须生成新的UUID作为MessageID,避免和历史请求混淆
  • 网络环境:多播消息通常不会跨路由传播,确保你的程序和ONVIF设备在同一个局域网内
  • XAddrs格式:部分设备会返回多个地址,一般取第一个作为设备服务地址即可

内容的提问来源于stack exchange,提问作者Jared H.

火山引擎 最新活动