UPNP(一)
http://antkillerfarm.github.io/
概述
官方的協(xié)議規(guī)范參見:
http://www.upnp.org/specs/
上圖是UPNP的協(xié)議棧。詳細(xì)的解釋參見:
http://www.h3c.com.cn/MiniSite/Technology_Circle/Net_Reptile/The_Five/Home/Catalog/201206/747039_97665_0.htm
這是H3C的一個系列教程中的一篇。
libupnp
libupnp是UPNP協(xié)議的一個實現(xiàn)庫。它最早由英特爾開發(fā)并開源,是目前Linux平臺最流行的實現(xiàn)庫,其官網(wǎng)為:
http://pupnp.sourceforge.net/
和一般的Linux應(yīng)用庫不同,libupnp沒有采用搭積木的方式構(gòu)建庫,而是自己集成了HTTP處理、XML處理、HTTP服務(wù)器、線程池等功能(當(dāng)然,這些功能做的都比較簡單,是個理想的教材庫)。估計這與它的跨平臺目標(biāo)有關(guān)。
安裝方法:
sudo apt-get install libupnp-dev
上圖是libupnp體系結(jié)構(gòu)圖,其參考教程可參見:
http://blog.csdn.net/braddoris/article/details/41646789
這里僅對上文中的內(nèi)容,做一個補充:
1.GENA協(xié)議規(guī)范
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路徑下。編譯之后,可生成三個可執(zhí)行文件:
1.tv_device。UPNP設(shè)備端實現(xiàn),即UPNP的服務(wù)提供者。
2.tv_ctrlpt。UPNP控制端實現(xiàn)。
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
這是整個初始化過程中,最重要的函數(shù)。它負(fù)責(zé)創(chuàng)建Web Server和線程池。UPNP的所有功能都在這些線程(而不是主線程)中實現(xiàn)。
事件處理
事件處理分成兩步
1.注冊事件處理回調(diào)函數(shù)
upnp/src/api/upnpapi.c: TvDeviceStart
upnp/src/api/upnpapi.c: UpnpRegisterRootDevice
這一步向一個中間結(jié)構(gòu)注冊回調(diào)函數(shù)。
upnp/src/api/upnpapi.c: UpnpInitPreamble
這一步調(diào)用SetGenaCallback函數(shù),將上一步注冊到中間結(jié)構(gòu)中的回調(diào)函數(shù),注冊到GENA事件處理線程中。
UpnpRegisterRootDevice有若干不同的變種:UpnpRegisterRootDevice2、UpnpRegisterRootDevice3、UpnpRegisterRootDevice4。其中,對于動態(tài)生成設(shè)備描述的方式來說,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
這一步根據(jù)事件類型進行分發(fā)。具體到GENA就是調(diào)用:
upnp/src/gena/gena_callback2.c: genaCallback
這一步根據(jù)角色的不同,調(diào)用gena_process_subscription_request(device)或gena_process_notification_event(control point)。
upnp/src/gena/gena_device.c: gena_process_subscription_request
這里會最終調(diào)用注冊的事件處理函數(shù)。
交互流程
1.設(shè)備發(fā)現(xiàn)
設(shè)備發(fā)現(xiàn)過程使用SSDP協(xié)議,其端口為:
#define SSDP_PORT 1900
1)主動告知
報文:
NOTIFY * HTTP/1.1 NTS: ssdp:alive
函數(shù)調(diào)用:
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
函數(shù)調(diào)用:
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
函數(shù)調(diào)用:
從RunMiniServer到DeviceAdvertisement的步驟和主動告知相同。
upnp/src/ssdp/ssdp_device.c: CreateServicePacket msg_type=MSGTYPE_SHUTDOWN
4)添加設(shè)備、服務(wù)
upnp/sample/common/tv_ctrlpt.c: TvCtrlPointCallbackEventHandler
upnp/sample/common/tv_ctrlpt.c: TvCtrlPointAddDevice
upnp/sample/common/sample_util.c: SampleUtil_FindAndParseService
輔助函數(shù)
upnp/sample/common/sample_util.c: SampleUtil_PrintEvent
這是個事件打印函數(shù),對于分析UPNP的交互流程很有幫助。
threadutil/src/ThreadPool.c: TPJobXXX
這是一系列和線程池操作相關(guān)的函數(shù)。
upnp/src/uuid/uuid.c: uuid_create
生成UUID的函數(shù)。
upnp/src/api/UpnpString.c
這個文件中,有個String的C語言實現(xiàn),代碼寫得不錯。
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的事件處理函數(shù)。
gmediarender的UPNP流程詳解
gmediarender使用libupnp庫進行DLNA協(xié)議的交互。這個項目的難度中等,可作為libupnp庫的進階教程。由于大部分的內(nèi)容和tv_device類似,因此,這里只列出差異的部分。
概述
MediaRenderer包含三個服務(wù):
1)RenderingControl。代碼在src/upnp_control.c中。
2)ConnectionManager。代碼在src/upnp_connmgr.c中。
3)AVTransport。代碼在src/upnp_transport.c中。
注意:MediaRenderer和所包含的服務(wù)都有版本的概念。比如MediaRenderer在XML中一般表示為urn:schemas-upnp-org:device:MediaRenderer:1,其中最后的數(shù)字1就是版本號。
此外,MediaRenderer的版本和所包含服務(wù)的版本,有一定的對應(yīng)關(guān)系。比如MediaServer:4中的ScheduledRecording:2。因為ScheduledRecording是在MediaServer:2中引入的,因此它的版本號就不可能和MediaServer的版本號一致。MediaRenderer也是一樣的情況。
一個服務(wù)可以提供若干功能。這些功能主要包括三方面:
1.狀態(tài)變量(State Variables)。狀態(tài)變量可以進行查詢操作。
2.動作(Action)。get類的Action可以實現(xiàn)和查詢狀態(tài)變量類似的效果,但比后者要復(fù)雜一些。
3.事件(Eveting)。事件是設(shè)備(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.數(shù)據(jù)結(jié)構(gòu)
相關(guān)數(shù)據(jù)結(jié)構(gòu)按從大到小的順序,依次為:
服務(wù)->動作->參數(shù)->值類型->基本數(shù)據(jù)類型
這里從最小的基本數(shù)據(jù)類型說起。由于MediaRenderer的三個服務(wù)在這里的細(xì)節(jié)都是類似的,因此下面僅以RenderingControl為例。
1)基本數(shù)據(jù)類型
src/upnp.h: param_datatype定義了可用的基本數(shù)據(jù)類型,比如STRING、BOOLEAN、I2、I4、UI2、UI4等。后四種都是整數(shù)類型,I表示整數(shù),U表示無符號,2表示2個字節(jié)的寬度。
2)值類型
src/upnp.h: var_meta定義了值類型的數(shù)據(jù)結(jié)構(gòu)。該類型的實例是src/upnp_control.c: control_var_meta。var_meta的sendevents成員的含義是,如果該值發(fā)生改變時,發(fā)送消息則設(shè)置為SENDEVENT_YES(默認(rèn)值),否則設(shè)為SENDEVENT_NO。
3)參數(shù)
src/upnp.h: argument定義了參數(shù)的數(shù)據(jù)結(jié)構(gòu)。典型示例如下:
{ "InstanceID", PARAM_DIR_IN, CONTROL_VAR_AAT_INSTANCE_ID }
3)動作
一個動作包含若干參數(shù),因此動作的數(shù)據(jù)結(jié)構(gòu)是一個argument數(shù)組。以SetVolume動作為例,它的參數(shù)結(jié)構(gòu)的實例為src/upnp_control.c: arguments_set_vol。
4)服務(wù)
一個服務(wù)包含若干動作,因此服務(wù)的數(shù)據(jù)結(jié)構(gòu)是一個argument的二維數(shù)組。在這里是src/upnp_control.c: argument_list。
服務(wù)不僅包含動作,還包含了其他一些東西,這些都被統(tǒng)一組織到src/upnp.h: service結(jié)構(gòu)中。該結(jié)構(gòu)的實例是src/upnp_control.c: control_service_。
功能發(fā)布
功能發(fā)布用于向外界宣布本服務(wù)所支持的功能。
gmediarender功能發(fā)布的內(nèi)容是動態(tài)生成的,其函數(shù)為:src/upnp.c: gen_scpd
事件處理
src/upnp_device.c: event_handler
返回值處理
src/upnp_device.h: upnp_add_response
調(diào)用這個函數(shù)或者它的派生函數(shù),生成返回值的xml。
狀態(tài)變量處理
gmediarender定義了一個變量容器來維護相關(guān)的狀態(tài)變量。這個容器的代碼在src/variable-container.c中。
代碼中用的比較多的是replace_var和get_var這兩個進一步封裝后的接口函數(shù)。
播放處理
gmediarender的播放處理代碼在src/output_gstreamer.c中。其中最關(guān)鍵的結(jié)構(gòu)是output_module。
gmediarender的URI主要有兩個即uri和next_uri。在pipeline的about-to-finish事件中,會做相應(yīng)的處理。
gmediaserver
除了gmediarender項目之外,GNU還有個名叫g(shù)mediaserver的項目,也是使用libupnp庫,結(jié)構(gòu)和gmediarender十分相似,它的官網(wǎng)是:
http://www.nongnu.org/gmediaserver/
自制的Control Point示例
概述
和gmediarender相比,libupnp的sample寫的并不好。這主要體現(xiàn)在以下方面:
1.封裝的層次太多。雖然這樣一來,main函數(shù)看起來很簡單,細(xì)節(jié)都被隱藏了起來。但隱藏的先決條件,是對用戶使用的透明。而sample顯然做不到這一點,于是,用戶擴展業(yè)務(wù)功能的時候,還要翻越層層封裝,才能找到需要修改的地方。
2.使用不方便。設(shè)備功能的XML描述文件居然是寫死的,擴展極為不易。(gmediarender的XML描述文件是動態(tài)生成的。)
針對這些問題,我打算模仿gmediarender的寫法,做一個Control Point的示例。
其代碼重構(gòu)的核心是:將用戶需要擴展的業(yè)務(wù)功能,抽象為數(shù)據(jù)結(jié)構(gòu),并將這些數(shù)據(jù)結(jié)構(gòu)的內(nèi)容定義放在一起,以便于用戶的修改。換句話說,用戶只需要修改數(shù)組的內(nèi)容,而不必修改代碼,即可擴展業(yè)務(wù)功能。
在功能上,為了使這個示例更有意義,這里選擇gmediarender作為和示例配套的Device程序。因為,gmediarender實現(xiàn)的是一個有實用價值的協(xié)議規(guī)范,而非demo,所需處理的情況也比demo復(fù)雜的多。
Step 1
這次,我打算從頭開始搭建Control Point示例。也就是從main函數(shù)出發(fā),逐步完善相關(guān)功能。這一步的代碼在:
https://github.com/antkillerfarm/antkillerfarm_crazy/tree/master/helloworld/upnp/step1
該程序主要實現(xiàn):
1.基本框架。包括初始化和注冊Control Point。
2.通過SSDP的搜索功能,搜索網(wǎng)絡(luò)設(shè)備。
Step 2
這一步的代碼在:
https://github.com/antkillerfarm/antkillerfarm_crazy/tree/master/helloworld/upnp/step2
總結(jié)
- 上一篇: DLNA, PulseAudio, di
- 下一篇: 图像处理理论(一)——直方图、二值化、滤