Android Says Bonjour

  • Android Says Bonjour

很高興能在農曆蛇年剛開始的這期《程序員》雜誌上繼續爲讀者奉上Android的故事。初來咋到,首先要向你們說聲」你好「。有意思的是,Android也很通人情,從4.1開始,它會說」Bonjour「了。不過它說得是否是原汁原味的法語腔呢?來看下文。 html

一背景知識介紹

Bonjour是法語中的Hello之意。它是Apple公司爲基於組播域名服務(multicast DNS)的開放性零配置網絡標準所起的名字。使用Bonjour的設備在網絡中自動組播它們本身的服務信息並監聽其它設備的服務信息。設備之間就像在打招呼,這也是該技術命名爲Bonjour的緣由。Bonjour使得局域網中的系統和服務即便在沒有網絡管理員的狀況下也很容易被找到。 android

舉一個簡單的例子:在局域網中,若是要進行打印服務,必須先知道打印服務器的IP地址。此IP地址通常由IT部門的人負責分配,而後他還得全員發郵件以公示此地址。有了Bonjour之後,打印服務器本身會依據零配置網絡標準在局域網內部找到一個可用的IP並註冊一個打印服務,名爲「print service」之類的。當客戶端須要打印服務時,會先搜索網絡內部的打印服務器。因爲不知道打印服務器的IP地址,客戶端只能根據諸如"print service"的名字去查找打印機。在Bonjour的幫助下,客戶端最終能找到這臺註冊了「print service」名字的打印機,並得到它的IP地址以及端口號。 程序員

Bonjour角度來看,該技術主要解決了三個問題: 編程

  • Addressing:即爲主機分配IPBonjourAddressing處理比較簡單,即每一個主機在網絡內部的地址可選範圍內找一個IP,而後查看網絡內部是否有其餘主機再用。若是該IP沒有被分配的話,它將使用此IP
  • NamingNaming解決的是host名和IP地址的對應關係。Bonjour採用的是Multiple DNS技術,即DNS查詢消息將經過UDP組播方式發送。一旦網絡內部某個機器發現查詢的機器名和本身設置的同樣,就回復這條請求。此外,Bonjour還拓展了MDNS的用途,即除了能查找host外,還支持對service的查找。不過,BonjourNaming有一個限制,即網絡內部不能有重名的hostservice
  • Service DiscoverySD基於上面的Naming工做,它使得應用程序能查找到網絡內部的服務,並解析該服務對應的IP地址和端口號。應用程序一旦獲得服務的IP地址和端口號,就能夠直接和該服務創建交互關係。

Bonjour技術在Mac OS以及ItunesIphone上都獲得了普遍應用。爲了進一步推廣,Apple經過開源工程mdnsresponder將其開源出來。在Windows平臺上,它將生成一個後臺程序mdnsresponder。在Android平臺上(或者說支持POSIXLinux平臺)它是一個名爲mdnsd的程序。不過,不管是mdnsresponder仍是mdnsd,應用開發者要作的僅僅是利用BonjourAPI向它們發起服務註冊、服務查詢和服務解析等請求並接收來自它們的處理結果。 服務器

下面咱們將介紹Bonjour API中使用最多的三個函數,它們分別是服務註冊、服務查詢和服務解析。理解這三個函數的功能也是理解MDnsSdListener的基礎。 網絡

使用Bonjour API必須包含以下的頭文件和動態庫,並鏈接到: 架構

#include <dns_sd.h>  //必須包含此頭文件 app

libmdnssd.so  //連接到此so less

Bonjour中,服務註冊的APIDNSServiceRegister,原型如圖1所示: dom

1  DNSServiceRegister原型

該函數的解釋以下:

  • sdRef:表明一個未初始化的DNSService實體。其類型DNSServiceRef是指針。該參數最終由DNSServiceRegister函數分配內存並初始化。
  • flags:表示當網絡內部有重名服務時的衝突處理。默認是按順序修改服務名。例如要註冊的服務名爲「printer」,當檢測到重名衝突時,就可更名爲「printer(1)」。
  • interfaceIndex:表示該服務輸出到主機的哪些網絡接口上。值-1表示僅對本機支持,也就是該服務的用在loop接口上。
  • name:表示服務名,爲空的話就取機器名。
  • regtype:服務類型,用字符串表達。Bonjour要求格式爲"_服務名._傳輸協議",例如"_ftp._tcp"。目前傳輸協議僅支持TCPUDP
  • domianhost通常都爲空。
  • port表示該服務的端口。若是爲0的話,Bonjour會自動分配一個。
  • txtLen以及txtRecord字符串用來描述該服務。通常都設置爲空。
  • callBack:設置回調函數。該服註冊的請求結果都會經過它回調給客戶端。
  • context:上下文指針,由應用程序設置。

當客戶端須要搜索網絡內部特定服務時,須要使用DNSServiceBrowser API,其原型如圖2所示:

2  DNSServiceBrowser原型

其中:

  • sdrefinterfaceIndexregtypedomain以及context含義與DNSServiceRegister同樣。
  • flags:在本函數中沒有做用。
  • callBack:爲DNSServiceBrowser處理結果的回調通知接口。

當客戶端想得到指定服務的IP和端口號時,須要使用DNSServiceResolve API,其原型如圖3所示:

3  DNSServiceResolve原型

其中:

  • nameregtypedomain都從DNSServiceBrowse函數的處理結果中得到。
  • callBack用於通知DNSServiceResolve的處理結果。該回調函數將返回服務的IP地址和端口號。

以上介紹的三個APIBonjure的核心API。不過Android中的Bonjour會是怎麼個說法呢?

Android Says Bonjour

幾乎能確定的是,Bonjour想跑在Android平臺上,還須要一番定製。不過這套定製不是針對mdnsd自己,而是針對Bonjour API的使用。Android平臺的Bonjour架構可由圖4表達:

4  Android Bonjour架構

由圖4可知,Android拓展了原有的Bonjour架構,改變以下:

  • Netd中增長了MDnsSdListener對象,它一方面經過socket和上層對象通訊,另外一方面經過Bonjour APImdnsd通訊(也是基於Socket的跨進程通訊)。從mdnsd角度來看,它是最懂Bonjour API的」人「了。
  • System_process進程新增NsdServiceNsdNetwork Service Discovery的縮寫。NsdService經過socket和位於Netd中的MDnsSdListener通訊。
  • App借用NsdManager API經過Binder技術和System_processNsdService通訊。

總之,在Android平臺中,應用程序要藉助其餘三個進程(System_processNetdmdnsd)才能享受到Bonjour好處。不過,這麼繁雜的進程間通訊會不會影響效率呢?

答案是確定的。但就如Bonjour的本意同樣,它僅是經過打一聲招呼以瞭解網絡內服務是否存在以及一些簡單信息。一旦客戶端經過Bonjour獲取到服務的IP地址和端口後,後續客戶端和服務的交互就屬於私密範疇(即客戶端經過服務的IP地址直接和其創建鏈接)了。從這個角度來看,Android上的這點效率損失實屬無傷大雅。

另外,AndroidBonjour架構的設計對讀者們來講還有一個啓示:若是手機廠商想定製一些功能,最好先對現有Android的架構有充分了解。這樣才能結合本身的需求,將功能模塊合理得集成到Android架構中以更有效得發揮其功用。

下面來看看AndroidBonjour架構中的幾位重要成員。

2.1  MDnsSdListener介紹

MDnsSdListenerAndroid Bonjour架構中扮演着轉換器的角色:

  • 一方面它處理來自NsdService的請求,並經過Bonjour API將其轉換成mdnsd能懂的「語言」以驅動其工做。
  • 另外一方面它接收來自mdnsd的信息,並把它們通報給NsdService

5所示爲MDnsSdListener的家族成員示意圖。

5  MDnsSdListener家族成員

由圖5可知:

  • MDnsSdListener的內部類Monitor用於和mdnsd進程通訊,它將調用前面提到的Bonjour API
  • Monitor內部針對每一個DNSService都會創建一個Element對象,該對象經過MonitormHead指針保存在一個list中。
  • HandlerMDnsSdListener註冊的Command

下面將簡單介紹MDnsSdListener的運行過程,其主要工做可分紅三步:

  • Netd建立MDnsSdListener對象,其內部會建立Monitor對象,而Monitor對象將啓動一個線程用於和mdnsd通訊,並接收來自Handler的請求。
  • NsdService啓動完畢後將向MDnsSdListener發送"start-service"命令。
  • NsdService響應應用程序的請求,向MDnsSdListener發送其餘命令,例如"discovery"等。Monitor將最終處理這些請求。

先來看第一步,當MDnsSdListener構造時,會建立一個Monitor對象,代碼如圖6所示:

6  Monitor的構造

由圖6可知:

  • MonitorthreadStart線程將調用其run函數,該函數經過poll方式偵聽包括mCtrlSocketPair在內的socket信息。這部分代碼屬於基本的Linux socket編程。對大部分讀者來講,難度應該不大。
  • NsdService發送"start-service"命令後,HandlerrunCommand將執行MonitorstartService函數。

starService將啓動mdnsd,其所使用的方法頗具Android特點,如圖7所示:

7  startService代碼示意

7中,MDS_SERVICE_NAME宏表明字符串"mdnsd"。瞭解Android的讀者,看完圖7,您能很快知道Android啓動mdnsd的方法嗎?

NsdService發送註冊服務請求時,HandlerserviceRegister函數將被調用,代碼如圖8所示:

8  serviceRegister示意圖

DNSServiceRegister內部將把請求發送給mdnsd去處理,處理的結果經過MDnsSdListenerRegisterCallback返回,該函數最終會經過socket把信息傳遞給NsdService去處理。

MDnsSdListener介紹暫且到此,感興趣的讀者不妨親自看看代碼以加深對Bonjour API用法的理解。

2.2  NsdService介紹

對全部Android App來講,NsdService纔是背後的Boss,其用法(固然,是NsdService客戶端API的封裝類NsdManager的用法)也是在SDK文檔中白紙黑字列出來的。NsdService的內部結構可由圖9表示:

9  NsdService內部結構示意圖

9列出了NsdService中的幾個重要成員,其中:

  • NsdServiceINsdManager.stub派生。這個類也是Android的特點產品,由INsdManager.aidl文件生成。
  • NsdService內部工做將經過NsdStateMachine及內部的三個狀態對象(DefaultStateEnableStateDisableState)驅動。讓筆者頗爲驚訝的是,整個NsdService的代碼只有800來行。並且從理論上說,NSD不存在什麼狀態轉換。狀態機的出現使得代碼理解會相對困難。還好NsdStateMachine只有三個狀態。讀者不妨以它爲契機,瞭解一下AndroidStateMachine的用法。由於它在系統不少地方都被用到。在那些代碼中,狀態就不止三個了。
  • NsdService經過NativeDaemonConnectorNetd中的MDnsSdListener創建socket通訊。
  • NativeCallbackReceiver用來通知NsdService來自Netd的消息。
  • 固然,NsdService費勁心力獲得的最重要的產出物就是NsdServiceInfo了。它就是Network ServiceAndroid Bonjour中的表明。其包含的內容有服務名、服務類型、IP地址和端口號等。

因爲篇幅緣由,本文不擬對NsdService展開詳細討論了。接下來,本文將介紹Android SDK中一個關於Nsd API使用的小例子NsdChat

2.3  NsdChat案例介紹

Android SDK新增了一個NsdChat例子用於向開發者介紹Android平臺中Nsd的使用方法。相關文檔位於http://developer.android.com/training/connect-devices-wirelessly/nsd.html。案例的源碼位於Android4.1源碼根目錄/development/samples/training/NsdChat下。

該例描述了一個簡單的聊天程序,故其命名爲NsdChatNsd在此例中的做用就是註冊並搜索網絡內的聊天服務。因此,在本例中有一個NsdChat進程將經過NsdServiceregisterService函數註冊一個聊天服務。相關代碼如圖10所示:

10  NsdChat註冊聊天服務

由圖10可知:

  • 應用程序要註冊的Nsd服務將經過NsdServiceInfo類來表達。結合前文背景知識,在此NsdServiceInfo中,最重要就是服務的端口號、服務名(根據Bonjour的要求,網絡內部不能有同名服務)以及服務的類型。
  • 接着,應用程序經過NsdManagerregisterService函數註冊此服務。註冊的結果經過NsdManager的內部接口類RegistrationListener來通知。

兩人聊天才有意義,因此另一個運行着NsdChat的客戶端進程將搜索網絡內部的」NsdChat「服務,相關代碼如圖11所示:

11  尋找「NsdChat」服務

由圖11可知:

  • 應用進程只需調用NsdManagerdiscoveryServices函數並傳遞要找的服務類型便可。搜索的結果經過NsdManager的內部接口類DiscoveryListener返回。

注意,Nsd只能根據服務類型進行搜索。當網絡中有多個同屬於一種服務類型(本例中,服務類型是"_http._tcp.")的服務時,應用程序還需根據DiscoveryListener返回的信息進行篩選。這部分代碼如圖12所示:

12  NsdChatDiscoveryListener處理

由圖12可知,discoveryServices的結果經過DiscoveryListener接口類提供的回調函數返回。注意其中onServiceFound函數對同類型服務的篩選處理(值得特別指出的是,Android SDK中並未對此處極易疏忽的地方作任何說明)。

當客戶端成功找到NsdChat服務後,下一步工做就是解析該服務的IP地址和端口號。這是經過NsdManagerresolveService函數(注意圖12中的紅框)來完成的。這個函數的處理結果將經過NsdManager定義的另一個接口類ResolveListener返回。

經過對NsdChat的研究,讀者會發現:

  • 整體而言,NsdManager的使用並不複雜,相關類也比較簡單。
  • 惟一特別之處是其主要API都被設計成異步調用的方式,這將增大應用程序開發的難度。請讀取務必注意這點。

三總結

本文對AndroidBonjour的實現進行了一番介紹。Bonjour的原理知識還請讀者閱讀https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html#//apple_ref/doc/uid/TP40002445-SW1。該網站是關於Bonjure基礎知識的入口,包含《About Bonjour》、《Bonjour API Architecture》等文檔。

另外,Android中的Bonjour主要是爲了支持Network Service Discovery功能。與其相似的還有UPnP技術中使用的Simple Service Discovery ProtocolSSDP)。相比Bonjour而言,UPnP不只實現了NSD,還在後續客戶端和服務端交互方面支持標準SOAP協議,極大方便了客戶端和服務端的代碼邏輯實現。因此,筆者在此提醒開發者,若是想使用Bonjour技術,要特別注意Nsd只能簡化服務註冊及尋找這一步驟,後續還需重點考慮客戶端和服務端交互的協議及實現。

關於DLNA,讀者可參考筆者的博客 http://blog.csdn.net/innost/article/details/7078539
相關文章
相關標籤/搜索