[英]How to use WS-Discovery spec to discover ONVIF devices on a network in Java?
我正在嘗試使用一些Java代碼來發現ONVIF設備。 具體來說,我正在嘗試獲取他們的設備服務地址(我相信這只是他們的IP地址?),正如ONVIF核心規范 (在第4.3節中)指出的那樣:“成功的發現將提供設備服務地址。設備服務地址,它可以通過設備服務接收詳細的設備信息...”。 我的目標是最終獲得網絡上ONVIF設備的詳細信息。 通常,我也在尋找有關使用ONVIF規范的指導。
我仍然對Web服務世界(以及一般而言的網絡)還是陌生的,所以請原諒我。 但是,我本人為此付出了很多努力:我閱讀了許多ONVIF核心規范 , ONVIF應用程序員指南和WS-Discovery規范 。 如果可以的話,我將總結一下我所知道的,以便您可以告訴我我是否處在正確的軌道上:
wsimport
(由JDK提供)從WSDL中生成了Java類,以便在我的代碼中使用。 但我知道調用這些方法將在設備發現之后進行,對嗎? Probe
消息,並且符合Probe約束的設備會發回ProbeMatch
消息,如《 ONVIF應用程序程序員指南》第13和14頁所述 這是我開始感到困惑的地方。 我如何用Java發送此消息? 《 ONVIF應用程序程序員指南》在第15頁提供了一些偽代碼,但我不知道如何實現它。 該指南明確指出了該指南中的第4.3.1節。 我了解“作用域”和“類型”只是您可以嵌入探針中的約束,但不是必需的(根據WS Discovery規范的第5頁)。 因為我想發現所有設備,所以我認為啟動時不需要任何約束,對嗎?
因此,該指南還在第110頁上提供了用於發現的示例SOAP消息。 從中刪除類型聲明(因為我不想使用該約束),我知道要發送的SOAP消息是(我相信嗎?):
<?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>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
a:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2005/04/discovery/Pr
obe</w:Action>
</e:Header>
<e:Body>
<d:Probe>
</d:Probe>
</e:Body>
</e:Envelope>
而且我也了解WS-Discovery技術使用的239.255.255.259地址和UDP端口3702 ...但這就是我的意思。 如何將SOAP消息發送到Java中的該地址和端口? 我如何讀取響應(我想我會以SOAP注入的XML文檔的形式返回ProbeMatch消息,因此我需要解析該XML以獲得XAddrs
,但不確定)。 我是否需要以某種方式將該SOAP消息的UDP廣播發送到該地址?
TL; DR :我相信要進行ONVIF設備發現,我需要將上述SOAP消息發送至UDP端口3702上的地址239.255.255.259。我不知道如何在Java中進行此操作,只是在尋找一些指導; 我什至不確定自己是否在進行設備發現的正確道路上。
使用CXF WSDiscoveryClient您可以探測ONVIF設備。
默認情況下,WSDiscoveryClient使用WS-discovery 1.1,而ONVIF使用WS-discovery 1.0,因此您需要啟用WS-discovery 1.0。
一個簡短的實現可能是:
import java.util.List;
import javax.xml.ws.EndpointReference;
import org.apache.cxf.ws.discovery.WSDiscoveryClient;
public class Main
{
public static void main(String[] args)
{
WSDiscoveryClient client = new WSDiscoveryClient();
client.setVersion10(); // use WS-discovery 1.0
client.setDefaultProbeTimeout(1000); // timeout 1s
System.out.println("Probe:" + client.getAddress());
List<EndpointReference> references = client.probe();
System.out.println("Nb answsers:" + references.size());
for (EndpointReference ref : references)
{
System.out.println(ref.toString());
}
}
}
經過反復試驗,我找到了另一個解決方案。 在大多數情況下,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行中完成的。
發送時,您必須給探針一個唯一的UUID。 這是我這樣做的錯誤之一; 我在Probe(在WS_DISCOVERY_PROBE_MESSAGE
)中使用了硬編碼的UUID進行了測試。 這將一次發現設備; 但是之后,如果您發送具有相同UUID的探測,則設備似乎根本不會回復。 您也不會收到任何錯誤響應,因此為什么我很難找出答案。 就像設備保留了所有收到的所有探針的UUID的內部日志一樣。 如果您發送帶有舊UUID的探測,它只會拒絕它。 至少,對於我正在測試的符合ONVIF的攝像機(AXIS M3045-V),情況就是如此。 我不確定ONVIF規范是否需要這種行為,但是至少在AXIS M3045-V中是明顯的。
注意:SOAP通常依賴HTTP進行傳輸。 但是這里我們在UDP之上使用它。
希望這對嘗試做類似事情的人有所幫助。 讓我知道我有什么可以幫助的; 此時,我已經閱讀了大量文檔,因此我可以伸出援手!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.