很高兴能在农历蛇年刚开始的这期《程序员》杂志上继续为读者奉上Android的故事。初来咋到,首先要向大家说声”你好“。有意思的是,Android也很通人情,从4.1开始,它会说”Bonjour“了。不过它说得是不是原汁原味的法语腔呢?来看下文。
Bonjour是法语中的Hello之意。它是Apple公司为基于组播域名服务(multicast DNS)的开放性零配置网络标准所起的名字。使用Bonjour的设备在网络中自动组播它们自己的服务信息并监听其它设备的服务信息。设备之间就像在打招呼,这也是该技术命名为Bonjour的原因。Bonjour使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。
举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的IP地址。此IP地址一般由IT部门的人负责分配,然后他还得全员发邮件以公示此地址。有了Bonjour以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的IP并注册一个打印服务,名为“print service”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的IP地址,客户端只能根据诸如"print service"的名字去查找打印机。在Bonjour的帮助下,客户端最终能找到这台注册了“print service”名字的打印机,并获得它的IP地址以及端口号。
从Bonjour角度来看,该技术主要解决了三个问题:
Bonjour技术在Mac OS以及Itunes、Iphone上都得到了广泛应用。为了进一步推广,Apple通过开源工程mdnsresponder将其开源出来。在Windows平台上,它将生成一个后台程序mdnsresponder。在Android平台上(或者说支持POSIX的Linux平台)它是一个名为mdnsd的程序。不过,不论是mdnsresponder还是mdnsd,应用开发者要做的仅仅是利用Bonjour的API向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。
下面我们将介绍Bonjour API中使用最多的三个函数,它们分别是服务注册、服务查询和服务解析。理解这三个函数的功能也是理解MDnsSdListener的基础。
使用Bonjour API必须包含如下的头文件和动态库,并连接到:
#include <dns_sd.h> //必须包含此头文件
libmdnssd.so //链接到此so
Bonjour中,服务注册的API为DNSServiceRegister,原型如图1所示:
图1 DNSServiceRegister原型
该函数的解释如下:
当客户端需要搜索网络内部特定服务时,需要使用DNSServiceBrowser API,其原型如图2所示:
图2 DNSServiceBrowser原型
其中:
当客户端想获得指定服务的IP和端口号时,需要使用DNSServiceResolve API,其原型如图3所示:
图3 DNSServiceResolve原型
其中:
以上介绍的三个API是Bonjure的核心API。不过Android中的Bonjour会是怎么个说法呢?
几乎能肯定的是,Bonjour想跑在Android平台上,还需要一番定制。不过这套定制不是针对mdnsd本身,而是针对Bonjour API的使用。Android平台的Bonjour架构可由图4表达:
图4 Android Bonjour架构
由图4可知,Android拓展了原有的Bonjour架构,改变如下:
总之,在Android平台中,应用程序要借助其他三个进程(System_process、Netd、mdnsd)才能享受到Bonjour好处。不过,这么繁杂的进程间通信会不会影响效率呢?
答案是肯定的。但就如Bonjour的本意一样,它仅是通过打一声招呼以了解网络内服务是否存在以及一些简单信息。一旦客户端通过Bonjour获取到服务的IP地址和端口后,后续客户端和服务的交互就属于私密范畴(即客户端通过服务的IP地址直接和其建立连接)了。从这个角度来看,Android上的这点效率损失实属无伤大雅。
另外,Android上Bonjour架构的设计对读者们来说还有一个启示:如果手机厂商想定制一些功能,最好先对现有Android的架构有充分了解。这样才能结合自己的需求,将功能模块合理得集成到Android架构中以更有效得发挥其功用。
下面来看看Android中Bonjour架构中的几位重要成员。
MDnsSdListener在Android Bonjour架构中扮演着转换器的角色:
图5所示为MDnsSdListener的家族成员示意图。
图5 MDnsSdListener家族成员
由图5可知:
下面将简单介绍MDnsSdListener的运行过程,其主要工作可分成三步:
先来看第一步,当MDnsSdListener构造时,会创建一个Monitor对象,代码如图6所示:
图6 Monitor的构造
由图6可知:
starService将启动mdnsd,其所使用的方法颇具Android特色,如图7所示:
图7 startService代码示意
图7中,MDS_SERVICE_NAME宏代表字符串"mdnsd"。了解Android的读者,看完图7,您能很快知道Android启动mdnsd的方法吗?
当NsdService发送注册服务请求时,Handler的serviceRegister函数将被调用,代码如图8所示:
图8 serviceRegister示意图
DNSServiceRegister内部将把请求发送给mdnsd去处理,处理的结果通过MDnsSdListenerRegisterCallback返回,该函数最终会通过socket把信息传递给NsdService去处理。
MDnsSdListener介绍暂且到此,感兴趣的读者不妨亲自看看代码以加深对Bonjour API用法的理解。
对所有Android App来说,NsdService才是背后的Boss,其用法(当然,是NsdService客户端API的封装类NsdManager的用法)也是在SDK文档中白纸黑字列出来的。NsdService的内部结构可由图9表示:
图9 NsdService内部结构示意图
图9列出了NsdService中的几个重要成员,其中:
由于篇幅原因,本文不拟对NsdService展开详细讨论了。接下来,本文将介绍Android SDK中一个关于Nsd API使用的小例子NsdChat。
Android SDK新增了一个NsdChat例子用于向开发者介绍Android平台中Nsd的使用方法。相关文档位于http://developer.android.com/training/connect-devices-wirelessly/nsd.html。案例的源码位于Android4.1源码根目录/development/samples/training/NsdChat下。
该例描述了一个简单的聊天程序,故其命名为NsdChat。Nsd在此例中的作用就是注册并搜索网络内的聊天服务。所以,在本例中有一个NsdChat进程将通过NsdService的registerService函数注册一个聊天服务。相关代码如图10所示:
图10 NsdChat注册聊天服务
由图10可知:
两人聊天才有意义,所以另外一个运行着NsdChat的客户端进程将搜索网络内部的”NsdChat“服务,相关代码如图11所示:
图11 寻找“NsdChat”服务
由图11可知:
注意,Nsd只能根据服务类型进行搜索。当网络中有多个同属于一种服务类型(本例中,服务类型是"_http._tcp.")的服务时,应用程序还需根据DiscoveryListener返回的信息进行筛选。这部分代码如图12所示:
图12 NsdChat的DiscoveryListener处理
由图12可知,discoveryServices的结果通过DiscoveryListener接口类提供的回调函数返回。注意其中onServiceFound函数对同类型服务的筛选处理(值得特别指出的是,Android SDK中并未对此处极易疏忽的地方做任何说明)。
当客户端成功找到NsdChat服务后,下一步工作就是解析该服务的IP地址和端口号。这是通过NsdManager的resolveService函数(注意图12中的红框)来完成的。这个函数的处理结果将通过NsdManager定义的另外一个接口类ResolveListener返回。
通过对NsdChat的研究,读者会发现:
本文对Android中Bonjour的实现进行了一番介绍。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 Protocol(SSDP)。相比Bonjour而言,UPnP不仅实现了NSD,还在后续客户端和服务端交互方面支持标准SOAP协议,极大方便了客户端和服务端的代码逻辑实现。所以,笔者在此提醒开发者,如果想使用Bonjour技术,要特别注意Nsd只能简化服务注册及寻找这一步骤,后续还需重点考虑客户端和服务端交互的协议及实现。
关于DLNA,读者可参考笔者的博客 http://blog.csdn.net/innost/article/details/7078539 。