Cling是由Java实现的DLNA/UPnP
协议栈。可以开发出类似多屏互动、资源共享、远程控制等功能的应用,通过Android 应用管理一个或多个设备,将音频、视频、图片推送到指定设备显示
Meta
ActionInvocation
动作请求的输入、输出和失败值
Action
描述一个动作和它的输入输出参数
ActionExecutor
处理ActionInvocation
的处理器
Service
服务的元数据,里面维护了一组Action
,有LocalService
和RemoteService
两个子类
LocalService
本地创建服务的元数据,维护了一张对应Action
的ActionExecutor
表
RemoteService
远程设备上发现服务的元数据,包括获取服务描述的URI,调用它的Action
和订阅事件
Resource
一个可寻址的对象,通过Registry
存储、管理和访问,对不同的资源有若干子类
1 | public class Resource<M> { |
ServiceControlResource
1 | public class ServiceControlResource extends Resource<LocalService> { |
Device
描述一个设备,根或者嵌套的
1 | public abstract class Device<DI extends DeviceIdentity, D extends Device, S extends Service> implements Validatable { |
RemoteDevice
网络上发现的设备
LocalDevice
本地创建的设备
DeviceIdentity
唯一的设备名,在网络发现时提供和接收
1 | public class DeviceIdentity { |
RemoteDeviceIdentity
远程设备的额外信息,包括设备描述的URL,未来应该使用的本地网络接口,可能有对方设备的MAC
1 | public class RemoteDeviceIdentity extends DeviceIdentity { |
GENASubscription
建立的订阅,有标识符、过期时间、序列处理和状态变量值,本地订阅和远端订阅都维护在Registry
里
LocalGENASubscription
对于本地服务的订阅,即其他设备对自己的订阅
RemoteGENASubscription
对于远端服务的订阅,即自己对其他设备的订阅,一旦建立,当从远端服务接收事件时会调用eventReceived()
ControlPoint
异步执行网络搜索、操作、事件订阅的统一接口,后面的所有操作都要用到它
1 | public interface ControlPoint { |
主要是search()
和execute()
接口,ControlPointImpl
是提供的实现类
ControlPointImpl
1 |
|
其中一个execute()
,其实就是获得配置中给定的线程池,把命令放进去执行
1 |
|
可以发现search()
也是通过execute()
实现的
ActionCallback
执行的动作基类,它是个可执行的Runnable
,主要关注它的run()
方法
1 |
|
分为本地服务和远程服务,远程服务的话通过控制点发命令给目标URL,然后等待响应
org.teleal.cling.support
包里都是ActionCallback
的子类,仿照库里提供的一些命令可以很方便的根据协议添加
一般都是在构造函数中提供字段,并且实现success()
和fail()
方法供回调,成功或失败后的处理经常不一样,所以一般在使用命令的地方实现一个这样的类
1 | Service service = device.findService(new UDAServiceId("SwitchPower")); |
注释中提供的示例代码
SubscriptionCallback
订阅和接受事件,通过GENA
,它也是一个Runnable
1 |
|
也是分为本地服务和远程服务的订阅
1 | private void establishRemoteSubscription(RemoteService service) { |
failed()
、established()
、ended()
、eventReceived()
、eventMissed()
都需要子类实现
Registry
UPNP协议栈的核心,追踪设备和资源,一个运行的UPNP栈有一个Registry
,任何被发现的设备被添加到这个Registry
里,暴露的本地设备也是一样,然后会持续的维持这些设备,必要时刷新他们的声明,过期时移除他们,同样追踪GENA
事件订阅
ProtocolFactory
UPNP协议的工厂,工厂创建可执行的协议基于接收到的UPNP消息,或者本地设备/搜索/服务的元数据
1 | public interface ProtocolFactory { |
ProtocolFactoryImpl
1 |
|
创建接收异步消息的协议,分为请求和响应,其中请求包括通知ReceivingNotification()
和搜索ReceivingSearch()
,响应就是搜索结果的响应ReceivingSearchResponse()
1 |
|
创建接收同步消息的协议,根据方法不同返回不同的协议
创建发送消息的协议比较简单,直接创建对应的
SendingAsync
异步处理协议、发送消息的基类,是一个Runnable
子类需要实现execute()
方法
SendingSync
同步处理协议、发送消息的基类
1 | public abstract class SendingSync<IN extends StreamRequestMessage, OUT extends StreamResponseMessage> extends SendingAsync { |
同步等待处理结果,子类需要实现executeSync()
方法
SendingAction
发送控制消息
1 | protected IncomingActionResponseMessage invokeRemote(OutgoingActionRequestMessage requestMessage) { |
executeSync()
里面调用了invokeRemote()
方法,通过sendRemoteRequest()
发送请求
1 | protected StreamResponseMessage sendRemoteRequest(OutgoingActionRequestMessage requestMessage) throws ActionException { |
发送请求,可以看到是通过配置里给定的SOAPActionProcessor
写入请求
SendingEvent
发送GENA
事件消息到远程订阅者
1 | public SendingEvent(UpnpService upnpService, LocalGENASubscription subscription) { |
构造函数里对订阅的每一个URL生成了请求消息executeSync()
里都发出去
SendingSubscribe
发送订阅消息,获得响应,Registry.addRemoteSubscription()
,调用subscription.establish()
SendingUnsubscribe
发送退订消息
SendingRenewal
发送续订消息
SendingSearch
发送搜索请求
1 |
|
SendingNotification
向注册的本地设备发送通知消息,两个子类分别通知存活和死亡
SendingNotificationAlive
SendingNotificationByebye
SOAPActionProcessor
完成UPNP SOAP
和动作请求的互相转换
UPNP协议层处理本地和远程的动作请求,UPNP传输层接收和返回请求和响应,这个处理器是两层之间的适配器
1 | public interface SOAPActionProcessor { |
SOAPActionProcessorImpl
这个是基于W3C DOM
的默认实现的XML解析器
ReceivingAsync
所有异步处理协议的基类,处理UPnP消息的接收
ReceivingSync
所有同步处理协议的基类,处理UPnP消息的接收并返回响应
1 | public abstract class ReceivingSync<IN extends StreamRequestMessage, OUT extends StreamResponseMessage> extends ReceivingAsync<IN> { |
ReceivingAction
接收动作
1 |
|
接收消息,转化为ActionInvocation
,然后由对应动作的处理器处理
LocalService
里的actionExecutors
并没有被赋过值,Why?
可能因为手机端只是发送动作给设备,而不接收动作
ReceivingEvent
接收GENA事件
1 |
|
接收到GENA事件,调用远端订阅的eventReceived()
ReceivingSubscribe
接收订阅,根据id和头部信息选择续订或新订阅,续订就是延长时间并更新,新订阅就是Registry.addLocalSubscription()
1 | subscription = new LocalGENASubscription(service, timeoutSeconds, requestMessage.getCallbackURLs()) { |
添加这个subscription
,在eventReceived()
的时候会发送事件
ReceivingUnsubscribe
接收退订
ReceivingRetrieval
ReceivingSearch
接收搜索请求,响应本地已注册的设备
1 |
|
对每个网络地址,发送响应
1 | protected void sendResponses(UpnpHeader searchTarget, NetworkAddress activeStreamServer) { |
searchTarget
是UPnP头,根据头的不同做不同处理
ReceivingNotification
接收通知消息
1 |
|
如果是存活消息,处理和ReceivingSearchResponse
类似,如果是再见消息,就移除设备
ReceivingSearchResponse
接收搜索的响应消息
1 | protected void execute() { |
先对EasyLink
消息做特殊处理,然后Registry.update()
看是否已经注册有这个设备,最后把RemoteDevice
封装成RetrieveRemoteDescriptors
处理
1 | private synchronized void matchEasylink(IncomingSearchResponse msg) { |
EasyLink
处理拿到IP和UUID,然后发广播通知Android系统
RetrieveRemoteDescriptors
一个Runnable
,获取所有的远程设备XML描述,分析并创建设备和服务元数据
在run()
中判断一下设备URL是否已存在,设备是否已存在于Registry
中,然后开始描述设备
1 | protected void describe() { |
这里请求设备描述信息,然后解析,这个过程包含了多个网络请求和XML解析,是很耗时的
1 | protected void describe(String descriptorXML) { |
通过DeviceDescriptorBinder
解析XML,得到RemoteDevice
1 | protected RemoteDevice describeServices(RemoteDevice currentDevice) throws DescriptorBindingException, ValidationException { |
对RemoteDevice
进一步解析服务、内联设备和图标
1 | protected RemoteService describeService(RemoteService service) throws DescriptorBindingException, ValidationException { |
通过ServiceDescriptorBinder
解析XML,得到RemoteService
Router
网络传输层接口,封装传输层,为上层提供方法来发送UPNP流(HTTP)和发送UDP数据报,还有局域网广播Router
维护监听套接字和服务
1 | public interface Router { |
Router
构造的时候会创建一组StreamServer
、DatagramIO
、MulticastReceiver
,然后执行他们
RouterImpl
1 |
|
发送UDP消息,就是遍历所有的接口发送
1 |
|
发送HTTP数据,就是使用StreamClient
发送请求
1 |
|
接收UDP消息,构建一个ReceivingAsync
并执行
1 |
|
接收HTTP数据,直接执行UpnpStream
DatagramIO
接受单播和发送UDP数据报的服务,每个IP绑定一个
该服务在一个套接字上监听UDP单播数据报,监听循环在run()
中开始,任何接收的数据报然后被转化为IncomingDatagramMessage
,然后被Router.received()
处理
1 | public interface DatagramIO<C extends DatagramIOConfiguration> extends Runnable { |
DatagramIOImpl
1 | public void run() { |
循环中先由套接字获得UDP数据报,然后交由Router.received()
处理
1 | synchronized public void send(DatagramPacket datagram) { |
通过Socket
发送数据报,这是一个MulticastSocket
MulticastSocket
多播套接字,参见java.net
包
StreamClient
发送TCP流请求消息的服务
1 | public interface StreamClient<C extends StreamClientConfiguration> { |
StreamClientImpl
StreamServer
接收TCP流的服务,每个IP一个,该服务在一个套接字上监听TCP连接
1 | public interface StreamServer<C extends StreamServerConfiguration> extends Runnable { |
StreamServerImpl
1 |
|
在循环中监听套接字,获得的UpnpStream
交由Router.received()
处理
UpnpStream
代表一个HTTP请求或响应的Runnable
1 | public StreamResponseMessage process(StreamRequestMessage requestMsg) { |
process()
从StreamRequestMessage
创建出一个ReceivingSync
并执行,然后返回StreamResponseMessage
HttpExchangeUpnpStream
1 |
|
MulticastReceiver
接受UDP数据报广播的服务,每个网络接口一个,该服务在一个套接字上监听UDP数据报,监听循环在run()
中开始,任何接收的数据报然后被转化为IncomingDatagramMessage
,然后被Router.received()
处理
1 | public interface MulticastReceiver<C extends MulticastReceiverConfiguration> extends Runnable { |
MulticastReceiverImpl
一些流程
搜索设备(发送UDP数据报)
ControlPoint
: 调用search()
,即处理SendingSearch
Router
: 循环调用send()
DatagramIO
: 对每一个端口,调用send()
,将消息封装成DatagramPacket
MulticastSocket
: 调用send()
扩展看一下
java.net
包
发送命令(发送TCP数据流)
ControlPoint
: 调用execute()
,执行ActionCallback
,即处理SendingActoin
Router
: 调用send()
StreamClient
: 调用sendRequest()
DefaultHttpClient
: 调用execute()
接收UDP数据报
DatagramIO
: 循环中在套接字上接收DatagramPacket
,转换成IncomingDatagramMessage
Router
:received()
,创建一个ReceivingAsync
并执行ReceivingAsync
: 执行execute()
,由不同的子类分别处理,拿到对应的数据结构
接收TCP数据流
StreamServer
: 循环中监听套接字,得到UpnpStream
Router
:received()
,其实就是执行这个UpnpStream
UpnpStream
: 从StreamRequestMessage
创建出一个ReceivingSync
并执行,然后返回StreamResponseMessage
ReceivingSync
: 执行executeSync()
,由不同的子类分别处理,拿到对应的数据结构