如果你想要像 MS 的網路芳臨 (netbios) 一樣,讓你的軟體能自動發現 LAN 上 面其它正在執行相同軟體的機器,並在連上網路時,自動通知其它機器,那麼 SSDP 這個通訊協定能幫助你。 本文討論如何使用 miniSSDPd,幫助你的應用程式使用 SSDP。 並以 Python 為例。
SSDP -- Simple Service Discovery Protocol,如其名稱,在 LAN 環境下 提供 service 的搜尋/發現的服務。 應用程式可以透過該協定,向 LAN 裡面的其它機器廣播應用程式所提供的 service, 也可以透過 SSDP 搜尋網路上所有的特定 service。 SSDP 的功能其實就和 DNS 差不多,用以查詢 service 和位址之間的對應關係。 和 DNS 不同的是, SSDP 不需要集中化 (centeralized) 的固定 server,而是 透過 multicasting 和 P2P 的方式運作。
SSDP 其實不複雜,可以想成是在 multicasting 的 UDP 上執行 HTTP protocol (HTTPU),而一般的 HTTP protocol 是使用 TCP。 因此,你可以透過 multicasting 的方式,把 HTTP 的 request 送到 LAN 裡的 每一台執行 SSDP 的機器。 收到 request 的 SSDP service 若有足夠的資訊,則回應該 request,反之則 丟掉該 request 不回應。 因此, SSDP 不需要一個固定的 server。
SSDP 有兩個重要的 request,一個是 NOTIFY,另一個則是 MSEARCH。 NOTIFY 是向其它機器廣播服務的項目和位址,你可以透過發送 NOTIFY 通知 LAN 裡 所有機器,告知你所提供的服務項目。 而 MSEARCH 則是用來尋問其它機器,是否有提供特定的 service,以取得這些 service 的位址。 因此,為了得知其它機器提供的 service,應用程式必需收集 NOTIFY,並/或送出 MSEARCH request。 實作這兩者都需要花一點時間,而且,你必需等待其它機器送 NOTIFY 或 MSEARCH 的回應。 因此,使用上很沒有效率。 miniSSDPd 是一個獨立的 daemon,隨時幫你收集透過 LAN 送來的 NOTIFY,並記錄 下來。 因此,我們可以直接向 miniSSDPd 查詢,就能隨時取得最新的資訊,不用等待。
如何廣播你的服務?
當一個 SSDP 的服務上線之後,必需定期的在 LAN 上面進行廣播。 如此,其它機器才能知道該服務的存在。 這有點像網芳,當一台新機器上線時,在網路芳臨就會立即出現該電腦的 icon。 在 SSDP 是透過 multicast 一個 NOTIFY 的 request 到網路上。 request 的內容如下
NOTIFY * HTTP/1.1 HOST: 239.255.255.250:1900 CACHE-CONTROL: max-age=120 NT: urn:schemas-wifialliance-org:service:WFAWLANConfig:1 USN: uuid:3b968ed4-7666-11e0-a686-002215d227b8 SERVER: UNIX/unknown UPnP/1.0 dcports/1.0 LOCATION: http://example.net/test NTS: ssdp:alive
基本上就是一個 HTTP request, 只是 command 為 NOTIFY 而非 GET/POST/...。 將這個 request 透過一個 UDP 的 socket,傳送到 239.255.255.250:1900 這個 multicast IP 位址即可。
import socket SSDP_PORT = 1900 SSDP_MCAST_ADDR = '239.255.255.250' msg = 'NOTIFY * HTTP/1.1\r\n....' msock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) msock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) msock.sendto(msg, 0, (SSDP_MCAST_ADDR, SSDP_PORT))
這個例子的 request 是告知其它機器,我有提供 IGD 的 service。 但通常,我們會定義自已的 service,這時你必需修改 NT 和 USN 欄位。 NT 是 service type,你可以定義自已的 service type,如 urn:my-company-domain:service:my-service:1 。 USN 則是一個代表該 service 的 unique number,你可以使用 uuidgen 為你的服務產生一個 USN。 而 LOCATION 欄位則是你的 service 的位址,你可以填入你的 URL 或 IP:port。 需要使用該服務的程式,就會使用連結該位址。
這個 request 必需定時發送,你必需在 max-age 到達之前,重新傳送數次,以確保 其它機器知道你的服務還在線上。
查詢網路上的服務和其位址
這可以透過 miniSSDPd 幫我們查詢。 miniSSDPd 在上線之後,會持續收集 NOTIFY,因此我們只需向它查詢即可。 miniSSDPd 會在 "/var/run/minissdpd.sock" 這個位址上,開啟一個 unix domain 的 socket。 因此,我們只需開一個 socket,連到該位址,就可以向 miniSSDPd 發出查詢要求。 而透過該 socket 傳送的 command 格式請見http://miniupnp.free.fr/minissdpd.html
001 import socket 002 003 def _encode_len(n): 004 r = '' 005 if n >= 268435456: 006 r = r + chr(((n >> 28) & 0x7f) | 0x80) 007 pass 008 if n >= 2097152: 009 r = r + chr(((n >> 21) & 0x7f)| 0x80) 010 pass 011 if n >= 16384: 012 r = r + chr(((n >> 14) & 0x7f)| 0x80) 013 pass 014 if n >= 128: 015 r = r + chr(((n >> 7) & 0x7f)| 0x80) 016 pass 017 r = r + chr(n & 0x7f) 018 return r 019 020 def _decode_len(s): 021 n = 0 022 for c in s: 023 n = (n << 7) | (ord(c) & 0x7f) 024 if not (ord(c) & 0x80): 025 break 026 pass 027 return n 028 029 def _encode_str(s): 030 n = len(s) 031 r = _encode_len(n) + s 032 return r 033 034 def _decode_str(s): 035 n = _decode_len(s) 036 skip = len(_encode_len(n)) 037 r = s[skip: skip + n] 038 039 return r, skip + n 040 041 def query_service(sock, st): 042 q = chr(1) + _encode_str(st) 043 sock.send(q) 044 045 rep = sock.recv(10240) 046 047 n = _decode_len(rep) 048 skip = len(_encode_len(n)) 049 050 result = [] 051 remain = rep[skip:] 052 for i in range(n): 053 location, skip = _decode_str(remain) 054 remain = remain[skip:] 055 056 r_st, skip = _decode_str(remain) 057 remain = remain[skip:] 058 059 r_usn, skip = _decode_str(remain) 060 remain = remain[skip:] 061 062 result.append((location, r_st, r_usn)) 063 pass 064 065 return result 066 067 sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM, 0) 068 sock.connect('/var/run/minissdpd.sock') 069 r = query_service(sock, 070 'urn:schemas-wifialliance-org:service:WFAWLANConfig:1') 071 print r 072
這個範例會連到 miniSSDPd 的 unix domain socket,然後查詢網路上有那些機器提供 urn:schemas-wifialliance-org:service:WFAWLANConfig:1 這個服務。 通常這是 AP 或 NAT 機器等 IP 設備。 最後會傳回一個 list,包括網路上提供該 service 的設備資訊。 相同的,你也可以查詢你自已定義的 service。
結論
miniSSDPd 是一個簡單的 daemon,只有 18Kbytes (i386), 而且 request 的格式很簡單,很容易和應用程式整合。 如果你也需要這種自動發現、通知的功能,不妨試試 miniSSDPd。