UPnP簡析

發佈於2012年12月12日 -

UPnP的全稱是Universal Plug and Play,是由UPnP論壇提出並完善的一套網絡協議。注意這裏使用了「一套」這個詞語——UPnP並不是一個協議,而是一組網絡協議的集合。如果你還不瞭解UPnP是做什麼的,微軟的這篇文章描述了一個使用UPnP的場景,這篇文章寫於2001年,但文中描寫的場景即使在今天看來,也足夠人們期待。這也從另一方面說明,這項技術還有待進一步發展和普及。

本文將從協議的角度,並儘量使用通俗的語言,來簡單分析一下UPnP的工作原理。

一、UPnP的結構

UPnP由發現、描述、控制、事件和展示幾部分組成。每部分功能如下:

  • 發現:在這個階段,網絡中的服務提供者(稱作「設備」)和使用服務者(稱作「控制點」)將互相發現對方。
  • 描述:當控制點需要訪問一個設備時,它需要知道設備的信息,設備通過XML的方式描述自己的信息、服務等。
  • 控制:當控制點得到設備的描述信息後,即可對設備進行控制。
  • 事件:當設備的狀態改變時,如果控制點訂閱了這個消息,將會收到事件通知。
  • 展示:設備可以通過瀏覽器展示自己。(這個功能通常不會使用,本文也不加以討論)

在逐一分析前四個部分之前,我們還需要明確一個事實:UPnP的基礎是,每個設備或控制點都已經處在一個網絡中,並且這個網絡內允許組播(或稱之爲多播,以下稱組播)。

此外,UPnP使用239.255.255.250:1900這個組播地址。

二、發現

UPnP使用簡單發現協議(SSDP)完成發現,這是一個工作在UDP上的HTTP協議。

當一個設備加入網絡,它將向組播組發送類似這樣的消息:

NOTIFY * HTTP/1.1
Host: 239.255.255.250:1900
Cache-control: max-age=1800
Location: http://192.168.0.1:49152/des.xml
Nt: upnp:rootdevice
Nts: ssdp:alive
Usn: uuid:de5d6118-bfcb-918e-0000-00001eccef34::upnp:rootdevice

這裏,各項含義如下:

  • Host:這裏必須使用IANA(Internet Assigned Numbers Authority)爲SSDP預留的組播地址:239.255.255.250:1900
  • Cache-control:max-age的數值表明設備將在這段時間(單位爲秒)後失效,因此,設備應當在失效前,重發這樣的消息。標準指出,這裏的數值應當不小於於1800秒(30分鐘),但實際上,這裏的取值範圍取決於UPnP的實現。
  • Location:設備描述文件的URL。
  • Nt:Notification Type的縮寫,這裏的值(upnp:rootdevice)表明這是一個「根設備」。每個設備可以有自己的子設備,本文只考慮設備獨立的情形。
  • Nts:Notification Sub Type的縮寫,標準規定必須是ssdp:alive
  • Usn:Unique Service Name的縮寫,是一個設備實例的標識符。

通過這樣的消息,控制點便可以知道網絡中的設備。

與之對應的是,當控制點加入網絡時,它也將組播一個消息,用來發現設備,例如:

M-SEARCH * HTTP/1.1
Host: 239.255.255.250:1900
Man: "ssdp:discover"
Mx: 5
ST: ssdp:rootdevice

各項含義如下:

  • Host:同上。
  • Man:必須是"ssdp:discover",注意這裏的引號不能省略。
  • Mx:1到5之間的一個值,表示最大的等待響應的秒數。
  • ST:Search Target的縮寫,表示搜索的節點類型。

而設備可以根據這樣的搜索產生響應,格式如下:

HTTP/1.1 200 OK
Cache-control: max-age=1800
Ext:
Location: http://192.168.0.1:2345/xx.xml
Server: Microsoft-Windows-NT/5.1 UPnP/1.0 UPnP-Device-Host/1.0
ST: ST:urn:schemas-upnp-org:service:ContentDirectory:1
USN: uuid:60b2e186-b084-44af-ac09-1c64ea1bb364::urn:schemas-upnp-org:service:ContentDirectory:1

通過這樣的流程,控制點即可完成對設備的發現。

三、描述

設備的描述通過HTTP協議完成,獲取描述的URL已經在上一節給出。

由於設備描述的標籤很多,這裏僅以一例作爲演示:

<?xml version="1.0" encoding="utf-8"?>
<root xmlns="urn:schemas-upnp-org:device-1-0">
  <specVersion>
    <major>1</major>
    <minor>1</minor>
  </specVersion>

  <device>
    <deviceType>urn:schemas-upnp-org:device:BinaryLight:1</deviceType>
    <friendlyName>Kitchen Lights</friendlyName>
    <manufacturer>OpenedHand</manufacturer>
    <modelName>Virtual Light</modelName>
    <UDN>uuid:cc93d8e6-6b8b-4f60-87ca-228c36b5b0e8</UDN>

    <serviceList>
      <service>
        <serviceType>urn:schemas-upnp-org:service:SwitchPower:1</serviceType>
        <serviceId>urn:upnp-org:serviceId:SwitchPower:1</serviceId>
        <SCPDURL>/SwitchPower1.xml</SCPDURL>
        <controlURL>/SwitchPower/Control</controlURL>
        <eventSubURL>/SwitchPower/Event</eventSubURL>
      </service>
    </serviceList>
  </device>
</root>

本例中,specVersion節點的內容是由標準規定的,所以我們僅討論device節點的內容:

  • deviceType:設備類型,格式爲"urn:schemas-upnp-org:device:deviceType:v",這裏deviceTypev是由設備定義的。
  • friendlyName:一個更友好的設備名。
  • manufacturer:製造商。
  • modelName:型號。
  • UDN:Unique Device Name的縮寫,設備的UUID。
  • serviceList:服務列表。

對於每一個服務,信息包括:

  • serviceType:與deviceType類似,這裏的後兩段由服務定義。
  • serviceId:服務ID,通常與serviceType對應。
  • SCPDURL:服務描述的URL。
  • controlURL:用於控制的URL。
  • eventSubURL:用於訂閱事件的URL。

一個服務描述的示例如下:

<?xml version="1.0" encoding="utf-8"?>
<scpd xmlns="urn:schemas-upnp-org:service-1-0">
  <specVersion>
    <major>1</major>
    <minor>1</minor>
  </specVersion>
  <actionList>
    <action>
      <name>SetTarget</name>
      <argumentList>
        <argument>
          <name>NewTargetValue</name>
          <relatedStateVariable>Target</relatedStateVariable>
          <direction>in</direction>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetTarget</name>
      <argumentList>
        <argument>
          <name>RetTargetValue</name>
          <relatedStateVariable>Target</relatedStateVariable>
          <direction>out</direction>
        </argument>
      </argumentList>
    </action>
    <action>
      <name>GetStatus</name>
      <argumentList>
        <argument>
          <name>ResultStatus</name>
          <relatedStateVariable>Status</relatedStateVariable>
          <direction>out</direction>
        </argument>
      </argumentList>
    </action>
  </actionList>
  <serviceStateTable>
    <stateVariable sendEvents="no">
      <name>Target</name>
      <dataType>boolean</dataType>
      <defaultValue>0</defaultValue>
    </stateVariable>
    <stateVariable sendEvents="yes">
      <name>Status</name>
      <dataType>boolean</dataType>
      <defaultValue>0</defaultValue>
    </stateVariable>
  </serviceStateTable>
</scpd>

這裏,actionList定義了「行爲」,serviceStateTable定義了「狀態變量」。

每個行爲都由一個名稱(name)和若干參數(argument)組成。而參數由名字(name)、傳遞方向(direction,取值inout)及關聯的狀態變量(relatedStateVariable)組成。

狀態變量之所以叫做狀態變量,是用來標明UPnP設備或程序的一些狀態的。一個程序可以訂閱(subscribe)狀態的變化,從而得到通知。而一個參數之所以必須關聯一個狀態變量,是因爲狀態變量的類型決定了參數的類型,因此,對於某些情形,我們可以使用「啞」的狀態變量。

四、控制

UPnP使用SOAP完成控制。SOAP工作在HTTP上,使用XML來描述遠程調用並返回結果。

以下是一個控制請求的例子:

POST /control/url HTTP/1.1
HOST: hostname:portNumber
CONTENT-TYPE: text/xml; charset="utf-8"
CONTENT-LENGTH: length of body
USER-AGENT: OS/version UPnP/1.1 product/version
SOAPACTION: "urn:schemas-upnp-org:service:serviceType:v#actionName"

<?xml version="1.0"?>
<s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:actionName xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
            <argumentName>in arg value</argumentName>
        </u:actionName>
    </s:Body>
</s:Envelope>

這裏,actionNameargumentName就是在協議描述中定義的行爲名稱和參數名稱。

而調用的結果同樣以XML方式返回:

HTTP/1.1 200 OK
CONTENT-TYPE: text/xml; charset="utf-8"
DATE: when response was generated
SERVER: OS/version UPnP/1.1 product/version
CONTENT-LENGTH: bytes in body

<?xml version="1.0"?>
<s:Envelope
 xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
 s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
    <s:Body>
        <u:actionNameResponse xmlns:u="urn:schemas-upnp-org:service:serviceType:v">
            <argumentName>out arg value</argumentName>
        </u:actionNameResponse>
    </s:Body>
</s:Envelope>

五、事件

通過事件這一機制,控制點可以監聽設備狀態的變化。需要指出的是,事件的通訊通常是單播的。

訂閱與退訂

訂閱的URL是在服務描述中給出的。一個訂閱的例子如下:

SUBSCRIBE publisher path HTTP/1.1
HOST: publisher host:publisher port
USER-AGENT: OS/version UPnP/1.1 product/version
CALLBACK: <delivery URL>
NT: upnp:event

此處,NT(Notification Type)取upnp:event表示訂閱事件;CALLBACK的值是回調的URL。

與之對應的是訂閱的響應:

HTTP/1.1 200 OK
DATE: when response was generated
SERVER: OS/version UPnP/1.1 product/version
SID: uuid:subscription-UUID
CONTENT-LENGTH: 0
TIMEOUT: Second-1800

這裏有兩個參數比較重要:

  • SID:本訂閱的標識符,通常使用UUID。
  • TIMEOUT:這裏的值表示本訂閱的有效期(秒)。這意味着在超時前,必須續訂。

續訂的請求與訂閱類似,只是附加了SID:

SUBSCRIBE publisher path HTTP/1.1
HOST: publisher host:publisher port
SID: uuid:subscription UUID

退訂也是類似的:

UNSUBSCRIBE publisher path HTTP/1.1
HOST: publisher host:publisher port
SID: uuid:subscription UUID

事件消息

我們直接來分析數據包:

NOTIFY delivery path HTTP/1.1
HOST: delivery host:delivery port
CONTENT-TYPE: text/xml; charset="utf-8"
NT: upnp:event
NTS: upnp:propchange
SID: uuid:subscription-UUID
SEQ: event key
CONTENT-LENGTH: bytes in body

<?xml version="1.0"?>
<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
    <e:property>
        <variableName>new value</variableName>
    </e:property>
</e:propertyset>

這裏,每個產生事件的狀態變量對應一個e:property標籤,variableName是變量名。

此外,SEQ是消息的順序號,從0開始。

當訂閱者收到消息之後,必須在30秒內發送確認:

HTTP/1.1 200 OK

如果訂閱者沒有確認,設備仍然會發送之後的消息,直到本次訂閱超時。

六、實現

從前面的介紹來看,UPnP的原理並不複雜。幸運的是,在網上可以找到很多UPnP的SDK。

UPnP論壇上提供了一系列SDK,包括UPnP論壇成員提供的解決方案和一些開源的實現,這些內容可以在這裏找到:http://upnp.org/sdcps-and-certification/resources/sdks/

參考資料

  1. UPnP Hacks: UPnP background
  2. UPnP Device Architecture version 1.1
  3. Writing a UPnP Service