经过反复试验,我找到了(另一种)解决我的问题的方法。在大多数情况下,mpromonet 可能是要走的路,我只是想避免使用像 Apache 这样相当大的依赖项。我还认为这应该可以通过一些简单的 UDP 消息传递来实现。
该解决方案也基于 SO 用户 Thomas 的有用代码。我主要只是通过删除线程来简化他的代码,并添加了一些注释。同样,他的解决方案可能比我的更好(性能更高);但是,对于初学者(比如我)来说,我的可能更容易理解。
这是代码:
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.net.*;
import javax.xml.namespace.QName;
import javax.xml.soap.*;
import java.util.*;
public class ONVIFDeviceDiscoveryFIN {
// Following constants are related to Discovery process
public static final int WS_DISCOVERY_TIMEOUT = 4000; // 4 seconds. Time to wait to receive a packet
public static final int WS_DISCOVERY_PORT = 3702;
public static final String WS_DISCOVERY_ADDRESS_IPv4 = "239.255.255.250";
// note that the probe below MUST be given a unique urn:uuid. Devices will NOT reply if the urn:uuid is not unique!
public static final String WS_DISCOVERY_PROBE_MESSAGE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
"<soap:Envelope xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" xmlns:tds=\"http://www.onvif.org/ver10/device/wsdl\" xmlns:tns=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\">\r\n" +
" <soap:Header>\r\n" +
" <wsa:Action>http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>\r\n" +
" <wsa:MessageID>urn:uuid:5e1cec36-03b9-4d8b-9624-0c5283982a00</wsa:MessageID>\r\n" +
" <wsa:To>urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>\r\n" +
" </soap:Header>\r\n" +
" <soap:Body>\r\n" +
" <tns:Probe>\r\n" +
" <tns:Types>tds:Device</tns:Types>\r\n" + // Constraint to find just ONVIF devices hopefully? Recall we are sending a probe on the 192.168.0.50 network; if we have no constraints, it would find everything there! WS-Discovery generally is for much more than ONVIF, like printers and stuff
" </tns:Probe>\r\n" +
" </soap:Body>\r\n" +
"</soap:Envelope>";
private static ArrayList<String> getResponsesToProbe(String uuid) throws IOException{
// TODO: add in ability to send scope and type constraints
// NOTE: We do need to know the address of the network interface to discover devices on...
// Function composes and sends a Probe to discover devices on the network. uuid is the urn:uuid to put in the probe. Functions returns all the SOAP-Infused XML responses (all the ProbeMatches).
// give the probe a unique urn:uuid (we must do this for each probe!). This is generated outside function
final String probe = WS_DISCOVERY_PROBE_MESSAGE.replaceAll("<wsa:MessageID>urn:uuid:.*</wsa:MessageID>", "<wsa:MessageID>urn:uuid:" + uuid + "</wsa:MessageID>");
// set up the "sender and receiver"; this is the socket that we send our probe from, and where we receive back the ProbeMatch responses.
// NOTE: that we do need to know the address of the network interface to discover devices on... (port could be anything)
final int port = 55000;
DatagramSocket senderAndReceiver = new DatagramSocket(port, InetAddress.getByName("192.168.0.50")); // so you do need to know the address of your network interface to discover devices on...
senderAndReceiver.setSoTimeout(WS_DISCOVERY_TIMEOUT);
// send the probe
DatagramPacket probeMsg = new DatagramPacket(probe.getBytes(), probe.length(), InetAddress.getByName(WS_DISCOVERY_ADDRESS_IPv4), WS_DISCOVERY_PORT);
senderAndReceiver.send(probeMsg);
// read in the responses
ArrayList<String> responses = new ArrayList(); // this is the collection of all SOAP-infused XML ProbeMatch responses
byte[] receiverBuffer = new byte[8192];
DatagramPacket receiverPacket = new DatagramPacket(receiverBuffer, receiverBuffer.length); // this is the packet that receive the response in. Get's updated with the next response on each call to .receive()
while (true) {
try {
senderAndReceiver.receive(receiverPacket);
responses.add(new String(receiverPacket.getData()));
} catch (SocketTimeoutException e) {
// System.out.println("Socket read timeout; taken to mean that there is no more responses -- i.e., no more Probe Matches");
break;
}
}
// close the socket
senderAndReceiver.close();
return responses;
}
public static void main(String[] args) throws IOException, SOAPException {
final String uuid = UUID.randomUUID().toString(); // generate the uuid to add to the Probe message
ArrayList<String> responses = getResponsesToProbe(uuid); // responses is a collection of all the SOAP-infused XML ProbeMatches . It's all of our responses to the probe; it's basically the devices we've discovered!
}
}
关于使用它的一些注意事项:
要使用此解决方案,您需要知道要查看的“网络接口”。在我的代码中,这是 192.168.0.50 。这是我要查找的相机所在的网络。要找到它,请在 cmd 提示符下运行arp -a
命令(不确定如何在 Mac 或 Linux 上执行此操作),然后找到相机的 IP。它所属的接口是您要用作“192.168.0.50”的接口。在我有限的理解中,这些接口基本上是对您的网络进行分段,因此您需要选择正确的接口来查找设备。我认为(?)Thomas 的代码通过查找所有这些网络接口来避免这个问题。这是在他的代码的第 81-100 行中完成的。
发送时,您必须为您的 Probe 提供一个唯一的 UUID。这是我这样做的错误之一;我在 Probe(在WS_DISCOVERY_PROBE_MESSAGE
)中使用硬编码的 UUID 进行了测试。这将有助于发现设备一次;但在那之后,如果您发送具有相同 UUID 的探测,设备似乎根本不会回复。你也不会得到任何错误响应,因此我很难找出答案。就好像设备保留了所有接收到的 Probe 的 UUID 的一些内部日志;如果您发送带有旧 UUID 的探测,它只会拒绝它。或者至少,我正在测试的符合 ONVIF 标准的相机(AXIS M3045-V)就是这种情况。我不确定 ONVIF 规范是否需要这种行为,但至少在 AXIS M3045-V 中很明显。
注意:SOAP 通常依赖 HTTP 进行传输;但这里我们在 UDP 之上使用它。
我希望这可以帮助任何尝试做类似事情的人。如果有什么我可以帮忙的,请告诉我;在这一点上,我已经阅读了大量的文档,所以我可以伸出援助之手!