基于UPnP协议_cling开源库实现分析_宣告&发现

716 阅读15分钟

宣告&发现

宣告

本质上,宣告就是UPnP设备向局域网中告知本设备的存在,可用于Control Point的控制和显示;

UPnP设备本质上就是一个LocalDevice:

我们首先看一下demo中初始化UPnP设备的逻辑(在NoboMediaRenderer.java):

protected Map<UnsignedIntegerFourBytes, NoboMediaPlayer> mMediaPlayers;
protected LastChange mRenderingControlLastChange;
protected LastChange mAvTransportLastChange;
protected LocalServiceBinder mLocalServiceBinder;
​
protected ServiceManager<NoboConnectionManagerService> mConnectionManager;
protected LastChangeAwareServiceManager<NoboAVTransportService> mAVTransport;
protected LastChangeAwareServiceManager<NoboAudioRenderingControl> mRenderingControl;
protected LocalDevice mDevice;
​
public NoboMediaRenderer(int numberOfPlayers, Context context) {
    mContext = context;
​
    mLocalServiceBinder = new AnnotationLocalServiceBinder();
    mAvTransportLastChange = new LastChange(new AVTransportLastChangeParser());
    mRenderingControlLastChange = new LastChange(new RenderingControlLastChangeParser());
​
    mMediaPlayers = new NoboMediaPlayers(context, numberOfPlayers,
                                         mAvTransportLastChange, mRenderingControlLastChange) {
        @Override
        protected void onPlay(NoboMediaPlayer player) {
        }
​
        @Override
        protected void onStop(NoboMediaPlayer player) {
        }
    };
​
    LocalService connectionManagerService
        = mLocalServiceBinder.read(NoboConnectionManagerService.class);
    mConnectionManager = new DefaultServiceManager(connectionManagerService) {
        @Override
        protected Object createServiceInstance() throws Exception {
            return new NoboConnectionManagerService();
        }
    };
    connectionManagerService.setManager(mConnectionManager);
​
​
​
    LocalService<NoboAVTransportService> avTransportService
        = mLocalServiceBinder.read(NoboAVTransportService.class);
    mAVTransport
        = new LastChangeAwareServiceManager<NoboAVTransportService>(avTransportService,
                                                                    new AVTransportLastChangeParser()) {
        @Override
        protected NoboAVTransportService createServiceInstance() throws Exception {
            return new NoboAVTransportService(mAvTransportLastChange, mMediaPlayers);
        }
    };
    avTransportService.setManager(mAVTransport);
​
​
​
    LocalService<NoboAudioRenderingControl> renderingControlService
        = mLocalServiceBinder.read(NoboAudioRenderingControl.class);
    mRenderingControl
        = new LastChangeAwareServiceManager<NoboAudioRenderingControl>(renderingControlService,
                                                                       new AVTransportLastChangeParser()) {
        @Override
        protected NoboAudioRenderingControl createServiceInstance() throws Exception {
            return new NoboAudioRenderingControl(mRenderingControlLastChange, mMediaPlayers);
        }
    };
    renderingControlService.setManager(mRenderingControl);
​
    try {
        UDN udn = UpnpProfiles.uniqueSystemIdentifier(UpnpProfiles.UPNP_SALT);
​
        mDevice = new LocalDevice(new DeviceIdentity(udn),
                                  new UDADeviceType(DeviceProfiles.UDA_DEVICE_TYPE, DeviceProfiles.UDA_DEVICE_VERSION),
                                  new DeviceDetails(
                                      DeviceProfiles.DEVICE_FRIENDLY_NAME,
                                      new ManufacturerDetails("Cling", Build.MANUFACTURER),
                                      new ModelDetails(DeviceProfiles.DMR_MODEL_NAME, DeviceProfiles.DMR_MODEL_DESC,
                                                       DeviceProfiles.DMR_MODEL_NUMBER, DeviceProfiles.DMR_MODEL_URL),
                                      new DLNADoc[] {
                                          new DLNADoc(DeviceProfiles.DEV_CLASS, DLNADoc.Version.V1_5)
                                      },
                                      new DLNACaps(new String[] {
                                          DeviceProfiles.DLNA_CAPS_AV,
                                          DeviceProfiles.DLNA_CAPS_IMAGE,
                                          DeviceProfiles.DLNA_CAPS_AUDIO
                                      })
                                  ),
                                  new Icon[] {
                                      createDefaultDeviceIcon()
                                  },
                                  new LocalService[]{
                                      avTransportService,
                                      renderingControlService,
                                      connectionManagerService
                                  });
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
​
    runLastChangePushThread();
}

该构造方法中,一共执行了5个大的逻辑:

  • 初始化ConnectionManager Service;
  • 初始化AVTransport Service;
  • 初始化RenderingControl Service;
  • 初始化LocalDevice(UPnP设备);
  • LocalDevice发布;
初始化ConnectionManager Service
初始化ConnectionManagerService类型的LocalService

在该构造方法中,首先初始化了mLocalServiceBinder变量,该变量的类型为AnnotationLocalServiceBinder;

protected LocalServiceBinder mLocalServiceBinder;mLocalServiceBinder = new AnnotationLocalServiceBinder();

然后使用mLocalServiceBinder变量来创建LocalService对象;

LocalService connectionManagerService
    = mLocalServiceBinder.read(NoboConnectionManagerService.class);
mConnectionManager = new DefaultServiceManager(connectionManagerService) {
    @Override
    protected Object createServiceInstance() throws Exception {
        return new NoboConnectionManagerService();
    }
};
connectionManagerService.setManager(mConnectionManager);

在该过程中,首先通过mLocalServiceBinder的read()方法,传入需要创建的对象的class,创建对应的LocalService对象;

public LocalService read(Class<?> clazz) throws LocalServiceBindingException {
    log.fine("Reading and binding annotations of service implementation class: " + clazz);
​
    // Read the service ID and service type from the annotation
    if (clazz.isAnnotationPresent(UpnpService.class)) {
        // 获取指定注释类型的注释,该方法以对象的形式返回该类
        UpnpService annotation = clazz.getAnnotation(UpnpService.class);
        UpnpServiceId idAnnotation = annotation.serviceId();
        UpnpServiceType typeAnnotation = annotation.serviceType();
​
        ServiceId serviceId = idAnnotation.namespace().equals(UDAServiceId.DEFAULT_NAMESPACE)
            ? new UDAServiceId(idAnnotation.value())
            : new ServiceId(idAnnotation.namespace(), idAnnotation.value());
​
        ServiceType serviceType = typeAnnotation.namespace().equals(UDAServiceType.DEFAULT_NAMESPACE)
            ? new UDAServiceType(typeAnnotation.value(), typeAnnotation.version())
            : new ServiceType(typeAnnotation.namespace(), typeAnnotation.value(), typeAnnotation.version());
​
        boolean supportsQueryStateVariables = annotation.supportsQueryStateVariables();
​
        Set<Class> stringConvertibleTypes = readStringConvertibleTypes(annotation.stringConvertibleTypes());
​
        return read(clazz, serviceId, serviceType, supportsQueryStateVariables, stringConvertibleTypes);
    } else {
        throw new LocalServiceBindingException("Given class is not an @UpnpService");
    }
}

在该方法中根据annotation获取的idAnnotation和typeAnnotation来分别确定serviceId和serviceType;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UpnpService {
​
    UpnpServiceId serviceId();
    UpnpServiceType serviceType();
​
    boolean supportsQueryStateVariables() default true;
    Class[] stringConvertibleTypes() default {};
}

然后创建supportsQueryStateVariables和stringConvertibleTypes,idAnnotation、typeAnnotation以及stringConvertibleTypes定义在ConnectionManagerService.java中:

@UpnpService(
        serviceId = @UpnpServiceId("ConnectionManager"),
        serviceType = @UpnpServiceType(value = "ConnectionManager", version = 1),
        stringConvertibleTypes = {ProtocolInfo.class, ProtocolInfos.class, ServiceReference.class}
)

最后将这个参数传入到read()方法中;

public LocalService read(Class<?> clazz, ServiceId id, ServiceType type,
                         boolean supportsQueryStateVariables, Set<Class> stringConvertibleTypes)
    throws LocalServiceBindingException {
​
    Map<StateVariable, StateVariableAccessor> stateVariables = readStateVariables(clazz, stringConvertibleTypes);
    Map<Action, ActionExecutor> actions = readActions(clazz, stateVariables, stringConvertibleTypes);
​
    // Special treatment of the state variable querying action
    if (supportsQueryStateVariables) {
        actions.put(new QueryStateVariableAction(), new QueryStateVariableExecutor());
    }
​
    try {
        return new LocalService(type, id, actions, stateVariables, stringConvertibleTypes, supportsQueryStateVariables);
​
    } catch (ValidationException ex) {
        log.severe("Could not validate device model: " + ex.toString());
        for (ValidationError validationError : ex.getErrors()) {
            log.severe(validationError.toString());
        }
        throw new LocalServiceBindingException("Validation of model failed, check the log");
    }
}

在该read()方法中,调用了readStateVariables()方法和readActions()方法,readStateVariables()用于获取所有支持的state variables,readActions()用于获取所有支持的Action的集合;

最后return new了LocalService对象;

public LocalService(ServiceType serviceType, ServiceId serviceId,
                    Map<Action, ActionExecutor> actionExecutors,
                    Map<StateVariable, StateVariableAccessor> stateVariableAccessors,
                    Set<Class> stringConvertibleTypes,
                    boolean supportsQueryStateVariables) throws ValidationException {
​
    super(serviceType, serviceId,
          actionExecutors.keySet().toArray(new Action[actionExecutors.size()]),
          stateVariableAccessors.keySet().toArray(new StateVariable[stateVariableAccessors.size()])
         );
​
    this.supportsQueryStateVariables = supportsQueryStateVariables;
    this.stringConvertibleTypes = stringConvertibleTypes;
    this.stateVariableAccessors = stateVariableAccessors;
    this.actionExecutors = actionExecutors;
}

将传入的变量保存到LocalService对象中;

其中执行了super()方法,我们看一下LocalService的父类Service的构造方法:

public Service(ServiceType serviceType, ServiceId serviceId,
               Action<S>[] actions, StateVariable<S>[] stateVariables) throws ValidationException {
​
    this.serviceType = serviceType;
    this.serviceId = serviceId;
​
    if (actions != null) {
        for (Action action : actions) {
            this.actions.put(action.getName(), action);
            action.setService(this);
        }
    }
​
    if (stateVariables != null) {
        for (StateVariable stateVariable : stateVariables) {
            this.stateVariables.put(stateVariable.getName(), stateVariable);
            stateVariable.setService(this);
        }
    }
​
}

其中将之前获取到的state variables和action一一保存到Map集合中;

至此,LocalService的创建过程就分析完了;

初始化ServiceManager

LocalService对象创建成功之后,就需要初始化mConnectionManager对象,该对象类型为ServiceManager,初始化逻辑为:

mConnectionManager = new DefaultServiceManager(connectionManagerService) {
    @Override
    protected Object createServiceInstance() throws Exception {
        return new NoboConnectionManagerService();
    }
};

创建了一个DefaultServiceManager对象,该构造方法中传入了刚刚创建好的LocalService:

protected DefaultServiceManager(LocalService<T> service) {
    this(service, null);
}
​
public DefaultServiceManager(LocalService<T> service, Class<T> serviceClass) {
    this.service = service;
    this.serviceClass = serviceClass;
}

将传入的LocalService保存到了DefaultServiceManager对象的service变量中;

在创建DefaultServiceManager对象的过程中,重写了createServiceInstance()方法,使之该方法返回new NoboConnectionManagerService();

NoboConnectionManager.java的构造方法中加载了UPnP协议所支持的媒体数据格式,包括image、audio、video;

保存ServiceManager

ServiceManager对象,即mConnectionManager变量初始化成功之后,紧接着调用了:

connectionManagerService.setManager(mConnectionManager);

将初始化好的mConnectionManager保存到connectionManagerService LocalService对象中:

synchronized public void setManager(ServiceManager<T> manager) {
    if (this.manager != null) {
        throw new IllegalStateException("Manager is final");
    }
    this.manager = manager;
}

至此,整个ConnectionManager Service的初始化过程就分析完成了;

初始化AVTransport Service
LocalService<NoboAVTransportService> avTransportService
    = mLocalServiceBinder.read(NoboAVTransportService.class);
mAVTransport
    = new LastChangeAwareServiceManager<NoboAVTransportService>(avTransportService,
                                                                new AVTransportLastChangeParser()) {
    @Override
    protected NoboAVTransportService createServiceInstance() throws Exception {
        return new NoboAVTransportService(mAvTransportLastChange, mMediaPlayers);
    }
};
avTransportService.setManager(mAVTransport);

其实AVTransport Service的创建方式也是类同,首先根据LocalServiceBinder.read()方法创建了AVTransportService类型的LocalService,与ConnectionManager Service稍微有点不同的是,mAVTransport变量的类型为LastChangeAwareServiceManager,LastChangeAwareServiceManager用于处理(收集和传递)GENA订阅服务事件的服务,其实LastChangeAwareServiceManager本质上是继承自DefaultServiceManager,在DefaultServiceManager的基础上增加了Event处理的机制;

核心逻辑:增加了Event处理的机制即为之后要讲到的Event订阅逻辑;

public LastChangeAwareServiceManager(LocalService<T> localService,
                                     LastChangeParser lastChangeParser) {
    this(localService, null, lastChangeParser);
}
​
public LastChangeAwareServiceManager(LocalService<T> localService,
                                     Class<T> serviceClass,
                                     LastChangeParser lastChangeParser) {
    super(localService, serviceClass);
    this.lastChangeParser = lastChangeParser;
}

在初始化LastChangeAwareServiceManager对象时,传入了一个new AVTransportLastChangeParser(),该对象主要是状态机,用于管理AVTransport的状态;

protected NoboAVTransportService createServiceInstance() throws Exception {
    return new NoboAVTransportService(mAvTransportLastChange, mMediaPlayers);
}

紧接着也是重写createServiceInstance()方法,定义返回NoboAVTransportService对象;

avTransportService.setManager(mAVTransport);

最后调用setManager,将创建好的状态机管理对象保存到LocalService中;

初始化RenderingControl Service

同上,不再做过多的称述;

初始化LocalDevice(UPnP设备)
创建LocalDevice对象

3种类型的LocalService创建完成之后,就需要创建对应的LocalDevice,LocalDevice包含上述的3个LocalService;

try {
    UDN udn = UpnpProfiles.uniqueSystemIdentifier(UpnpProfiles.UPNP_SALT);
​
    mDevice = new LocalDevice(new DeviceIdentity(udn),
            new UDADeviceType(DeviceProfiles.UDA_DEVICE_TYPE, DeviceProfiles.UDA_DEVICE_VERSION),
            new DeviceDetails(
                    DeviceProfiles.DEVICE_FRIENDLY_NAME,
                    new ManufacturerDetails("Cling", Build.MANUFACTURER),
                    new ModelDetails(DeviceProfiles.DMR_MODEL_NAME, DeviceProfiles.DMR_MODEL_DESC,
                            DeviceProfiles.DMR_MODEL_NUMBER, DeviceProfiles.DMR_MODEL_URL),
                    new DLNADoc[] {
                            new DLNADoc(DeviceProfiles.DEV_CLASS, DLNADoc.Version.V1_5)
                    },
                    new DLNACaps(new String[] {
                            DeviceProfiles.DLNA_CAPS_AV,
                            DeviceProfiles.DLNA_CAPS_IMAGE,
                            DeviceProfiles.DLNA_CAPS_AUDIO
                    })
            ),
            new Icon[] {
                    createDefaultDeviceIcon()
            },
            new LocalService[]{
                    avTransportService,
                    renderingControlService,
                    connectionManagerService
            });
} catch (Exception ex) {
    throw new RuntimeException(ex);
}

首先先对上述涉及到的类进行简单的说明:

说明
UDN唯一的设备名称,设备的通用唯一标识符
DeviceIdentity用于保存LocalDevice的UDN以及广播的最大有效时长
UDADeviceType用于定义具有固定的schemas-upnp-org命名空间的设备类型
DeviceDetails用于描述LocalDevice详细信息
ManufacturerDetails用于封装关于设备制造商的可选元数据
ModelDetails用于封装关于设备模型的可选元数据
DLNADoc用于表示DLNA文档及其版本
DLNACaps用于表示DLNA功能
Icon定义本地设备图标

解释完上述类之后,我们看一下LocalDevice的构造方法:

public LocalDevice(DeviceIdentity identity, DeviceType type, DeviceDetails details,
                   Icon[] icons, LocalService[] services) throws ValidationException {
    super(identity, type, details, icons, services);
    this.deviceDetailsProvider = null;
}

LocalDevice继承自Device:

public Device(DI identity, DeviceType type, DeviceDetails details,
              Icon[] icons, S[] services) throws ValidationException {
    this(identity, null, type, details, icons, services, null);
}
​
public Device(DI identity, UDAVersion version, DeviceType type, DeviceDetails details,
              Icon[] icons, S[] services, D[] embeddedDevices) throws ValidationException {
​
    this.identity = identity;
    this.version = version == null ? new UDAVersion() : version;
    this.type = type;
    this.details = details;
​
    // We don't fail device validation if icons were invalid, only log a warning. To
    // comply with mutability rules (can't set icons field in validate() method), we
    // validate the icons here before we set the field value
    List<Icon> validIcons = new ArrayList<>();
    if (icons != null) {
        for (Icon icon : icons) {
            if (icon != null) {
                icon.setDevice(this); // Set before validate()!
                List<ValidationError> iconErrors = icon.validate();
                if(iconErrors.isEmpty()) {
                    validIcons.add(icon);
                } else {
                    log.warning("Discarding invalid '" + icon + "': " + iconErrors);
                }
            }
        }
    }
    this.icons = validIcons.toArray(new Icon[validIcons.size()]);
​
    boolean allNullServices = true;
    if (services != null) {
        for (S service : services) {
            if (service != null) {
                allNullServices = false;
                service.setDevice(this);
            }
        }
    }
    this.services = services == null || allNullServices ? null : services;
​
    boolean allNullEmbedded = true;
    // 传入的embeddedDevices变量为null,所以不会执行该if判断
    if (embeddedDevices != null) {
        for (D embeddedDevice : embeddedDevices) {
            if (embeddedDevice != null) {
                allNullEmbedded = false;
                embeddedDevice.setParentDevice(this);
            }
        }
    }
    this.embeddedDevices = embeddedDevices == null || allNullEmbedded  ? null : embeddedDevices;
​
    List<ValidationError> errors = validate();
    if (errors.size() > 0) {
        if (log.isLoggable(Level.FINEST)) {
            for (ValidationError error : errors) {
                log.finest(error.toString());
            }
        }
        throw new ValidationException("Validation of device graph failed, call getErrors() on exception", errors);
    }
}

上述主要就是对传入的参数进行保存和遍历处理,主要是对Icon和Services的处理;

boolean allNullServices = true;
if (services != null) {
    for (S service : services) {
        if (service != null) {
            allNullServices = false;
            service.setDevice(this);
        }
    }
}
void setDevice(D device) {
    if (this.device != null)
        throw new IllegalStateException("Final value has been set already, model is immutable");
    this.device = device;
}

使LocalService的对象中也持有的LocalDevice的句柄;

至此,LocalDevice对象就创建完成了,之后会创建一个线程,然后一直执行如下逻辑:

new Thread() {
    @Override
    public void run() {
        try {
            while(true) {
                mAVTransport.fireLastChange();
                mRenderingControl.fireLastChange();
                Thread.sleep(500);
            }
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}.start();

首先我们看一下mAVTransport.fireLastChange():

/**
     * Call this method to propagate all accumulated "LastChange" values to GENA subscribers.
     */
public void fireLastChange() {
​
    // We need to obtain locks in the right order to avoid deadlocks:
    // 1. The lock() of the DefaultServiceManager
    // 2. The monitor/synchronized of the LastChange.fire() method
​
    lock();
    try {
        getImplementation().getLastChange().fire(getPropertyChangeSupport());
    } finally {
        unlock();
    }
}

调用此方法将所有累积的“LastChange”值传播到GENA订阅者,因为在创建上述3种LocalService的时候,就已经创建好了对应的订阅对象,在之后client连接UPnP设备时,会创建对应的事件订阅逻辑,上述方法就是用于处理之后事件订阅推送的逻辑;

我们依次看一下上述逻辑:

getImplementation()

public T getImplementation() {
    lock();
    try {
        if (serviceImpl == null) {
            init();
        }
        return serviceImpl;
    } finally {
        unlock();
    }
}

首次执行的时候,serviceImpl应该空,在为空的情况下,会执行init()方法:

protected void init() {
    log.fine("No service implementation instance available, initializing...");
    try {
        // The actual instance we ware going to use and hold a reference to (1:1 instance for manager)
        serviceImpl = createServiceInstance();
​
        // How the implementation instance will tell us about property changes
        propertyChangeSupport = createPropertyChangeSupport(serviceImpl);
        propertyChangeSupport.addPropertyChangeListener(createPropertyChangeListener(serviceImpl));
​
    } catch (Exception ex) {
        throw new RuntimeException("Could not initialize implementation: " + ex, ex);
    }
}

在该方法中首先执行了createServiceInstance()方法,这个方法就是我们上述刚刚重新的createServiceInstance()方法,返回的是我们自定义的Service对象,然后就是配置属性监听逻辑;

getLastChange()

@Override
public LastChange getLastChange() {
    return lastChange;
}

返回的是LastChange对象,该对象的赋值逻辑在AbstractAVTransportService的构造方法中:

protected AbstractAVTransportService(LastChange lastChange) {
    this.propertyChangeSupport = new PropertyChangeSupport(this);
    this.lastChange = lastChange;
}

而NoboAVTransportService继承自AbstractAVTransportService,并执行了super(),NoboAVTransportService的创建在重新的createServiceInstance()方法中执行的;

fire(getPropertyChangeSupport())

synchronized public void fire(PropertyChangeSupport propertyChangeSupport) {
    String lastChanges = toString();
    if (lastChanges != null && lastChanges.length() > 0) {
        propertyChangeSupport.firePropertyChange("LastChange", previousValue, lastChanges);
        reset();
    }
}

在该方法中执行了toString(),该方法主要用于获取UPnP设备发送的event事件集合,如果为空,说明当前没有状态发生变化,否则执行propertyChangeSupport.firePropertyChange()方法,propertyChangeSupport对象即上述init()方法中初始化的propertyChangeSupport对象:

propertyChangeSupport.firePropertyChange("LastChange", previousValue, lastChanges);

这一块逻辑涉及到了Android Sdk中的流程,暂不做分析;

至此,LocalDevice的创建初始化就完成了,就可以将LocalDevice发布到局域网中;

LocalDevice发布
mUpnpService = (AndroidUpnpService) service;
​
NoboMediaRenderer mediaRenderer = new NoboMediaRenderer(1, getApplicationContext());
mUpnpService.getRegistry().addDevice(mediaRenderer.getDevice());

在发布逻辑中,首先先获取AndroidUpnpService,连接成功之后,在onServiceConnected()方法中执行上述逻辑;

通过NoboMediaRenderer对象,获取到LocalDevice实例,即上述刚刚初始化好的LocalDevice对象;

然后通过mUpnpService.getRegistry()获取到UpnpService中的注册表:

protected class Binder extends android.os.Binder implements AndroidUpnpService {
​
    public UpnpService get() {
        return upnpService;
    }
​
    public UpnpServiceConfiguration getConfiguration() {
        return upnpService.getConfiguration();
    }
​
    public Registry getRegistry() {
        return upnpService.getRegistry();
    }
​
    public ControlPoint getControlPoint() {
        return upnpService.getControlPoint();
    }
}

getRegistry()

其中调用了upnpService.getRegistry(),upnpService对象类型为UpnpServiceImpl,UpnpServiceImpl实现了UpnpService接口类:

public Registry getRegistry() {
    return registry;
}

而registry的初始化逻辑在UpnpServiceImpl的构造方法中:

public UpnpServiceImpl(UpnpServiceConfiguration configuration, RegistryListener... registryListeners) {
    this.configuration = configuration;
​
    log.info(">>> Starting UPnP service...");
​
    log.info("Using configuration: " + getConfiguration().getClass().getName());
​
    // Instantiation order is important: Router needs to start its network services after registry is ready
​
    this.protocolFactory = createProtocolFactory();
​
    this.registry = createRegistry(protocolFactory);
    for (RegistryListener registryListener : registryListeners) {
        this.registry.addListener(registryListener);
    }
​
    this.router = createRouter(protocolFactory, registry);
​
    try {
        this.router.enable();
    } catch (RouterException ex) {
        throw new RuntimeException("Enabling network router failed: " + ex, ex);
    }
​
    this.controlPoint = createControlPoint(protocolFactory, registry);
​
    log.info("<<< UPnP service started successfully");
}

调用了createRegistry()方法:

protected Registry createRegistry(ProtocolFactory protocolFactory) {
    return new RegistryImpl(this);
}

返回了一个RegistryImpl对象:

@Inject
public RegistryImpl(UpnpService upnpService) {
    log.fine("Creating Registry: " + getClass().getName());
​
    this.upnpService = upnpService;
​
    log.fine("Starting registry background maintenance...");
    registryMaintainer = createRegistryMaintainer();
    if (registryMaintainer != null) {
        getConfiguration().getRegistryMaintainerExecutor().execute(registryMaintainer);
    }
}

调用了createRegistryMaintainer()方法,这个方法主要是用于创建注册表容器,该方法的返回类型为Runnable;

protected RegistryMaintainer createRegistryMaintainer() {
    return new RegistryMaintainer(
        this,
        getConfiguration().getRegistryMaintenanceIntervalMillis()
    );
}

然后紧接着调用了getConfiguration().getRegistryMaintainerExecutor().execute(registryMaintainer)逻辑,该逻辑主要是用于启动registry;

因为RegistryMaintainer的类型为Runnable,所以我们需要关注一下run()方法:

public void run() {
    stopped = false;
    if (log.isLoggable(Level.FINE))
        log.fine("Running registry maintenance loop every milliseconds: " + sleepIntervalMillis);
    while (!stopped) {
​
        try {
            registry.maintain();
            Thread.sleep(sleepIntervalMillis);
        } catch (InterruptedException ex) {
            stopped = true;
        }
​
    }
    log.fine("Stopped status on thread received, ending maintenance loop");
}

在run()中通过while控制逻辑,判断条件为stopped,默认为false,该变量只有在调用stop()方法的时候,才会被值为true;

在run()方法中调用了registry.maintain()方法:

synchronized void maintain() {
​
    if (log.isLoggable(Level.FINEST))
        log.finest("Maintaining registry...");
​
    // Remove expired resources
    Iterator<RegistryItem<URI, Resource>> it = resourceItems.iterator();
    while (it.hasNext()) {
        RegistryItem<URI, Resource> item = it.next();
        if (item.getExpirationDetails().hasExpired()) {
            if (log.isLoggable(Level.FINER))
                log.finer("Removing expired resource: " + item);
            it.remove();
        }
    }
​
    // Let each resource do its own maintenance
    for (RegistryItem<URI, Resource> resourceItem : resourceItems) {
        resourceItem.getItem().maintain(
            pendingExecutions,
            resourceItem.getExpirationDetails()
        );
    }
​
    // These add all their operations to the pendingExecutions queue
    remoteItems.maintain();
    localItems.maintain();
​
    // We now run the queue asynchronously so the maintenance thread can continue its loop undisturbed
    runPendingExecutions(true);
}

在该方法中执行了remoteItems.maintain()和localItems.maintain()方法,目前remoteItems和localItems都为空,在后续addDevice()中,会向localItems中添加创建好的LocalDevice;

所以我们看一下maintain()方法:

void maintain() {
​
    if(getDeviceItems().isEmpty()) return ;
​
    Set<RegistryItem<UDN, LocalDevice>> expiredLocalItems = new HashSet<>();
​
    // "Flooding" is enabled, check if we need to send advertisements for all devices
    int aliveIntervalMillis = registry.getConfiguration().getAliveIntervalMillis();
    if(aliveIntervalMillis > 0) {
        long now = System.currentTimeMillis();
        if(now - lastAliveIntervalTimestamp > aliveIntervalMillis) {
            lastAliveIntervalTimestamp = now;
            for (RegistryItem<UDN, LocalDevice> localItem : getDeviceItems()) {
                if (isAdvertised(localItem.getKey())) {
                    log.finer("Flooding advertisement of local item: " + localItem);
                    expiredLocalItems.add(localItem);
                }
            }
        }
    } else {
        // Reset, the configuration might dynamically switch the alive interval
        lastAliveIntervalTimestamp = 0;
​
        // Alive interval is not enabled, regular expiration check of all devices
        for (RegistryItem<UDN, LocalDevice> localItem : getDeviceItems()) {
            if (isAdvertised(localItem.getKey()) && localItem.getExpirationDetails().hasExpired(true)) {
                log.finer("Local item has expired: " + localItem);
                expiredLocalItems.add(localItem);
            }
        }
    }
​
    // Now execute the advertisements
    for (RegistryItem<UDN, LocalDevice> expiredLocalItem : expiredLocalItems) {
        log.fine("Refreshing local device advertisement: " + expiredLocalItem.getItem());
        advertiseAlive(expiredLocalItem.getItem());
        expiredLocalItem.getExpirationDetails().stampLastRefresh();
    }
​
    // Expire incoming subscriptions
    Set<RegistryItem<String, LocalGENASubscription>> expiredIncomingSubscriptions = new HashSet<>();
    for (RegistryItem<String, LocalGENASubscription> item : getSubscriptionItems()) {
        if (item.getExpirationDetails().hasExpired(false)) {
            expiredIncomingSubscriptions.add(item);
        }
    }
    for (RegistryItem<String, LocalGENASubscription> subscription : expiredIncomingSubscriptions) {
        log.fine("Removing expired: " + subscription);
        removeSubscription(subscription.getItem());
        subscription.getItem().end(CancelReason.EXPIRED);
    }
​
}

在该方法中执行了3个for循环,我们主要关注第一个for循环,在第一个for循环中,执行了advertiseAlive(expiredLocalItem.getItem())方法:

protected void advertiseAlive(final LocalDevice localDevice) {
    registry.executeAsyncProtocol(new Runnable() {
        public void run() {
            try {
                log.finer("Sleeping some milliseconds to avoid flooding the network with ALIVE msgs");
                Thread.sleep(randomGenerator.nextInt(100));
            } catch (InterruptedException ex) {
                log.severe("Background execution interrupted: " + ex.getMessage());
            }
            registry.getProtocolFactory().createSendingNotificationAlive(localDevice).run();
        }
    });
}

在该方法中执行了registry.getProtocolFactory().createSendingNotificationAlive(localDevice).run(),其中getProtocolFactory()返回的是一个协议工厂:

public SendingNotificationAlive createSendingNotificationAlive(LocalDevice localDevice) {
    return new SendingNotificationAlive(getUpnpService(), localDevice);
}

创建了SendingNotificationAlive对象:

public SendingNotificationAlive(UpnpService upnpService, LocalDevice device) {
    super(upnpService, device);
}

SendingNotificationAlive继承自SendingNotification:

public SendingNotification(UpnpService upnpService, LocalDevice device) {
    super(upnpService);
    this.device = device;
}

SendingNotification继承自SendingAsync:

protected SendingAsync(UpnpService upnpService) {
    this.upnpService = upnpService;
}

上述调用了registry.getProtocolFactory().createSendingNotificationAlive(localDevice).run(),run()在SendingAsync中定义:

public void run() {
    try {
        execute();
    } catch (Exception ex) {
        Throwable cause = Exceptions.unwrap(ex);
        if (cause instanceof InterruptedException) {
            log.log(Level.INFO, "Interrupted protocol '" + getClass().getSimpleName() + "': " + ex, cause);
        } else {
            throw new RuntimeException(
                "Fatal error while executing protocol '" + getClass().getSimpleName() + "': " + ex, ex
            );
        }
    }
}

该方法中调用了execute()方法,该方法在SendingNotificationAlive中实现,而在SendingNotificationAlive中的execute最终调用了SendingNotification中的execute()方法:

protected void execute() throws RouterException {
​
    List<NetworkAddress> activeStreamServers =
        getUpnpService().getRouter().getActiveStreamServers(null);
    if (activeStreamServers.size() == 0) {
        log.fine("Aborting notifications, no active stream servers found (network disabled?)");
        return;
    }
​
    // Prepare it once, it's the same for each repetition
    List<Location> descriptorLocations = new ArrayList<>();
    for (NetworkAddress activeStreamServer : activeStreamServers) {
        descriptorLocations.add(
            new Location(
                activeStreamServer,
                getUpnpService().getConfiguration().getNamespace().getDescriptorPathString(getDevice())
            )
        );
    }
​
    for (int i = 0; i < getBulkRepeat(); i++) {
        try {
​
            for (Location descriptorLocation : descriptorLocations) {
                sendMessages(descriptorLocation);
            }
​
            // UDA 1.0 is silent about this but UDA 1.1 recomments "a few hundred milliseconds"
            log.finer("Sleeping " + getBulkIntervalMilliseconds() + " milliseconds");
            Thread.sleep(getBulkIntervalMilliseconds());
​
        } catch (InterruptedException ex) {
            log.warning("Advertisement thread was interrupted: " + ex);
        }
    }
}

该方法中执行了sendMessages()方法:

public void sendMessages(Location descriptorLocation) throws RouterException {
    log.finer("Sending root device messages: " + getDevice());
    List<OutgoingNotificationRequest> rootDeviceMsgs =
            createDeviceMessages(getDevice(), descriptorLocation);
    for (OutgoingNotificationRequest upnpMessage : rootDeviceMsgs) {
        getUpnpService().getRouter().send(upnpMessage);
    }
​
    if (getDevice().hasEmbeddedDevices()) {
        for (LocalDevice embeddedDevice : getDevice().findEmbeddedDevices()) {
            log.finer("Sending embedded device messages: " + embeddedDevice);
            List<OutgoingNotificationRequest> embeddedDeviceMsgs =
                    createDeviceMessages(embeddedDevice, descriptorLocation);
            for (OutgoingNotificationRequest upnpMessage : embeddedDeviceMsgs) {
                getUpnpService().getRouter().send(upnpMessage);
            }
        }
    }
​
    List<OutgoingNotificationRequest> serviceTypeMsgs =
            createServiceTypeMessages(getDevice(), descriptorLocation);
    if (serviceTypeMsgs.size() > 0) {
        log.finer("Sending service type messages");
        for (OutgoingNotificationRequest upnpMessage : serviceTypeMsgs) {
            getUpnpService().getRouter().send(upnpMessage);
        }
    }
}

在该方法中,总共有3个List,rootDeviceMsgs、embeddedDeviceMsgs、serviceTypeMsgs,分别对应3种类型的device:root device、embedded device以及对应的services list of the embedded device,每个List的类型为OutgoingNotificationRequest,我们看一下OutgoingNotificationRequest的构造方法:

protected OutgoingNotificationRequest(Location location, LocalDevice device, NotificationSubtype type) {
    super(
        new UpnpRequest(UpnpRequest.Method.NOTIFY),
        ModelUtil.getInetAddressByName(Constants.IPV4_UPNP_MULTICAST_GROUP),
        Constants.UPNP_MULTICAST_PORT
    );
​
    this.type = type;
​
    getHeaders().add(UpnpHeader.Type.MAX_AGE, new MaxAgeHeader(device.getIdentity().getMaxAgeSeconds()));
    getHeaders().add(UpnpHeader.Type.LOCATION, new LocationHeader(location.getURL()));
​
    getHeaders().add(UpnpHeader.Type.SERVER, new ServerHeader());
    getHeaders().add(UpnpHeader.Type.HOST, new HostHeader());
    getHeaders().add(UpnpHeader.Type.NTS, new NTSHeader(type));
}

在该构造方法中定义了该request的method为NOTIFY,意为通知多播地址,宣告的意思;

在每个对应的for循环中执行了getUpnpService().getRouter().send(upnpMessage)方法,该upnpMessage中包含了上述描述的request,意为向多播地址宣告该device的可见和可用性;

紧接着调用了getUpnpService().getRouter().send(upnpMessage),Router为网络传输层接口,通过调用send()方法发送,Router的实现类为RouterImpl:

public void send(OutgoingDatagramMessage msg) throws RouterException {
    lock(readLock);
    try {
        if (enabled) {
            for (DatagramIO datagramIO : datagramIOs.values()) {
                datagramIO.send(msg);
            }
        } else {
            log.fine("Router disabled, not sending datagram: " + msg);
        }
    } finally {
        unlock(readLock);
    }
}

调用了datagramIO.send()方法:

synchronized public void send(OutgoingDatagramMessage message) {
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending message from address: " + localAddress);
    }
    DatagramPacket packet = datagramProcessor.write(message);
​
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending UDP datagram packet to: " + message.getDestinationAddress() + ":" + message.getDestinationPort());
    }
​
    send(packet);
}

调用了datagramProcessor.write(message),datagramProcessor就是DefaultUpnpServiceConfiguration构造方法中初始化的变量:

public DatagramPacket write(OutgoingDatagramMessage message) throws UnsupportedDataException {
​
    StringBuilder statusLine = new StringBuilder();
​
    UpnpOperation operation = message.getOperation();
​
    if (operation instanceof UpnpRequest) {
​
        UpnpRequest requestOperation = (UpnpRequest) operation;
        statusLine.append(requestOperation.getHttpMethodName()).append(" * ");
        statusLine.append("HTTP/1.").append(operation.getHttpMinorVersion()).append("\r\n");
​
    } else if (operation instanceof UpnpResponse) {
        UpnpResponse responseOperation = (UpnpResponse) operation;
        statusLine.append("HTTP/1.").append(operation.getHttpMinorVersion()).append(" ");
        statusLine.append(responseOperation.getStatusCode()).append(" ").append(responseOperation.getStatusMessage());
        statusLine.append("\r\n");
    } else {
        throw new UnsupportedDataException(
                "Message operation is not request or response, don't know how to process: " + message
        );
    }
​
    // UDA 1.0, 1.1.2: No body but message must have a blank line after header
    StringBuilder messageData = new StringBuilder();
    messageData.append(statusLine);
​
    messageData.append(message.getHeaders().toString()).append("\r\n");
​
    if (log.isLoggable(Level.FINER)) {
        log.finer("Writing message data for: " + message);
        log.finer("---------------------------------------------------------------------------------");
        log.finer(messageData.toString().substring(0, messageData.length() - 2)); // Don't print the blank lines
        log.finer("---------------------------------------------------------------------------------");
    }
​
    try {
        // According to HTTP 1.0 RFC, headers and their values are US-ASCII
        // TODO: Probably should look into escaping rules, too
        byte[] data = messageData.toString().getBytes("US-ASCII");
​
        log.fine("Writing new datagram packet with " + data.length + " bytes for: " + message);
        return new DatagramPacket(data, data.length, message.getDestinationAddress(), message.getDestinationPort());
​
    } catch (UnsupportedEncodingException ex) {
        throw new UnsupportedDataException(
            "Can't convert message content to US-ASCII: " + ex.getMessage(), ex, messageData
        );
    }
}

在该方法中对message进行解析,封装了对应格式的数据报包;

最后调用了send()方法:

synchronized public void send(DatagramPacket datagram) {
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending message from address: " + localAddress);
    }
​
    try {
        socket.send(datagram);
    } catch (SocketException ex) {
        log.fine("Socket closed, aborting datagram send to: " + datagram.getAddress());
    } catch (RuntimeException ex) {
        throw ex;
    } catch (Exception ex) {
        log.log(Level.SEVERE, "Exception sending datagram to: " + datagram.getAddress() + ": " + ex, ex);
    }
}

在该方法中使用了socket的send()方法发送了数据报包,socket的类型为DatagramSocket;

至此,通过多播套接字发送了发现广播;

上述逻辑,只是推送消息逻辑,但是还没有真正执行,真正执行的逻辑在addDevice()方法中触发;

addDevice()

注册表创建成功并启动之后,紧接着调用了addDevice()方法:

synchronized public void addDevice(LocalDevice localDevice) {
    localItems.add(localDevice);
}

将创建好的LocalDevice添加到了localItems中,localItems的类型为LocalItems:

void add(LocalDevice localDevice) throws RegistrationException {
    add(localDevice, null);
}
​
void add(final LocalDevice localDevice, DiscoveryOptions options) throws RegistrationException {
​
    // Always set/override the options, even if we don't end up adding the device
    setDiscoveryOptions(localDevice.getIdentity().getUdn(), options);
​
    if (registry.getDevice(localDevice.getIdentity().getUdn(), false) != null) {
        log.fine("Ignoring addition, device already registered: " + localDevice);
        return;
    }
​
    log.fine("Adding local device to registry: " + localDevice);
​
    for (Resource deviceResource : getResources(localDevice)) {
​
        if (registry.getResource(deviceResource.getPathQuery()) != null) {
            throw new RegistrationException("URI namespace conflict with already registered resource: " + deviceResource);
        }
​
        registry.addResource(deviceResource);
        log.fine("Registered resource: " + deviceResource);
​
    }
​
    log.fine("Adding item to registry with expiration in seconds: " + localDevice.getIdentity().getMaxAgeSeconds());
​
    RegistryItem<UDN, LocalDevice> localItem = new RegistryItem<>(
        localDevice.getIdentity().getUdn(),
        localDevice,
        localDevice.getIdentity().getMaxAgeSeconds()
    );
​
    getDeviceItems().add(localItem);
    log.fine("Registered local device: " + localItem);
​
    if (isByeByeBeforeFirstAlive(localItem.getKey()))
        advertiseByebye(localDevice, true);
​
    if (isAdvertised(localItem.getKey()))
        advertiseAlive(localDevice);
​
    for (final RegistryListener listener : registry.getListeners()) {
        registry.getConfiguration().getRegistryListenerExecutor().execute(
            new Runnable() {
                public void run() {
                    listener.localDeviceAdded(registry, localDevice);
                }
            }
        );
    }
​
}

在add()方法中,根据传入的LocalDevice创建了对应的RegistryItem<UDN, LocalDevice>对象,然后将localItem添加到了Set<RegistryItem<UDN, D>>类型的deviceItems集合中;

根据之前getRegistry()的逻辑中,就提及到了advertiseAlive()方法的使用,但是之前还没有执行addDevice()方法,deviceItems集合为空,但是在addDevice()方法中再次调用了advertiseAlive(localDevice)方法,现在该方法中触发了root device、embedded device以及services list of the embedded device的device msg的sendMessage()逻辑;

至此,整个Server端UPnP设备宣告流程就执行完成了,接下来就是client端Control Point发现可用UPnP设备流程;

发现

mUpnpService = (AndroidUpnpService) service;
​
mUpnpService.getControlPoint().search();

同理,client的实现也需要绑定AndroidUpnpService,然后通过getControlPoint()获取AndroidUpnpService中的控制点实例,然后执行search()方法扫描局域网中可用的UPnP设备;

getControlPoint()
public ControlPoint getControlPoint() {
    return upnpService.getControlPoint();
}

同理,upnpService对象为UpnpServiceImpl对象:

public ControlPoint getControlPoint() {
    return controlPoint;
}

controlPoint实例的初始化同样也在UpnpServiceImpl的构造方法中:

public UpnpServiceImpl(UpnpServiceConfiguration configuration, RegistryListener... registryListeners) {
    this.configuration = configuration;
    
    this.protocolFactory = createProtocolFactory();
​
    ……………………
​
    this.controlPoint = createControlPoint(protocolFactory, registry);
​
    log.info("<<< UPnP service started successfully");
}

其中调用了createProtocolFactory()方法:

protected ProtocolFactory createProtocolFactory() {
    return new ProtocolFactoryImpl(this);
}

创建了ProtocolFactoryImpl对象:

@Inject
public ProtocolFactoryImpl(UpnpService upnpService) {
    log.fine("Creating ProtocolFactory: " + getClass().getName());
    this.upnpService = upnpService;
}

紧接着执行了createControlPoint()方法:

protected ControlPoint createControlPoint(ProtocolFactory protocolFactory, Registry registry) {
    return new ControlPointImpl(getConfiguration(), protocolFactory, registry);
}

我们看一下ControlPointImpl的构造方法:

@Inject
public ControlPointImpl(UpnpServiceConfiguration configuration, ProtocolFactory protocolFactory, Registry registry) {
    log.fine("Creating ControlPoint: " + getClass().getName());
​
    this.configuration = configuration;
    this.protocolFactory = protocolFactory;
    this.registry = registry;
}

至此,ControlPoint就创建成功;

在上述的过程中,我们多次看到了configuration,configuration这个变量的初始化逻辑在AndroidUpnpServiceImpl的onCreate()方法中:

upnpService = new UpnpServiceImpl(createConfiguration()) {
    ……………………
}

其中传入了createConfiguration()方法:

protected UpnpServiceConfiguration createConfiguration() {
    return new AndroidUpnpServiceConfiguration();
}

创建了AndroidUpnpServiceConfiguration对象:

public AndroidUpnpServiceConfiguration() {
    this(0); // Ephemeral port
}
​
public AndroidUpnpServiceConfiguration(int streamListenPort) {
    super(streamListenPort, false);
​
    // This should be the default on Android 2.1 but it's not set by default
    System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");
}

其中调用了super(),AndroidUpnpServiceConfiguration继承自DefaultUpnpServiceConfiguration:

protected DefaultUpnpServiceConfiguration(int streamListenPort, boolean checkRuntime) {
    if (checkRuntime && ModelUtil.ANDROID_RUNTIME) {
        throw new Error("Unsupported runtime environment, use org.fourthline.cling.android.AndroidUpnpServiceConfiguration");
    }
​
    this.streamListenPort = streamListenPort;
​
    defaultExecutorService = createDefaultExecutorService();
​
    datagramProcessor = createDatagramProcessor();
    soapActionProcessor = createSOAPActionProcessor();
    genaEventProcessor = createGENAEventProcessor();
​
    deviceDescriptorBinderUDA10 = createDeviceDescriptorBinderUDA10();
    serviceDescriptorBinderUDA10 = createServiceDescriptorBinderUDA10();
​
    namespace = createNamespace();
}

在该构造方法中创建了许多关于协议传输过程中需要使用到的对象;

search() -- client发送search指令
public void search() {
    search(new STAllHeader(), MXHeader.DEFAULT_VALUE);
}
​
public void search(UpnpHeader searchType, int mxSeconds) {
    log.fine("Sending asynchronous search for: " + searchType.getString());
    getConfiguration().getAsyncProtocolExecutor().execute(
        getProtocolFactory().createSendingSearch(searchType, mxSeconds)
    );
}
NotificationSubtype

在search()方法中,执行了new STAllHeader对象:

public class STAllHeader extends UpnpHeader<NotificationSubtype> {
​
    public STAllHeader() {
        setValue(NotificationSubtype.ALL);
    }
​
    public void setString(String s) throws InvalidHeaderException {
        if (!s.equals(NotificationSubtype.ALL.getHeaderString())) {
            throw new InvalidHeaderException("Invalid ST header value (not "+NotificationSubtype.ALL+"): " + s);
        }
    }
​
    public String getString() {
        return getValue().getHeaderString();
    }
​
}

在STAllHeader的构造方法中调用了setValue(NotificationSubtype.ALL)方法,我们看一下setValue方法的逻辑:

public void setValue(T value) {
    this.value = value;
}

value的类型为NotificationSubtype,所以我们看一下NotificationSubtype的定义:

public enum NotificationSubtype {
​
    ALIVE("ssdp:alive"),
    UPDATE("ssdp:update"),
    BYEBYE("ssdp:byebye"),
    ALL("ssdp:all"),
    DISCOVER("ssdp:discover"),
    PROPCHANGE("upnp:propchange");
​
    private String headerString;
​
    NotificationSubtype(String headerString) {
        this.headerString = headerString;
    }
​
    public String getHeaderString() {
        return headerString;
    }
}

NotificationSubtype定义了发现设备的协议;

search()方法紧接着调用了getConfiguration().getAsyncProtocolExecutor(),其实就是在DefaultUpnpServiceConfiguration构造方法中初始化的defaultExecutorService变量,在defaultExecutorService变量的线程池中执行了getProtocolFactory().createSendingSearch(searchType, mxSeconds),getProtocolFactory()是ProtocolFactoryImpl对象,createSendingSearch的返回类型为Runnable:()

public SendingSearch createSendingSearch(UpnpHeader searchTarget, int mxSeconds) {
    return new SendingSearch(getUpnpService(), searchTarget, mxSeconds);
}

其中创建了SendingSearch对象:

public SendingSearch(UpnpService upnpService, UpnpHeader searchTarget, int mxSeconds) {
    super(upnpService);
​
    if (!UpnpHeader.Type.ST.isValidHeaderType(searchTarget.getClass())) {
        throw new IllegalArgumentException(
            "Given search target instance is not a valid header class for type ST: " + searchTarget.getClass()
        );
    }
    this.searchTarget = searchTarget;
    this.mxSeconds = mxSeconds;
}

其中SendingSearch继承了SendingAsync,SendingAsync实现了Runnable:

public abstract class SendingAsync implements Runnable {
​
    final private static Logger log = Logger.getLogger(UpnpService.class.getName());
​
    private final UpnpService upnpService;
​
    protected SendingAsync(UpnpService upnpService) {
        this.upnpService = upnpService;
    }
​
    public UpnpService getUpnpService() {
        return upnpService;
    }
​
    public void run() {
        try {
            execute();
        } catch (Exception ex) {
            Throwable cause = Exceptions.unwrap(ex);
            if (cause instanceof InterruptedException) {
                log.log(Level.INFO, "Interrupted protocol '" + getClass().getSimpleName() + "': " + ex, cause);
            } else {
                throw new RuntimeException(
                    "Fatal error while executing protocol '" + getClass().getSimpleName() + "': " + ex, ex
                );
            }
        }
    }
​
    protected abstract void execute() throws RouterException;
​
    @Override
    public String toString() {
        return "(" + getClass().getSimpleName() + ")";
    }
​
}

SendingAsync的run()中执行了execute()方法,这个方法为abstract,在SendingSearch中实现:

protected void execute() throws RouterException {
​
    log.fine("Executing search for target: " + searchTarget.getString() + " with MX seconds: " + getMxSeconds());
​
    OutgoingSearchRequest msg = new OutgoingSearchRequest(searchTarget, getMxSeconds());
    prepareOutgoingSearchRequest(msg);
​
    for (int i = 0; i < getBulkRepeat(); i++) {
        try {
​
            getUpnpService().getRouter().send(msg);
​
            // UDA 1.0 is silent about this but UDA 1.1 recommends "a few hundred milliseconds"
            log.finer("Sleeping " + getBulkIntervalMilliseconds() + " milliseconds");
            Thread.sleep(getBulkIntervalMilliseconds());
​
        } catch (InterruptedException ex) {
            // Interruption means we stop sending search messages, e.g. on shutdown of thread pool
            break;
        }
    }
}

在execute()方法中创建了msg,然后调用getUpnpService().getRouter().send(msg)将msg发送到局域网中;

上述逻辑和UPnP设备宣告的逻辑非常类似;

msg的类型为OutgoingSearchRequest,OutgoingSearchRequest的构造方法:

public OutgoingSearchRequest(UpnpHeader searchTarget, int mxSeconds) {
    super(
        new UpnpRequest(UpnpRequest.Method.MSEARCH),
        ModelUtil.getInetAddressByName(Constants.IPV4_UPNP_MULTICAST_GROUP),
        Constants.UPNP_MULTICAST_PORT
    );
​
    this.searchTarget = searchTarget;
​
    getHeaders().add(UpnpHeader.Type.MAN, new MANHeader(NotificationSubtype.DISCOVER.getHeaderString()));
    getHeaders().add(UpnpHeader.Type.MX, new MXHeader(mxSeconds));
    getHeaders().add(UpnpHeader.Type.ST, searchTarget);
    getHeaders().add(UpnpHeader.Type.HOST, new HostHeader());
}

在该构造方法中,定义了UpnpRequest.Method,同时封装请求头数据;

Router为网络传输层接口,通过调用send()方法发送,Router的实现类为RouterImpl:

public void send(OutgoingDatagramMessage msg) throws RouterException {
    lock(readLock);
    try {
        if (enabled) {
            for (DatagramIO datagramIO : datagramIOs.values()) {
                datagramIO.send(msg);
            }
        } else {
            log.fine("Router disabled, not sending datagram: " + msg);
        }
    } finally {
        unlock(readLock);
    }
}

调用了datagramIO.send()方法:

synchronized public void send(OutgoingDatagramMessage message) {
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending message from address: " + localAddress);
    }
    DatagramPacket packet = datagramProcessor.write(message);
​
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending UDP datagram packet to: " + message.getDestinationAddress() + ":" + message.getDestinationPort());
    }
​
    send(packet);
}
write()

调用了datagramProcessor.write(message),datagramProcessor就是DefaultUpnpServiceConfiguration构造方法中初始化的变量:

public DatagramPacket write(OutgoingDatagramMessage message) throws UnsupportedDataException {
​
    StringBuilder statusLine = new StringBuilder();
​
    UpnpOperation operation = message.getOperation();
​
    if (operation instanceof UpnpRequest) {
​
        UpnpRequest requestOperation = (UpnpRequest) operation;
        statusLine.append(requestOperation.getHttpMethodName()).append(" * ");
        statusLine.append("HTTP/1.").append(operation.getHttpMinorVersion()).append("\r\n");
​
    } else if (operation instanceof UpnpResponse) {
        UpnpResponse responseOperation = (UpnpResponse) operation;
        statusLine.append("HTTP/1.").append(operation.getHttpMinorVersion()).append(" ");
        statusLine.append(responseOperation.getStatusCode()).append(" ").append(responseOperation.getStatusMessage());
        statusLine.append("\r\n");
    } else {
        throw new UnsupportedDataException(
                "Message operation is not request or response, don't know how to process: " + message
        );
    }
​
    // UDA 1.0, 1.1.2: No body but message must have a blank line after header
    StringBuilder messageData = new StringBuilder();
    messageData.append(statusLine);
​
    messageData.append(message.getHeaders().toString()).append("\r\n");
​
    if (log.isLoggable(Level.FINER)) {
        log.finer("Writing message data for: " + message);
        log.finer("---------------------------------------------------------------------------------");
        log.finer(messageData.toString().substring(0, messageData.length() - 2)); // Don't print the blank lines
        log.finer("---------------------------------------------------------------------------------");
    }
​
    try {
        // According to HTTP 1.0 RFC, headers and their values are US-ASCII
        // TODO: Probably should look into escaping rules, too
        byte[] data = messageData.toString().getBytes("US-ASCII");
​
        log.fine("Writing new datagram packet with " + data.length + " bytes for: " + message);
        return new DatagramPacket(data, data.length, message.getDestinationAddress(), message.getDestinationPort());
​
    } catch (UnsupportedEncodingException ex) {
        throw new UnsupportedDataException(
            "Can't convert message content to US-ASCII: " + ex.getMessage(), ex, messageData
        );
    }
}

在该方法中对message进行解析,封装了对应格式的数据报包;

send()

最后调用了send()方法:

synchronized public void send(DatagramPacket datagram) {
    if (log.isLoggable(Level.FINE)) {
        log.fine("Sending message from address: " + localAddress);
    }
​
    try {
        socket.send(datagram);
    } catch (SocketException ex) {
        log.fine("Socket closed, aborting datagram send to: " + datagram.getAddress());
    } catch (RuntimeException ex) {
        throw ex;
    } catch (Exception ex) {
        log.log(Level.SEVERE, "Exception sending datagram to: " + datagram.getAddress() + ": " + ex, ex);
    }
}

在该方法中使用了socket的send()方法发送了数据报包,socket的类型为DatagramSocket;

至此,通过多播套接字发送了发现广播;

需要注意的是,上述的两个send都是async,异步的,使用的是UDP的协议方式,之后的逻辑中都是使用TCP/IP的协议;

search_client响应search Response

client端发送了发现请求之后,等待响应。响应逻辑在之前描述的Router中定义了;

响应逻辑的定义在AsyncServletStreamServerImpl中:

protected Servlet createServlet(final Router router) {
    return new HttpServlet() {
        @Override
        protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
​
           final long startTime = System.currentTimeMillis();
           final int counter = mCounter++;
            if (log.isLoggable(Level.FINE))
               log.fine(String.format("HttpServlet.service(): id: %3d, request URI: %s", counter, req.getRequestURI()));
​
            AsyncContext async = req.startAsync();
            async.setTimeout(getConfiguration().getAsyncTimeoutSeconds()*1000);
​
            async.addListener(new AsyncListener() {
​
                @Override
                public void onTimeout(AsyncEvent arg0) throws IOException {
                    long duration = System.currentTimeMillis() - startTime;
                    if (log.isLoggable(Level.FINE))
                        log.fine(String.format("AsyncListener.onTimeout(): id: %3d, duration: %,4d, request: %s", counter, duration, arg0.getSuppliedRequest()));
                }
​
​
                @Override
                public void onStartAsync(AsyncEvent arg0) throws IOException {
                    if (log.isLoggable(Level.FINE))
                        log.fine(String.format("AsyncListener.onStartAsync(): id: %3d, request: %s", counter, arg0.getSuppliedRequest()));
                }
​
​
                @Override
                public void onError(AsyncEvent arg0) throws IOException {
                    long duration = System.currentTimeMillis() - startTime;
                    if (log.isLoggable(Level.FINE))
                        log.fine(String.format("AsyncListener.onError(): id: %3d, duration: %,4d, response: %s", counter, duration, arg0.getSuppliedResponse()));
                }
​
​
                @Override
                public void onComplete(AsyncEvent arg0) throws IOException {
                    long duration = System.currentTimeMillis() - startTime;
                    if (log.isLoggable(Level.FINE))
                        log.fine(String.format("AsyncListener.onComplete(): id: %3d, duration: %,4d, response: %s", counter, duration, arg0.getSuppliedResponse()));
                }
​
            });
​
            AsyncServletUpnpStream stream =
                new AsyncServletUpnpStream(router.getProtocolFactory(), async, req) {
                    @Override
                    protected Connection createConnection() {
                        return new AsyncServletConnection(getRequest());
                    }
                };
​
            router.received(stream);
        }
    };
}

在该方法中调用了received()方法,用于处理接收到请求或者是响应;

在search()阶段,执行了SendingSearch中的execute()方法,用于发送search请求,UPnP设备在宣告阶段告知了局域网UPnP设备的存在,得到了NOTIFY类型的响应;

所以我们可以直接查看NOTIFY响应逻辑:

public void received(UpnpStream stream) {
    if (!enabled) {
        log.fine("Router disabled, ignoring incoming: " + stream);
        return;
    }
    log.fine("Received synchronous stream: " + stream);
    getConfiguration().getSyncProtocolExecutorService().execute(stream);
}

调用了execute()方法执行了UpnpStream Runnable,所以我们看一下UpnpStream对象,该对象的类型为AsyncServletUpnpStream:

@Override
public void run() {
    try {
        StreamRequestMessage requestMessage = readRequestMessage();
        if (log.isLoggable(Level.FINER))
            log.finer("Processing new request message: " + requestMessage);
​
        responseMessage = process(requestMessage);
​
        ……………………
    } finally {
        complete();
    }
}

调用了process()方法:

public StreamResponseMessage process(StreamRequestMessage requestMsg) {
    log.fine("Processing stream request message: " + requestMsg);
​
    try {
        // Try to get a protocol implementation that matches the request message
        syncProtocol = getProtocolFactory().createReceivingSync(requestMsg);
    } catch (ProtocolCreationException ex) {
        log.warning("Processing stream request failed - " + Exceptions.unwrap(ex).toString());
        return new StreamResponseMessage(UpnpResponse.Status.NOT_IMPLEMENTED);
    }
​
    // Run it
    log.fine("Running protocol for synchronous message processing: " + syncProtocol);
    syncProtocol.run();
    ……………………
}

而在该方法中调用了syncProtocol.run(),syncProtocol对象类型为ReceivingAsync:

public void run() {
    boolean proceed;
    try {
        proceed = waitBeforeExecution();
    } catch (InterruptedException ex) {
        log.info("Protocol wait before execution interrupted (on shutdown?): " + getClass().getSimpleName());
        proceed = false;
    }
​
    if (proceed) {
        try {
            execute();
        } catch (Exception ex) {
            ……………………
        }
    }
}

在该方法中调用了execute()方法,而execute()方法为抽象方法,该方法的实现有4种:ReceivingNotification、ReceivingSearch、ReceivingSearchResponse、ReceivingSync,我们需要区别一下ReceivingSearch和ReceivingSearchResponse:

  • ReceivingSearch:处理搜索请求的接收,响应本地注册设备,这个处理在UPnP设备侧;
  • ReceivingSearchResponse:处理搜索响应消息的接收,这个用于处理UPnP设备回应的response,在client侧;

而我们现在的阶段为search响应阶段,在client侧,所以直接看ReceivingSearchResponse中的execute():

public class ReceivingSearchResponse extends ReceivingAsync<IncomingSearchResponse> {
​
    final private static Logger log = Logger.getLogger(ReceivingSearchResponse.class.getName());
​
    public ReceivingSearchResponse(UpnpService upnpService, IncomingDatagramMessage<UpnpResponse> inputMessage) {
        super(upnpService, new IncomingSearchResponse(inputMessage));
    }
​
    protected void execute() throws RouterException {
​
        if (!getInputMessage().isSearchResponseMessage()) {
            log.fine("Ignoring invalid search response message: " + getInputMessage());
            return;
        }
​
        ……………………
    }
}

在该方法中调用了getInputMessage()方法来获取UPnP设备响应的数据,我们首先先分析一个InputMessage的赋值逻辑;

InputMessage变量

这个InputMessage的赋值逻辑在Router的enable()方法中,在enable()方法中调用了startAddressBasedTransports()方法:

protected void startAddressBasedTransports(Iterator<InetAddress> addresses) throws InitializationException {
    while (addresses.hasNext()) {
        InetAddress address = addresses.next();
​
        // HTTP servers
        StreamServer streamServer = getConfiguration().createStreamServer(networkAddressFactory);
        if (streamServer == null) {
            ……………………
        }
​
        // Datagram I/O
        DatagramIO datagramIO = getConfiguration().createDatagramIO(networkAddressFactory);
        if (datagramIO == null) {
            ……………………
        }
    }
​
    for (Map.Entry<InetAddress, StreamServer> entry : streamServers.entrySet()) {
        if (log.isLoggable(Level.FINE))
            log.fine("Starting stream server on address: " + entry.getKey());
        getConfiguration().getStreamServerExecutorService().execute(entry.getValue());
    }
​
    for (Map.Entry<InetAddress, DatagramIO> entry : datagramIOs.entrySet()) {
        if (log.isLoggable(Level.FINE))
            log.fine("Starting datagram I/O on address: " + entry.getKey());
        getConfiguration().getDatagramIOExecutor().execute(entry.getValue());
    }
}

在startAddressBasedTransports()方法中创建了HTTP Server的响应和DatagramIO的响应,InputMessage就是通过调用createDatagramIO()方法的时候,创建了DatagramIO对象,然后在for循环中执行了getDatagramIOExecutor().execute(entry.getValue())方法,用于执行了DatagramIOImpl中的run()方法:

public void run() {
    log.fine("Entering blocking receiving loop, listening for UDP datagrams on: " + socket.getLocalAddress());
​
    while (true) {
​
        try {
            byte[] buf = new byte[getConfiguration().getMaxDatagramBytes()];
            DatagramPacket datagram = new DatagramPacket(buf, buf.length);
​
            socket.receive(datagram);
​
            log.fine(
                    "UDP datagram received from: "
                            + datagram.getAddress().getHostAddress()
                            + ":" + datagram.getPort()
                            + " on: " + localAddress
            );
​
​
            router.received(datagramProcessor.read(localAddress.getAddress(), datagram));
​
        } catch (SocketException ex) {
            log.fine("Socket closed");
            break;
        } catch (UnsupportedDataException ex) {
            log.info("Could not read datagram: " + ex.getMessage());
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
    try {
        if (!socket.isClosed()) {
            log.fine("Closing unicast socket");
            socket.close();
        }
    } catch (Exception ex) {
        throw new RuntimeException(ex);
    }
}

调用了DatagramIOImpl中的run(),其中有一个while无限循环,用于处理数据包的响应,其中调用了router.received(),received()方法的入参类型为IncomingDatagramMessage,即将响应消息封装成了IncomingDatagramMessage对象;

public void received(IncomingDatagramMessage msg) {
    if (!enabled) {
        log.fine("Router disabled, ignoring incoming message: " + msg);
        return;
    }
    try {
        ReceivingAsync protocol = getProtocolFactory().createReceivingAsync(msg);
        if (protocol == null) {
            if (log.isLoggable(Level.FINEST))
                log.finest("No protocol, ignoring received message: " + msg);
            return;
        }
        if (log.isLoggable(Level.FINE))
            log.fine("Received asynchronous message: " + msg);
        getConfiguration().getAsyncProtocolExecutor().execute(protocol);
    } catch (ProtocolCreationException ex) {
        log.warning("Handling received datagram failed - " + Exceptions.unwrap(ex).toString());
    }
}

然后在该方法中调用了getProtocolFactory().createReceivingAsync(msg)方法:

public ReceivingAsync createReceivingAsync(IncomingDatagramMessage message) throws ProtocolCreationException {
    if (log.isLoggable(Level.FINE)) {
        log.fine("Creating protocol for incoming asynchronous: " + message);
    }
​
    if (message.getOperation() instanceof UpnpRequest) {
        IncomingDatagramMessage<UpnpRequest> incomingRequest = message;
​
        switch (incomingRequest.getOperation().getMethod()) {
            case NOTIFY:
                return isByeBye(incomingRequest) || isSupportedServiceAdvertisement(incomingRequest)
                    ? createReceivingNotification(incomingRequest) : null;
            case MSEARCH:
                return createReceivingSearch(incomingRequest);
        }
​
    } else if (message.getOperation() instanceof UpnpResponse) {
        IncomingDatagramMessage<UpnpResponse> incomingResponse = message;
​
        return isSupportedServiceAdvertisement(incomingResponse)
            ? createReceivingSearchResponse(incomingResponse) : null;
    }
​
    throw new ProtocolCreationException("Protocol for incoming datagram message not found: " + message);
}

在该方法中通过判断message.getOperation()类型来创建对应不同的Receiver,我们目前值search的Response阶段,所以会调用createReceivingSearchResponse()方法:

protected ReceivingAsync createReceivingSearchResponse(IncomingDatagramMessage<UpnpResponse> incomingResponse) {
    return new ReceivingSearchResponse(getUpnpService(), incomingResponse);
}
public ReceivingSearchResponse(UpnpService upnpService, IncomingDatagramMessage<UpnpResponse> inputMessage) {
    super(upnpService, new IncomingSearchResponse(inputMessage));
}
protected ReceivingAsync(UpnpService upnpService, M inputMessage) {
    this.upnpService = upnpService;
    this.inputMessage = inputMessage;
}
​
public M getInputMessage() {
    return inputMessage;
}

最后在一系列的透传下,进入了ReceivingAsync中,并将之间封装好的IncomingDatagramMessage对象赋值给了inputMessage;

创建RemoteDevice
public class ReceivingSearchResponse extends ReceivingAsync<IncomingSearchResponse> {
​
    final private static Logger log = Logger.getLogger(ReceivingSearchResponse.class.getName());
​
    public ReceivingSearchResponse(UpnpService upnpService, IncomingDatagramMessage<UpnpResponse> inputMessage) {
        super(upnpService, new IncomingSearchResponse(inputMessage));
    }
​
    protected void execute() throws RouterException {
​
        if (!getInputMessage().isSearchResponseMessage()) {
            log.fine("Ignoring invalid search response message: " + getInputMessage());
            return;
        }
​
        UDN udn = getInputMessage().getRootDeviceUDN();
        if (udn == null) {
            log.fine("Ignoring search response message without UDN: " + getInputMessage());
            return;
        }
​
        RemoteDeviceIdentity rdIdentity = new RemoteDeviceIdentity(getInputMessage());
        log.fine("Received device search response: " + rdIdentity);
​
        if (getUpnpService().getRegistry().update(rdIdentity)) {
            log.fine("Remote device was already known: " + udn);
            return;
        }
​
        RemoteDevice rd;
        try {
            rd = new RemoteDevice(rdIdentity);
        } catch (ValidationException ex) {
            log.warning("Validation errors of device during discovery: " + rdIdentity);
            for (ValidationError validationError : ex.getErrors()) {
                log.warning(validationError.toString());
            }
            return;
        }
​
        if (rdIdentity.getDescriptorURL() == null) {
            log.finer("Ignoring message without location URL header: " + getInputMessage());
            return;
        }
​
        if (rdIdentity.getMaxAgeSeconds() == null) {
            log.finer("Ignoring message without max-age header: " + getInputMessage());
            return;
        }
​
        // Unfortunately, we always have to retrieve the descriptor because at this point we
        // have no idea if it's a root or embedded device
        getUpnpService().getConfiguration().getAsyncProtocolExecutor().execute(
                new RetrieveRemoteDescriptors(getUpnpService(), rd)
        );
​
    }
​
}

InputMessage获取成功之后,就通过InputMessage来创建对应的RemoteDevice对象以及RemoteDevice相关的一些参数信息;

在最后执行了:

getUpnpService().getConfiguration().getAsyncProtocolExecutor().execute(
    new RetrieveRemoteDescriptors(getUpnpService(), rd)
);

至此,宣告&发现阶段的工作就执行完成了。

接近着下来就是扫描阶段;