UPNP(一)

    xiaoxiao2025-08-28  56

    http://antkillerfarm.github.io/

    概述

    官方的协议规范参见:

    http://www.upnp.org/specs/

    上图是UPNP的协议栈。详细的解释参见:

    http://www.h3c.com.cn/MiniSite/Technology_Circle/Net_Reptile/The_Five/Home/Catalog/201206/747039_97665_0.htm

    这是H3C的一个系列教程中的一篇。

    libupnp

    libupnp是UPNP协议的一个实现库。它最早由英特尔开发并开源,是目前Linux平台最流行的实现库,其官网为:

    http://pupnp.sourceforge.net/

    和一般的Linux应用库不同,libupnp没有采用搭积木的方式构建库,而是自己集成了HTTP处理、XML处理、HTTP服务器、线程池等功能(当然,这些功能做的都比较简单,是个理想的教材库)。估计这与它的跨平台目标有关。

    安装方法:

    sudo apt-get install libupnp-dev

    上图是libupnp体系结构图,其参考教程可参见:

    http://blog.csdn.net/braddoris/article/details/41646789

    这里仅对上文中的内容,做一个补充:

    1.GENA协议规范

    https://tools.ietf.org/id/draft-cohen-gena-p-base-01.txt

    2.其他参考资料

    http://max.book118.com/html/2014/0811/9385832.shtm

    http://blog.csdn.net/hqyhqyhq/article/details/17921797

    http://download.csdn.net/detail/liguangshou06/2685789

    libupnp示例详解

    libupnp自带的示例在upnp/sample路径下。编译之后,可生成三个可执行文件:

    1.tv_device。UPNP设备端实现,即UPNP的服务提供者。

    2.tv_ctrlpt。UPNP控制端实现。

    3.tv_combo。前两者的混合体。

    实际测试使用中,可以同时运行tv_ctrlpt和tv_device,以观察两者之间的交互。

    tv_device代码流程详解

    初始化

    upnp/sample/linux/tv_device_main.c: main

    upnp/sample/common/tv_device.c: device_main

    upnp/sample/common/tv_device.c: TvDeviceStart

    upnp/src/api/upnpapi.c: UpnpInit

    upnp/src/api/upnpapi.c: UpnpInitStartServers

    upnp/src/genlib/miniserver/miniserver.c: StartMiniServer

    这是整个初始化过程中,最重要的函数。它负责创建Web Server和线程池。UPNP的所有功能都在这些线程(而不是主线程)中实现。

    事件处理

    事件处理分成两步

    1.注册事件处理回调函数

    upnp/src/api/upnpapi.c: TvDeviceStart

    upnp/src/api/upnpapi.c: UpnpRegisterRootDevice

    这一步向一个中间结构注册回调函数。

    upnp/src/api/upnpapi.c: UpnpInitPreamble

    这一步调用SetGenaCallback函数,将上一步注册到中间结构中的回调函数,注册到GENA事件处理线程中。

    UpnpRegisterRootDevice有若干不同的变种:UpnpRegisterRootDevice2、UpnpRegisterRootDevice3、UpnpRegisterRootDevice4。其中,对于动态生成设备描述的方式来说,UpnpRegisterRootDevice2更好用一些。

    2.事件处理流程

    upnp/src/genlib/miniserver/miniserver.c: RunMiniServer

    upnp/src/genlib/miniserver/miniserver.c: web_server_accept

    upnp/src/genlib/miniserver/miniserver.c: schedule_request_job

    这一步从线程池中启动一个线程来处理事件。

    upnp/src/genlib/miniserver/miniserver.c: handle_request

    upnp/src/genlib/miniserver/miniserver.c: dispatch_request

    这一步根据事件类型进行分发。具体到GENA就是调用:

    upnp/src/gena/gena_callback2.c: genaCallback

    这一步根据角色的不同,调用gena_process_subscription_request(device)或gena_process_notification_event(control point)。

    upnp/src/gena/gena_device.c: gena_process_subscription_request

    这里会最终调用注册的事件处理函数。

    交互流程

    1.设备发现

    设备发现过程使用SSDP协议,其端口为:

    #define SSDP_PORT 1900

    1)主动告知

    报文:

    NOTIFY * HTTP/1.1 NTS: ssdp:alive

    函数调用:

    upnp/src/genlib/miniserver/miniserver.c: RunMiniServer

    upnp/src/genlib/miniserver/miniserver.c: ssdp_read

    upnp/src/ssdp/ssdp_server.c: readFromSSDPSocket

    upnp/src/ssdp/ssdp_server.c: ssdp_event_handler_thread

    upnp/src/ssdp/ssdp_device.c: ssdp_handle_device_request

    upnp/src/ssdp/ssdp_device.c: advertiseAndReplyThread

    upnp/src/ssdp/ssdp_server.c: AdvertiseAndReply

    upnp/src/ssdp/ssdp_device.c: DeviceAdvertisement

    upnp/src/ssdp/ssdp_device.c: CreateServicePacket msg_type=MSGTYPE_ADVERTISEMENT

    2)查询消息

    报文:

    M-SEARCH * HTTP/1.1 MAN: ssdp:discover

    函数调用:

    upnp/sample/common/tv_ctrlpt.c: TvCtrlPointCommandLoop

    upnp/sample/common/tv_ctrlpt.c: TvCtrlPointProcessCommand

    upnp/sample/common/tv_ctrlpt.c: TvCtrlPointRefresh

    upnp/src/api/upnpapi.c: UpnpSearchAsync

    upnp/src/ssdp/ssdp_ctrlpt.c: SearchByTarget

    upnp/src/ssdp/ssdp_ctrlpt.c: CreateClientRequestPacket

    3)再见消息

    报文:

    NOTIFY * HTTP/1.1 NTS: ssdp:byebye

    函数调用:

    从RunMiniServer到DeviceAdvertisement的步骤和主动告知相同。

    upnp/src/ssdp/ssdp_device.c: CreateServicePacket msg_type=MSGTYPE_SHUTDOWN

    4)添加设备、服务

    upnp/sample/common/tv_ctrlpt.c: TvCtrlPointCallbackEventHandler

    upnp/sample/common/tv_ctrlpt.c: TvCtrlPointAddDevice

    upnp/sample/common/sample_util.c: SampleUtil_FindAndParseService

    辅助函数

    upnp/sample/common/sample_util.c: SampleUtil_PrintEvent

    这是个事件打印函数,对于分析UPNP的交互流程很有帮助。

    threadutil/src/ThreadPool.c: TPJobXXX

    这是一系列和线程池操作相关的函数。

    upnp/src/uuid/uuid.c: uuid_create

    生成UUID的函数。

    upnp/src/api/UpnpString.c

    这个文件中,有个String的C语言实现,代码写得不错。

    tv_ctrlpt代码流程详解

    1.初始化

    upnp/sample/linux/tv_ctrlpt_main.c: main

    common/tv_ctrlpt.c: TvCtrlPointStart

    upnp/src/api/upnpapi.c: UpnpInit

    以下与tv_device相同。

    2.注册客户端

    common/tv_ctrlpt.c: TvCtrlPointStart

    upnp/src/api/upnpapi.c: UpnpRegisterClient

    其他部分与tv_device注册事件的流程相同。

    common/tv_ctrlpt.c: TvCtrlPointCallbackEventHandler

    这是tv_ctrlpt的事件处理函数。

    gmediarender的UPNP流程详解

    gmediarender使用libupnp库进行DLNA协议的交互。这个项目的难度中等,可作为libupnp库的进阶教程。由于大部分的内容和tv_device类似,因此,这里只列出差异的部分。

    概述

    MediaRenderer包含三个服务:

    1)RenderingControl。代码在src/upnp_control.c中。

    2)ConnectionManager。代码在src/upnp_connmgr.c中。

    3)AVTransport。代码在src/upnp_transport.c中。

    注意:MediaRenderer和所包含的服务都有版本的概念。比如MediaRenderer在XML中一般表示为urn:schemas-upnp-org:device:MediaRenderer:1,其中最后的数字1就是版本号。

    此外,MediaRenderer的版本和所包含服务的版本,有一定的对应关系。比如MediaServer:4中的ScheduledRecording:2。因为ScheduledRecording是在MediaServer:2中引入的,因此它的版本号就不可能和MediaServer的版本号一致。MediaRenderer也是一样的情况。

    一个服务可以提供若干功能。这些功能主要包括三方面:

    1.状态变量(State Variables)。状态变量可以进行查询操作。

    2.动作(Action)。get类的Action可以实现和查询状态变量类似的效果,但比后者要复杂一些。

    3.事件(Eveting)。事件是设备(Device)主动推送给控制点(Control Point)的消息,需要后者订阅(SUBSCRIBE)或退订(UNSUBSCRIBE)。

    初始化

    src/main.c: main

    src/upnp_device.c: upnp_device_init

    src/upnp_device.c: initialize_device

    upnp/src/api/upnpapi.c: UpnpInit

    动作(Action)处理

    1.数据结构

    相关数据结构按从大到小的顺序,依次为:

    服务->动作->参数->值类型->基本数据类型

    这里从最小的基本数据类型说起。由于MediaRenderer的三个服务在这里的细节都是类似的,因此下面仅以RenderingControl为例。

    1)基本数据类型

    src/upnp.h: param_datatype定义了可用的基本数据类型,比如STRING、BOOLEAN、I2、I4、UI2、UI4等。后四种都是整数类型,I表示整数,U表示无符号,2表示2个字节的宽度。

    2)值类型

    src/upnp.h: var_meta定义了值类型的数据结构。该类型的实例是src/upnp_control.c: control_var_meta。var_meta的sendevents成员的含义是,如果该值发生改变时,发送消息则设置为SENDEVENT_YES(默认值),否则设为SENDEVENT_NO。

    3)参数

    src/upnp.h: argument定义了参数的数据结构。典型示例如下:

    { "InstanceID", PARAM_DIR_IN, CONTROL_VAR_AAT_INSTANCE_ID }

    3)动作

    一个动作包含若干参数,因此动作的数据结构是一个argument数组。以SetVolume动作为例,它的参数结构的实例为src/upnp_control.c: arguments_set_vol。

    4)服务

    一个服务包含若干动作,因此服务的数据结构是一个argument的二维数组。在这里是src/upnp_control.c: argument_list。

    服务不仅包含动作,还包含了其他一些东西,这些都被统一组织到src/upnp.h: service结构中。该结构的实例是src/upnp_control.c: control_service_。

    功能发布

    功能发布用于向外界宣布本服务所支持的功能。

    gmediarender功能发布的内容是动态生成的,其函数为:src/upnp.c: gen_scpd

    事件处理

    src/upnp_device.c: event_handler

    返回值处理

    src/upnp_device.h: upnp_add_response

    调用这个函数或者它的派生函数,生成返回值的xml。

    状态变量处理

    gmediarender定义了一个变量容器来维护相关的状态变量。这个容器的代码在src/variable-container.c中。

    代码中用的比较多的是replace_var和get_var这两个进一步封装后的接口函数。

    播放处理

    gmediarender的播放处理代码在src/output_gstreamer.c中。其中最关键的结构是output_module。

    gmediarender的URI主要有两个即uri和next_uri。在pipeline的about-to-finish事件中,会做相应的处理。

    gmediaserver

    除了gmediarender项目之外,GNU还有个名叫gmediaserver的项目,也是使用libupnp库,结构和gmediarender十分相似,它的官网是:

    http://www.nongnu.org/gmediaserver/

    自制的Control Point示例

    概述

    和gmediarender相比,libupnp的sample写的并不好。这主要体现在以下方面:

    1.封装的层次太多。虽然这样一来,main函数看起来很简单,细节都被隐藏了起来。但隐藏的先决条件,是对用户使用的透明。而sample显然做不到这一点,于是,用户扩展业务功能的时候,还要翻越层层封装,才能找到需要修改的地方。

    2.使用不方便。设备功能的XML描述文件居然是写死的,扩展极为不易。(gmediarender的XML描述文件是动态生成的。)

    针对这些问题,我打算模仿gmediarender的写法,做一个Control Point的示例。

    其代码重构的核心是:将用户需要扩展的业务功能,抽象为数据结构,并将这些数据结构的内容定义放在一起,以便于用户的修改。换句话说,用户只需要修改数组的内容,而不必修改代码,即可扩展业务功能。

    在功能上,为了使这个示例更有意义,这里选择gmediarender作为和示例配套的Device程序。因为,gmediarender实现的是一个有实用价值的协议规范,而非demo,所需处理的情况也比demo复杂的多。

    Step 1

    这次,我打算从头开始搭建Control Point示例。也就是从main函数出发,逐步完善相关功能。这一步的代码在:

    https://github.com/antkillerfarm/antkillerfarm_crazy/tree/master/helloworld/upnp/step1

    该程序主要实现:

    1.基本框架。包括初始化和注册Control Point。

    2.通过SSDP的搜索功能,搜索网络设备。

    Step 2

    这一步的代码在:

    https://github.com/antkillerfarm/antkillerfarm_crazy/tree/master/helloworld/upnp/step2

    转载请注明原文地址: https://ju.6miu.com/read-1302061.html
    最新回复(0)