基于UPnP协议_cling开源库实现分析

1,477 阅读2分钟

简介

Cling

Cling类库是由Java实现的DLNA/UPnP协议栈。基于DLNA/UPnP可以开发出类似多屏互动、资源共享、远程控制等功能的应用,通过Android应用管理一个或多个设备,将音频、视频、图片推送到指定设备显示、播放。

UPnP的实现类库有很多,如CyberGarage、Platinum UPnP、libupnp等一些开源库,例如:

  • Platinum UPnP,基于C++开发,可以支持Windows,iOS,Android等平台,XBMC就是使用的此库。
  • Cling,基于Java开发,也是后续要介绍的,市面上很多支持DLNA功能的App都是使用的此库,如BubbleUPnP。

Cling基本使用

Cling库包括两个模块:

  • Cling Core

    • 核心类库,基于UDA1.0,实现了定义服务、设备发现,通过ControlPoint发送指令等UPnP的基本功能;
  • Cling Support

    • 顾名思义该包为Cling中一些功能的扩展,如:ConnectionManager Service、AVTransport Service、RenderingControl Service以及LastChange;

Android平台基本使用

以Android平台创建UPnP服务并顶用相关的控制方法介绍Cling的基本使用:

cling_work.drawio.png

UPnP协议支持Android设备作为Control Point和UPnP Device两种设备类型;

版本迭代

image-20220108172459794.png

image-20220108172602432.png

Cling Core和Cling Support两个模块的迭代最新的为2018年,Cling目前已停止维护,Cling提供了源码,开发人员可以自己维护和扩展功能;

总体设计

概述

UPnP协议:

UPNP协议架构.drawio.png

Cling作为UPnP协议栈,其主旨即是在设备的发现、控制等过程中对不同的协议及内容进行处理。UPnP协议栈由多个层组成,Cling只关心底层的TCP/IP协议以及包含SSDP(简单设备发现协议)、SOAP(简单对象访问协议)、GENA(一般事件通知协议)协议的层;

使用场景

以一个简单的设备使用场景为例:

使用场景:用户将手机A中的媒体内容播放到电视B上,前提:A、B在同一个局域网中。

  • A加入到多播组中,建立MulticastSocket监听多播信息;
  • A向多播发出M-SEARCH报文;
  • B获取多播的报文,判断是否符合条件,若符合向多播地址回应OK报文,报文中包含description URL;
  • A监听多播获取到相关报文,并通过URL获得设备描述信息;
  • A通过AVTransport Service将媒体内容推送到B并播放;

在整个过程中A通过Cling既充当了DMC又作为DMS,而B作为DMR播放媒体内容;

流程图

设备发现及控制流程

设备发现及控制流程.png

媒体播放流程

媒体播放流程.png

详细涉及

类图

cling类图.png

类详解

  1. ControlPoint:异步执行网络搜索、操作、事件订阅的统一接口,后面的所有操作都要用到它;
  2. RegistryListener:注册表监听器,用于监听所有类型的Device的新增、删除和更新;
  3. Registry:UPnP协议栈的核心,追踪设备和资源;
  4. AndroidUpnpService:Android侧UPnP协议服务,其中包含了registry以及ControlPoint等;
  5. ProtocolFactory:UPnP协议工厂,工厂创建可执行的协议基于接收到的UPnP消息,或者本地设备/搜索/服务的元数据;
  6. Router:网络传输层接口,疯转传输层,为上层提供方法来发送UPnP流(HTTP)和发送UDP数据报,还有局域网广播;
  7. StreamClient:发送TCP流请求消息的服务;
  8. StreamServer:接收TCP流的服务,每个IP一个,该服务在一个套接字上监听TCP连接;
  9. DatagramIO:接收单播和发送UDP数据报的服务,每个IP绑定一个,该服务在一个套接字上监听UDP单播数据报,监听循环在run()中开始,任何接收的数据报然后被转化为IncomingDatagramMessage,然后被Router.received()处理;
  10. MulticastReceiver:接收UDP数据报广播的服务,每个网络接口一个,该服务在一个套接字上监听UDP数据报,监听循环在run()中开始,任何接收的数据报然后被转化为IncomingDatagramMessage,然后被Router.received()处理;
  11. UpnpServiceConfiguration:UPnP协议配置参数;
  12. GENAEventProcessor:GENA事件处理器;
  13. SOAPActionProcessor:完成UPnP SOAP和动作请求的互相转换;
  14. DatagramProcessor:数据报处理器;
  15. ServiceDescriptorBinder:远程Service描述文档解析类;
  16. DeviceDescriptorBinder:远程Device描述文档解析类;
  17. NetworkAddressFactory:网络地址工厂类;

流程源码分析

UpnpRequest讲解

首先我们通过上一章的UPnP协议分析可知,client和server之间的数据包请求类型,这些类型定义在UpnpRequest.java中:

public class UpnpRequest extends UpnpOperation {
​
    public static enum Method {
​
        GET("GET"),
        POST("POST"),
        NOTIFY("NOTIFY"),
        MSEARCH("M-SEARCH"),
        SUBSCRIBE("SUBSCRIBE"),
        UNSUBSCRIBE("UNSUBSCRIBE"),
        UNKNOWN("UNKNOWN");
​
        private static Map<String, Method> byName = new HashMap<String, Method>() {{
            for (Method m : Method.values()) {
                put(m.getHttpName(), m);
            }
        }};
​
        private String httpName;
​
        Method(String httpName) {
            this.httpName = httpName;
        }
​
        public String getHttpName() {
            return httpName;
        }
​
        public static Method getByHttpName(String httpName) {
            if (httpName == null) return UNKNOWN;
            Method m = byName.get(httpName.toUpperCase(Locale.ENGLISH));
            return m != null ? m : UNKNOWN;
        }
    }
​
    private Method method;
    private URI uri;
​
    public UpnpRequest(Method method) {
        this.method = method;
    }
​
    public UpnpRequest(Method method, URI uri) {
        this.method = method;
        this.uri = uri;
    }
​
    public UpnpRequest(Method method, URL url) {
        this.method = method;
        try {
            if (url != null) {
                this.uri = url.toURI();
            }
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e);
        }
    }
​
    public Method getMethod() {
        return method;
    }
​
    public String getHttpMethodName() {
        return method.getHttpName();
    }
​
    public URI getURI() {
        return uri;
    }
​
    public void setUri(URI uri) {
        this.uri = uri;
    }
​
    @Override
    public String toString() {
        return getHttpMethodName() + (getURI() != null ? " " + getURI() : "");
    }
}

这个类主要是用于定义request的,里面包含了request需要定义的属性值,我们主要关注request的METHOD的类型,有NOTIFY、GET、POST等一系列的method;

Router讲解

在讲解UPnP工作流程之前,我们需要先了解cling中的一个类,这个类控制了下面所有的逻辑:Router.java;

public interface Router {
​
    public UpnpServiceConfiguration getConfiguration();
​
    public ProtocolFactory getProtocolFactory();
​
    boolean enable() throws RouterException;
​
    boolean disable() throws RouterException;
​
    void shutdown() throws RouterException ;
​
    boolean isEnabled() throws RouterException;
​
    void handleStartFailure(InitializationException ex) throws InitializationException;
​
    public List<NetworkAddress> getActiveStreamServers(InetAddress preferredAddress) throws RouterException;
​
    public void received(IncomingDatagramMessage msg);
​
    public void received(UpnpStream stream);
​
    public void send(OutgoingDatagramMessage msg) throws RouterException;
​
    public StreamResponseMessage send(StreamRequestMessage msg) throws RouterException;
​
    public void broadcast(byte[] bytes) throws RouterException;
​
}
​

数据传输层接口,负责接收和发送UPnP和UDP消息,或者将接收到的数据流广播给局域网内的其他设备,是数据收发处理的核心类,实现类为RouterImpl。

其中包括了几个方法,我们现在看一下这几个方法的作用:

方法名方法解释备注
enable()1.得到配置中的NetworkAddressFactory,其目前实现为NetworkAddressFactoryImpl; 2.调用startInterfaceBasedTransports()为NetworkAddressFactory中的每个网络接口绑定一个多播接收器MulticastReceiver,用来监听多播地址,并处理获取到的数据,key-value形式存储在multicastReceivers中; 3.调用startAddressBasedTransports为NetworkAddressFactory的每个地址绑定一个StreamServer和DatagramIO,监听并进行数据处理。key-value形式存储在streamServers中; 4.创建一个StreamClient用于发送TCP消息;通过可重入锁写锁控制启动和停止的并发
desable()停止StreamClient; 停止StreamServers中每个StreamServer; 停止MulticastReceivers中的每个MulticastReceiver; 停止DatagramIOs中每个DatagramIO;通过可重入锁写锁控制启动和停止的并发
getActiveStreamServers()根据preferredAddress得到活跃的StreamServer,如果preferredAddress对应的StreamServer存储且活跃则返回,否则返回当前所有活跃的StreamServer
received()根据消息类型得到协议并执行
send(StreamRequestMessage msg)通过StreamClient发送TCP包;
send(OutgoingDatagramMessage msg)send()方法向所有DatagramIO发送UDP包;
broadcast()向所有DatagramIO广播发送数据

Router接口类,实现类为RouterImpl,RouterImpl的创建在AndroidUpnpServiceImpl的onCreate()方法中创建;

后续拆解分析流程;