UPNP(一)

http://antkillerfarm.github.io/

概述

官方的协议规范参见:

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

UPNP(一)_第1张图片

上图是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

UPNP(一)_第2张图片

上图是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

你可能感兴趣的:(UPNP(一))