这个过程是从发出search请求开始,到收到匹配设备返回的响应消息,最后,应用程序根据响应消息显示出RemoteDevice信息。
1,发送请求
首先,注册监听DefaultRegistryListener.java,可以统一来用添加、删除、更新本地或远端设备。
upnpService.getRegistry().addListener(deviceListRegistryListener);
它的实现类可以被多个线程并发调用,所以他应该是线程安全的。其中监听的方法都是在一个单独的线程被调用的。
如remoteDeviceAdded(),在一个新发现的设备的完整元数据可用时被调用。
然后,从应用中的upnpService.getControlPoint().search();开始。通过ControlPointImpl.java发起搜索。
不带参数的search()发起搜索类型是:STAllHeader() > "ssdp:all"。
通过DefaultUpnpServiceConfiguration.java中的,ClingExecutor实例将搜索任务被放入一个线程池。
具体搜索任务的类型,通过ProtocolFactoryImpl.java 中的createSendingSearch()来创建。
接着看SendingSearch.java,搜索数据包的信息,搜索请求消息没间隔500毫秒,发送一次,共发送5次。
execute()@SendingSearch.java
OutgoingSearchRequest.java
super(
new UpnpRequest(UpnpRequest.Method.MSEARCH),
ModelUtil.getInetAddressByName(Constants.IPV4_UPNP_MULTICAST_GROUP),
Constants.UPNP_MULTICAST_PORT
);
搜索请求,类型:"M-SEARCH"
目标地址:IPV4_UPNP_MULTICAST_GROUP = "239.255.255.250";
目标端口:UPNP_MULTICAST_PORT = 1900;
getUpnpService().getRouter().send(msg); 转到RouterImpl.java通过数据包IO接口实例发出消息。
Send()@DatagramIOImpl.java
通过DatagramProcessorImpl.java的write完成发送数据包的填充。
最后通过MulticastSocket的send完成发送。
2,接受响应
MulticastSocket是绑定了本机IP地址的套接字,能发送单播、多播数据包,也能接受单播数据包。
在RouterImpl.java中的startAddressBasedTransports()方法,对DatagramIOImpl初始化后,会将其放入线程池运行,所以直接看DatagramIOImpl的run方法。
run() @DatagramIOImpl.java
这里的socket是MulticastSocket对象,负责监听所有发送到本机IP的UDP数据包。
byte[] buf = new byte[getConfiguration().getMaxDatagramBytes()];
DatagramPacket datagram = new DatagramPacket(buf, buf.length);
socket.receive(datagram);
这里的router是RouterImpl.java对象,处理前还是由DatagramProcessorImpl.java中的read方法解读DatagramPacket,转成IncomingDatagramMessage类型对象。
router.received(datagramProcessor.read(localAddress.getAddress(), datagram));
然后看RouterImpl.java如何把接收的响应信息,回到到监听处。
根据消息类型,ProtocolFactoryImpl.java中createReceivingAsync方法负责构建搜索响应的实例。
具体是createReceivingSearchResponse(incomingResponse)方法,最终创建的ReceivingSearchResponse实例,这个实例来处理搜索响应消息的接收。
同样的,ReceivingSearchResponse实例,也会被添加到线程池执行,具体看其execute方法。
execute()@ReceivingSearchResponse.java
这里获取返回搜索响应的远端设备的描述信息,
RemoteDeviceIdentity rdIdentity = new RemoteDeviceIdentity(getInputMessage());
根据远端设备的描述信息,构建RemoteDevice对象。
rd = new RemoteDevice(rdIdentity);
因为这个时候并不知道,这个远端设备是根设备,还是嵌入设备,所以还要去解析它的描述符,然后在处理添加。具体是:
new RetrieveRemoteDescriptors(getUpnpService(), rd)
这个runnable也会在线程池被执行。
run()@RetrieveRemoteDescriptors.java
其中describe()的调用非常耗时,需要确保每次的调用都是必须的,且尽量少的调用。
出去描述符文件等的解析外,跟设备添加有关的调用是:
getUpnpService().getRegistry().addDevice(hydratedDevice);
addDevice(RemoteDevice remoteDevice) @ RegistryImpl.java
add(final RemoteDevice device) @ RemoteItems.java
如下循环完成远端设备的添加:
for (final RegistryListener listener : registry.getListeners()) {
registry.getConfiguration().getRegistryListenerExecutor().execute(
new Runnable() {
public void run() {
listener.remoteDeviceAdded(registry, device);
}
}
);
}
通过RegistryListener的具体实现类,把远端设备添加到应用列表中。如DefaultRegistryListener.java,通常应用程序都会集成这个类,并实现其中的接口:
remoteDeviceAdded(),remoteDeviceUpdated()等。