扫描
扫描,其实就是对发现的可见和可用的UPnP设备进行访问,扫描对应的UPnP设备描述和UPnP设备中包含的services描述文档;
这个过程不是client或者是server手动触发的,而是在一定情况下触发的,比如在发现可见和可用的UPnP设备时主动请求设备描述文档和服务描述文档,使用的request.method为GET;
其实在发现阶段执行最后的时候,执行了一个Runnable,这一步其实就是扫描阶段的开始,发现和扫描阶段的执行是绑定的,不需要任何外部条件触发,例如人为的点击事件等:
getUpnpService().getConfiguration().getAsyncProtocolExecutor().execute(
new RetrieveRemoteDescriptors(getUpnpService(), rd)
);
RetrieveRemoteDescriptors
RetrieveRemoteDescriptors为Runnable,我们看一下其中的run()方法:
public void run() {
URL deviceURL = rd.getIdentity().getDescriptorURL();
// Performance optimization, try to avoid concurrent GET requests for device descriptor,
// if we retrieve it once, we have the hydrated device. There is no different outcome
// processing this several times concurrently.
if (activeRetrievals.contains(deviceURL)) {
log.finer("Exiting early, active retrieval for URL already in progress: " + deviceURL);
return;
}
// Exit if it has been discovered already, could be we have been waiting in the executor queue too long
if (getUpnpService().getRegistry().getRemoteDevice(rd.getIdentity().getUdn(), true) != null) {
log.finer("Exiting early, already discovered: " + deviceURL);
return;
}
try {
activeRetrievals.add(deviceURL);
describe();
} catch (RouterException ex) {
log.log(Level.WARNING,
"Descriptor retrieval failed: " + deviceURL,
ex
);
} finally {
activeRetrievals.remove(deviceURL);
}
}
在该方法中首先先获取了deviceURL,这是Device的描述文档的资源连接,但是仅此而已,并没有实际的获取到,需要再次通过发送request获取到详细的device描述文档;
扫描Device Describe
方法中调用的describe()方法:
protected void describe() throws RouterException {
// All of the following is a very expensive and time consuming procedure, thanks to the
// braindead design of UPnP. Several GET requests, several descriptors, several XML parsing
// steps - all of this could be done with one and it wouldn't make a difference. So every
// call of this method has to be really necessary and rare.
if(getUpnpService().getRouter() == null) {
log.warning("Router not yet initialized");
return ;
}
StreamRequestMessage deviceDescRetrievalMsg;
StreamResponseMessage deviceDescMsg;
try {
deviceDescRetrievalMsg =
new StreamRequestMessage(UpnpRequest.Method.GET, rd.getIdentity().getDescriptorURL());
// Extra headers
UpnpHeaders headers =
getUpnpService().getConfiguration().getDescriptorRetrievalHeaders(rd.getIdentity());
if (headers != null)
deviceDescRetrievalMsg.getHeaders().putAll(headers);
log.fine("Sending device descriptor retrieval message: " + deviceDescRetrievalMsg);
deviceDescMsg = getUpnpService().getRouter().send(deviceDescRetrievalMsg);
} catch(IllegalArgumentException ex) {
// UpnpRequest constructor can throw IllegalArgumentException on invalid URI
// IllegalArgumentException can also be thrown by Apache HttpClient on blank URI in send()
log.warning(
"Device descriptor retrieval failed: "
+ rd.getIdentity().getDescriptorURL()
+ ", possibly invalid URL: " + ex);
return ;
}
if (deviceDescMsg == null) {
log.warning(
"Device descriptor retrieval failed, no response: " + rd.getIdentity().getDescriptorURL()
);
return;
}
if (deviceDescMsg.getOperation().isFailed()) {
log.warning(
"Device descriptor retrieval failed: "
+ rd.getIdentity().getDescriptorURL() +
", "
+ deviceDescMsg.getOperation().getResponseDetails()
);
return;
}
if (!deviceDescMsg.isContentTypeTextUDA()) {
log.fine(
"Received device descriptor without or with invalid Content-Type: "
+ rd.getIdentity().getDescriptorURL());
// We continue despite the invalid UPnP message because we can still hope to convert the content
}
String descriptorContent = deviceDescMsg.getBodyString();
if (descriptorContent == null || descriptorContent.length() == 0) {
log.warning("Received empty device descriptor:" + rd.getIdentity().getDescriptorURL());
return;
}
log.fine("Received root device descriptor: " + deviceDescMsg);
describe(descriptorContent);
}
在该方法中创建了StreamRequestMessage对象,其中封装了UpnpRequest.Method.GET,即请求类型为GET,RemoteDevice的instanceId以及device描述文档的URL;
调用了getUpnpService().getRouter().send(deviceDescRetrievalMsg),用于获取device描述文档,返回值为ResponseMessage,然后调用了describe(String descriptorXML)方法:
protected void describe(String descriptorXML) throws RouterException {
boolean notifiedStart = false;
RemoteDevice describedDevice = null;
try {
DeviceDescriptorBinder deviceDescriptorBinder =
getUpnpService().getConfiguration().getDeviceDescriptorBinderUDA10();
describedDevice = deviceDescriptorBinder.describe(
rd,
descriptorXML
);
log.fine("Remote device described (without services) notifying listeners: " + describedDevice);
notifiedStart = getUpnpService().getRegistry().notifyDiscoveryStart(describedDevice);
log.fine("Hydrating described device's services: " + describedDevice);
RemoteDevice hydratedDevice = describeServices(describedDevice);
if (hydratedDevice == null) {
if(!errorsAlreadyLogged.contains(rd.getIdentity().getUdn())) {
errorsAlreadyLogged.add(rd.getIdentity().getUdn());
log.warning("Device service description failed: " + rd);
}
if (notifiedStart)
getUpnpService().getRegistry().notifyDiscoveryFailure(
describedDevice,
new DescriptorBindingException("Device service description failed: " + rd)
);
return;
} else {
log.fine("Adding fully hydrated remote device to registry: " + hydratedDevice);
// The registry will do the right thing: A new root device is going to be added, if it's
// already present or we just received the descriptor again (because we got an embedded
// devices' notification), it will simply update the expiration timestamp of the root
// device.
getUpnpService().getRegistry().addDevice(hydratedDevice);
}
} catch (ValidationException ex) {
……………………
}
}
在该方法中解析device Descriptor,获取到device中的services list,其中就包括了ConnectionManagerService、AVTransportService、RendingControlService这3个服务,调用了describeServices(RemoteDevice currentDevice)来获取当前device中的service描述文档;
扫描Service Describe
protected RemoteDevice describeServices(RemoteDevice currentDevice)
throws RouterException, DescriptorBindingException, ValidationException {
List<RemoteService> describedServices = new ArrayList<>();
if (currentDevice.hasServices()) {
List<RemoteService> filteredServices = filterExclusiveServices(currentDevice.getServices());
for (RemoteService service : filteredServices) {
RemoteService svc = describeService(service);
// Skip invalid services (yes, we can continue with only some services available)
if (svc != null)
describedServices.add(svc);
else
log.warning("Skipping invalid service '" + service + "' of: " + currentDevice);
}
}
List<RemoteDevice> describedEmbeddedDevices = new ArrayList<>();
if (currentDevice.hasEmbeddedDevices()) {
for (RemoteDevice embeddedDevice : currentDevice.getEmbeddedDevices()) {
// Skip invalid embedded device
if (embeddedDevice == null)
continue;
RemoteDevice describedEmbeddedDevice = describeServices(embeddedDevice);
// Skip invalid embedded services
if (describedEmbeddedDevice != null)
describedEmbeddedDevices.add(describedEmbeddedDevice);
}
}
Icon[] iconDupes = new Icon[currentDevice.getIcons().length];
for (int i = 0; i < currentDevice.getIcons().length; i++) {
Icon icon = currentDevice.getIcons()[i];
iconDupes[i] = icon.deepCopy();
}
// Yes, we create a completely new immutable graph here
return currentDevice.newInstance(
currentDevice.getIdentity().getUdn(),
currentDevice.getVersion(),
currentDevice.getType(),
currentDevice.getDetails(),
iconDupes,
currentDevice.toServiceArray(describedServices),
describedEmbeddedDevices
);
}
在describeServices()方法中for循环调用了describeService(RemoteService service)方法,用于遍历扫描RemoteService描述文档:
protected RemoteService describeService(RemoteService service)
throws RouterException, DescriptorBindingException, ValidationException {
URL descriptorURL;
try {
descriptorURL = service.getDevice().normalizeURI(service.getDescriptorURI());
} catch(IllegalArgumentException e) {
log.warning("Could not normalize service descriptor URL: " + service.getDescriptorURI());
return null;
}
StreamRequestMessage serviceDescRetrievalMsg = new StreamRequestMessage(UpnpRequest.Method.GET, descriptorURL);
// Extra headers
UpnpHeaders headers =
getUpnpService().getConfiguration().getDescriptorRetrievalHeaders(service.getDevice().getIdentity());
if (headers != null)
serviceDescRetrievalMsg.getHeaders().putAll(headers);
log.fine("Sending service descriptor retrieval message: " + serviceDescRetrievalMsg);
StreamResponseMessage serviceDescMsg = getUpnpService().getRouter().send(serviceDescRetrievalMsg);
if (serviceDescMsg == null) {
log.warning("Could not retrieve service descriptor, no response: " + service);
return null;
}
if (serviceDescMsg.getOperation().isFailed()) {
log.warning("Service descriptor retrieval failed: "
+ descriptorURL
+ ", "
+ serviceDescMsg.getOperation().getResponseDetails());
return null;
}
if (!serviceDescMsg.isContentTypeTextUDA()) {
log.fine("Received service descriptor without or with invalid Content-Type: " + descriptorURL);
// We continue despite the invalid UPnP message because we can still hope to convert the content
}
String descriptorContent = serviceDescMsg.getBodyString();
if (descriptorContent == null || descriptorContent.length() == 0) {
log.warning("Received empty service descriptor:" + descriptorURL);
return null;
}
log.fine("Received service descriptor, hydrating service model: " + serviceDescMsg);
ServiceDescriptorBinder serviceDescriptorBinder =
getUpnpService().getConfiguration().getServiceDescriptorBinderUDA10();
return serviceDescriptorBinder.describe(service, descriptorContent);
}
而在该方法中,也是通过StreamRequestMessage和StreamResponseMessage的形式,请求获取到service的描述文档;
至此,扫描阶段的逻辑分析就结束了;