Android Says Bonjour

                版权声明:From 阿拉神农                    https://blog.csdn.net/Innost/article/details/8629139                </div>
                      <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
                          <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-cd6c485e8b.css">
      <div class="htmledit_views" id="content_views">
        <ul><li><h1 align="center"><a name="t0"></a><span style="font-family:'Times New Roman';font-size:24px;">Android Says Bonjour</span></h1></li></ul><p><span style="font-size:14px;">很高兴能在农历蛇年刚开始的这期《程序员》杂志上继续为读者奉上<span style="font-family:'Times New Roman';">Android</span>的故事。初来咋到,首先要向大家说声”你好“。有意思的是,<span style="font-family:'Times New Roman';">Android</span>也很通人情,从<span style="font-family:'Times New Roman';">4.1</span>开始,它会说”<span style="font-family:'Times New Roman';">Bonjour</span>“了。不过它说得是不是原汁原味的法语腔呢?来看下文。</span></p><h2><a name="t1"></a>一背景知识介绍</h2><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Bonjour</span>是法语中的<span style="font-family:'Times New Roman';">Hello</span>之意。它是<span style="font-family:'Times New Roman';">Apple</span>公司为基于组播域名服务<span style="font-family:'Times New Roman';">(multicast DNS)</span>的开放性零配置网络标准所起的名字。使用<span style="font-family:'Times New Roman';">Bonjour</span>的设备在网络中自动组播它们自己的服务信息并监听其它设备的服务信息。设备之间就像在打招呼,这也是该技术命名为<span style="font-family:'Times New Roman';">Bonjour</span>的原因。<span style="font-family:'Times New Roman';">Bonjour</span>使得局域网中的系统和服务即使在没有网络管理员的情况下也很容易被找到。</span></p><p><span style="font-size:14px;">举一个简单的例子:在局域网中,如果要进行打印服务,必须先知道打印服务器的<span style="font-family:'Times New Roman';">IP</span>地址。此<span style="font-family:'Times New Roman';">IP</span>地址一般由<span style="font-family:'Times New Roman';">IT</span>部门的人负责分配,然后他还得全员发邮件以公示此地址。有了<span style="font-family:'Times New Roman';">Bonjour</span>以后,打印服务器自己会依据零配置网络标准在局域网内部找到一个可用的<span style="font-family:'Times New Roman';">IP</span>并注册一个打印服务,名为“<span style="font-family:'Times New Roman';">print service</span>”之类的。当客户端需要打印服务时,会先搜索网络内部的打印服务器。由于不知道打印服务器的<span style="font-family:'Times New Roman';">IP</span>地址,客户端只能根据诸如<span style="font-family:'Times New Roman';">"print service"</span>的名字去查找打印机。在<span style="font-family:'Times New Roman';">Bonjour</span>的帮助下,客户端最终能找到这台注册了“<span style="font-family:'Times New Roman';">print service</span>”名字的打印机,并获得它的<span style="font-family:'Times New Roman';">IP</span>地址以及端口号。</span></p><p><span style="font-size:14px;">从<span style="font-family:'Times New Roman';">Bonjour</span>角度来看,该技术主要解决了三个问题:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Addressing</span>:即为主机分配<span style="font-family:'Times New Roman';">IP</span>。<span style="font-family:'Times New Roman';">Bonjour</span>的<span style="font-family:'Times New Roman';">Addressing</span>处理比较简单,即每个主机在网络内部的地址可选范围内找一个<span style="font-family:'Times New Roman';">IP</span>,然后查看网络内部是否有其他主机再用。如果该<span style="font-family:'Times New Roman';">IP</span>没有被分配的话,它将使用此<span style="font-family:'Times New Roman';">IP</span>。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Naming</span>:<span style="font-family:'Times New Roman';">Naming</span>解决的是<span style="font-family:'Times New Roman';">host</span>名和<span style="font-family:'Times New Roman';">IP</span>地址的对应关系。<span style="font-family:'Times New Roman';">Bonjour</span>采用的是<span style="font-family:'Times New Roman';">Multiple DNS</span>技术,即<span style="font-family:'Times New Roman';">DNS</span>查询消息将通过<span style="font-family:'Times New Roman';">UDP</span>组播方式发送。一旦网络内部某个机器发现查询的机器名和自己设置的一样,就回复这条请求。此外,<span style="font-family:'Times New Roman';">Bonjour</span>还拓展了<span style="font-family:'Times New Roman';">MDNS</span>的用途,即除了能查找<span style="font-family:'Times New Roman';">host</span>外,还支持对<span style="font-family:'Times New Roman';">service</span>的查找。不过,<span style="font-family:'Times New Roman';">Bonjour</span>的<span style="font-family:'Times New Roman';">Naming</span>有一个限制,即网络内部不能有重名的<span style="font-family:'Times New Roman';">host</span>或<span style="font-family:'Times New Roman';">service</span>。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Service Discovery</span>:<span style="font-family:'Times New Roman';">SD</span>基于上面的<span style="font-family:'Times New Roman';">Naming</span>工作,它使得应用程序能查找到网络内部的服务,并解析该服务对应的<span style="font-family:'Times New Roman';">IP</span>地址和端口号。应用程序一旦得到服务的<span style="font-family:'Times New Roman';">IP</span>地址和端口号,就可以直接和该服务建立交互关系。</span></li></ul><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Bonjour</span>技术在<span style="font-family:'Times New Roman';">Mac OS</span>以及<span style="font-family:'Times New Roman';">Itunes</span>、<span style="font-family:'Times New Roman';">Iphone</span>上都得到了广泛应用。为了进一步推广,<span style="font-family:'Times New Roman';">Apple</span>通过开源工程<span style="font-family:'Times New Roman';">mdnsresponder</span>将其开源出来。在<span style="font-family:'Times New Roman';">Windows</span>平台上,它将生成一个后台程序<span style="font-family:'Times New Roman';">mdnsresponder</span>。在<span style="font-family:'Times New Roman';">Android</span>平台上(或者说支持<span style="font-family:'Times New Roman';">POSIX</span>的<span style="font-family:'Times New Roman';">Linux</span>平台)它是一个名为<span style="font-family:'Times New Roman';">mdnsd</span>的程序。不过,不论是<span style="font-family:'Times New Roman';">mdnsresponder</span>还是<span style="font-family:'Times New Roman';">mdnsd</span>,应用开发者要做的仅仅是利用<span style="font-family:'Times New Roman';">Bonjour</span>的<span style="font-family:'Times New Roman';">API</span>向它们发起服务注册、服务查询和服务解析等请求并接收来自它们的处理结果。</span></p><p><span style="font-size:14px;">下面我们将介绍<span style="font-family:'Times New Roman';">Bonjour API</span>中使用最多的三个函数,它们分别是服务注册、服务查询和服务解析。理解这三个函数的功能也是理解<span style="font-family:'Times New Roman';">MDnsSdListener</span>的基础。</span></p><p><span style="font-size:14px;">使用<span style="font-family:'Times New Roman';">Bonjour API</span>必须包含如下的头文件和动态库,并连接到:</span></p><div style="background:#f0f7fe;"><p><span style="font-size:14px;color:#333333;">#include &lt;<strong>dns_sd.h</strong>&gt;&nbsp; //必须包含此头文件</span></p><p><span style="color:#333333;"><span style="font-size:14px;"><strong>libmdnssd.so</strong>&nbsp; //链接到此so</span></span></p></div><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Bonjour</span>中,服务注册的<span style="font-family:'Times New Roman';">API</span>为<span style="font-family:'Times New Roman';">DNSServiceRegister</span>,原型如图<span style="font-family:'Times New Roman';">1</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212320_6806.png" width="577" height="203"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">1&nbsp; DNSServiceRegister</span>原型</span></p><p><span style="font-size:14px;">该函数的解释如下:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">sdRef</span>:代表一个未初始化的<span style="font-family:'Times New Roman';">DNSService</span>实体。其类型<span style="font-family:'Times New Roman';">DNSServiceRef</span>是指针。该参数最终由<span style="font-family:'Times New Roman';">DNSServiceRegister</span>函数分配内存并初始化。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">flags</span>:表示当网络内部有重名服务时的冲突处理。默认是按顺序修改服务名。例如要注册的服务名为“<span style="font-family:'Times New Roman';">printer</span>”,当检测到重名冲突时,就可改名为“<span style="font-family:'Times New Roman';">printer(1)</span>”。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">interfaceIndex</span>:表示该服务输出到主机的哪些网络接口上。值<span style="font-family:'Times New Roman';">-1</span>表示仅对本机支持,也就是该服务的用在<span style="font-family:'Times New Roman';">loop</span>接口上。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">name</span>:表示服务名,为空的话就取机器名。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">regtype</span>:服务类型,用字符串表达。<span style="font-family:'Times New Roman';">Bonjour</span>要求格式为<span style="font-family:'Times New Roman';">"_</span>服务名<span style="font-family:'Times New Roman';">._</span>传输协议<span style="font-family:'Times New Roman';">"</span>,例如<span style="font-family:'Times New Roman';">"_ftp._tcp"</span>。目前传输协议仅支持<span style="font-family:'Times New Roman';">TCP</span>和<span style="font-family:'Times New Roman';">UDP</span>。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">domian</span>和<span style="font-family:'Times New Roman';">host</span>一般都为空。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">port</span>表示该服务的端口。如果为<span style="font-family:'Times New Roman';">0</span>的话,<span style="font-family:'Times New Roman';">Bonjour</span>会自动分配一个。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">txtLen</span>以及<span style="font-family:'Times New Roman';">txtRecord</span>字符串用来描述该服务。一般都设置为空。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">callBack</span>:设置回调函数。该服注册的请求结果都会通过它回调给客户端。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">context</span>:上下文指针,由应用程序设置。</span></li></ul><p><span style="font-size:14px;">当客户端需要搜索网络内部特定服务时,需要使用<span style="font-family:'Times New Roman';">DNSServiceBrowser API</span>,其原型如图<span style="font-family:'Times New Roman';">2</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212387_7547.png" width="567" height="151"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">2&nbsp; DNSServiceBrowser</span>原型</span></p><p><span style="font-size:14px;">其中:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">sdref</span>、<span style="font-family:'Times New Roman';">interfaceIndex</span>、<span style="font-family:'Times New Roman';">regtype</span>、<span style="font-family:'Times New Roman';">domain</span>以及<span style="font-family:'Times New Roman';">context</span>含义与<span style="font-family:'Times New Roman';">DNSServiceRegister</span>一样。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">flags</span>:在本函数中没有作用。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">callBack</span>:为<span style="font-family:'Times New Roman';">DNSServiceBrowser</span>处理结果的回调通知接口。</span></li></ul><p><span style="font-size:14px;">当客户端想获得指定服务的<span style="font-family:'Times New Roman';">IP</span>和端口号时,需要使用<span style="font-family:'Times New Roman';">DNSServiceResolve API</span>,其原型如图<span style="font-family:'Times New Roman';">3</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212427_1360.png" width="595" height="163"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">3&nbsp; DNSServiceResolve</span>原型</span></p><p><span style="font-size:14px;">其中:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">name</span>、<span style="font-family:'Times New Roman';">regtype</span>和<span style="font-family:'Times New Roman';">domain</span>都从<span style="font-family:'Times New Roman';">DNSServiceBrowse</span>函数的处理结果中获得。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">callBack</span>用于通知<span style="font-family:'Times New Roman';">DNSServiceResolve</span>的处理结果。该回调函数将返回服务的<span style="font-family:'Times New Roman';">IP</span>地址和端口号。</span></li></ul><p><span style="font-size:14px;">以上介绍的三个<span style="font-family:'Times New Roman';">API</span>是<span style="font-family:'Times New Roman';">Bonjure</span>的核心<span style="font-family:'Times New Roman';">API</span>。不过<span style="font-family:'Times New Roman';">Android</span>中的<span style="font-family:'Times New Roman';">Bonjour</span>会是怎么个说法呢?</span></p><h2><a name="t2"></a>二<span style="font-family:'Times New Roman';">Android Says Bonjour</span></h2><p><span style="font-size:14px;">几乎能肯定的是,<span style="font-family:'Times New Roman';">Bonjour</span>想跑在<span style="font-family:'Times New Roman';">Android</span>平台上,还需要一番定制。不过这套定制不是针对<span style="font-family:'Times New Roman';">mdnsd</span>本身,而是针对<span style="font-family:'Times New Roman';">Bonjour API</span>的使用。<span style="font-family:'Times New Roman';">Android</span>平台的<span style="font-family:'Times New Roman';">Bonjour</span>架构可由图<span style="font-family:'Times New Roman';">4</span>表达:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212473_5408.png" width="401" height="182"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">4&nbsp; Android Bonjour</span>架构</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">4</span>可知,<span style="font-family:'Times New Roman';">Android</span>拓展了原有的<span style="font-family:'Times New Roman';">Bonjour</span>架构,改变如下:</span></p><ul><li><span style="font-size:14px;">在<span style="font-family:'Times New Roman';">Netd</span>中增加了<span style="font-family:'Times New Roman';">MDnsSdListener</span>对象,它一方面通过<span style="font-family:'Times New Roman';">socket</span>和上层对象通信,另一方面通过<span style="font-family:'Times New Roman';">Bonjour API</span>和<span style="font-family:'Times New Roman';">mdnsd</span>通信(也是基于<span style="font-family:'Times New Roman';">Socket</span>的跨进程通信)。从<span style="font-family:'Times New Roman';">mdnsd</span>角度来看,它是最懂<span style="font-family:'Times New Roman';">Bonjour API</span>的”人“了。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">System_process</span>进程新增<span style="font-family:'Times New Roman';">NsdService</span>。<span style="font-family:'Times New Roman';">Nsd</span>是<span style="font-family:'Times New Roman';">Network Service Discovery</span>的缩写。<span style="font-family:'Times New Roman';">NsdService</span>通过<span style="font-family:'Times New Roman';">socket</span>和位于<span style="font-family:'Times New Roman';">Netd</span>中的<span style="font-family:'Times New Roman';">MDnsSdListener</span>通信。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">App</span>借用<span style="font-family:'Times New Roman';">NsdManager API</span>通过<span style="font-family:'Times New Roman';">Binder</span>技术和<span style="font-family:'Times New Roman';">System_process</span>的<span style="font-family:'Times New Roman';">NsdService</span>通信。</span></li></ul><p><span style="font-size:14px;">总之,在<span style="font-family:'Times New Roman';">Android</span>平台中,应用程序要借助其他三个进程(<span style="font-family:'Times New Roman';">System_process</span>、<span style="font-family:'Times New Roman';">Netd</span>、<span style="font-family:'Times New Roman';">mdnsd</span>)才能享受到<span style="font-family:'Times New Roman';">Bonjour</span>好处。不过,这么繁杂的进程间通信会不会影响效率呢?</span></p><p><span style="font-size:14px;">答案是肯定的。但就如<span style="font-family:'Times New Roman';">Bonjour</span>的本意一样,它仅是通过打一声招呼以了解网络内服务是否存在以及一些简单信息。一旦客户端通过<span style="font-family:'Times New Roman';">Bonjour</span>获取到服务的<span style="font-family:'Times New Roman';">IP</span>地址和端口后,后续客户端和服务的交互就属于私密范畴(即客户端通过服务的<span style="font-family:'Times New Roman';">IP</span>地址直接和其建立连接)了。从这个角度来看,<span style="font-family:'Times New Roman';">Android</span>上的这点效率损失实属无伤大雅。</span></p><p><span style="font-size:14px;">另外,<span style="font-family:'Times New Roman';">Android</span>上<span style="font-family:'Times New Roman';">Bonjour</span>架构的设计对读者们来说还有一个启示:如果手机厂商想定制一些功能,最好先对现有<span style="font-family:'Times New Roman';">Android</span>的架构有充分了解。这样才能结合自己的需求,将功能模块合理得集成到<span style="font-family:'Times New Roman';">Android</span>架构中以更有效得发挥其功用。</span></p><p><span style="font-size:14px;">下面来看看<span style="font-family:'Times New Roman';">Android</span>中<span style="font-family:'Times New Roman';">Bonjour</span>架构中的几位重要成员。</span></p><h3><a name="t3"></a><span style="font-family:'Times New Roman';font-size:24px;">2.1&nbsp; MDnsSdListener</span>介绍</h3><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">MDnsSdListener</span>在<span style="font-family:'Times New Roman';">Android Bonjour</span>架构中扮演着转换器的角色:</span></p><ul><li><span style="font-size:14px;">一方面它处理来自<span style="font-family:'Times New Roman';">NsdService</span>的请求,并通过<span style="font-family:'Times New Roman';">Bonjour API</span>将其转换成<span style="font-family:'Times New Roman';">mdnsd</span>能懂的“语言”以驱动其工作。</span></li><li><span style="font-size:14px;">另一方面它接收来自<span style="font-family:'Times New Roman';">mdnsd</span>的信息,并把它们通报给<span style="font-family:'Times New Roman';">NsdService</span>。</span></li></ul><p><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">5</span>所示为<span style="font-family:'Times New Roman';">MDnsSdListener</span>的家族成员示意图。</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212577_5911.png" width="369" height="260"></p><span style="font-size:14px;"></span><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">5&nbsp; MDnsSdListener</span>家族成员</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">5</span>可知:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">MDnsSdListener</span>的内部类<span style="font-family:'Times New Roman';">Monitor</span>用于和<span style="font-family:'Times New Roman';">mdnsd</span>进程通信,它将调用前面提到的<span style="font-family:'Times New Roman';">Bonjour API</span>。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Monitor</span>内部针对每个<span style="font-family:'Times New Roman';">DNSService</span>都会建立一个<span style="font-family:'Times New Roman';">Element</span>对象,该对象通过<span style="font-family:'Times New Roman';">Monitor</span>的<span style="font-family:'Times New Roman';">mHead</span>指针保存在一个<span style="font-family:'Times New Roman';">list</span>中。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Handler</span>是<span style="font-family:'Times New Roman';">MDnsSdListener</span>注册的<span style="font-family:'Times New Roman';">Command</span>。</span></li></ul><p><span style="font-size:14px;">下面将简单介绍<span style="font-family:'Times New Roman';">MDnsSdListener</span>的运行过程,其主要工作可分成三步:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Netd</span>创建<span style="font-family:'Times New Roman';">MDnsSdListener</span>对象,其内部会创建<span style="font-family:'Times New Roman';">Monitor</span>对象,而<span style="font-family:'Times New Roman';">Monitor</span>对象将启动一个线程用于和<span style="font-family:'Times New Roman';">mdnsd</span>通信,并接收来自<span style="font-family:'Times New Roman';">Handler</span>的请求。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">NsdService</span>启动完毕后将向<span style="font-family:'Times New Roman';">MDnsSdListener</span>发送<span style="font-family:'Times New Roman';">"start-service"</span>命令。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">NsdService</span>响应应用程序的请求,向<span style="font-family:'Times New Roman';">MDnsSdListener</span>发送其他命令,例如<span style="font-family:'Times New Roman';">"discovery"</span>等。<span style="font-family:'Times New Roman';">Monitor</span>将最终处理这些请求。</span></li></ul><p><span style="font-size:14px;">先来看第一步,当<span style="font-family:'Times New Roman';">MDnsSdListener</span>构造时,会创建一个<span style="font-family:'Times New Roman';">Monitor</span>对象,代码如图<span style="font-family:'Times New Roman';">6</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212630_7643.png" width="551" height="91"></p><span style="font-size:14px;"></span><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">6&nbsp; Monitor</span>的构造</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">6</span>可知:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Monitor</span>的<span style="font-family:'Times New Roman';">threadStart</span>线程将调用其<span style="font-family:'Times New Roman';">run</span>函数,该函数通过<span style="font-family:'Times New Roman';">poll</span>方式侦听包括<span style="font-family:'Times New Roman';">mCtrlSocketPair</span>在内的<span style="font-family:'Times New Roman';">socket</span>信息。这部分代码属于基本的<span style="font-family:'Times New Roman';">Linux socket</span>编程。对大部分读者来说,难度应该不大。</span></li><li><span style="font-size:14px;">当<span style="font-family:'Times New Roman';">NsdService</span>发送<span style="font-family:'Times New Roman';">"start-service"</span>命令后,<span style="font-family:'Times New Roman';">Handler</span>的<span style="font-family:'Times New Roman';">runCommand</span>将执行<span style="font-family:'Times New Roman';">Monitor</span>的<span style="font-family:'Times New Roman';">startService</span>函数。</span></li></ul><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">starService</span>将启动<span style="font-family:'Times New Roman';">mdnsd</span>,其所使用的方法颇具<span style="font-family:'Times New Roman';">Android</span>特色,如图<span style="font-family:'Times New Roman';">7</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212670_7775.png" width="453" height="196"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">7&nbsp; startService</span>代码示意</span></p><p><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">7</span>中,<span style="font-family:'Times New Roman';">MDS_SERVICE_NAME</span>宏代表字符串<span style="font-family:'Times New Roman';">"mdnsd"</span>。了解<span style="font-family:'Times New Roman';">Android</span>的读者,看完图<span style="font-family:'Times New Roman';">7</span>,您能很快知道<span style="font-family:'Times New Roman';">Android</span>启动<span style="font-family:'Times New Roman';">mdnsd</span>的方法吗?</span></p><p><span style="font-size:14px;">当<span style="font-family:'Times New Roman';">NsdService</span>发送注册服务请求时,<span style="font-family:'Times New Roman';">Handler</span>的<span style="font-family:'Times New Roman';">serviceRegister</span>函数将被调用,代码如图<span style="font-family:'Times New Roman';">8</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212759_5668.png" width="619" height="207"></p><span style="font-size:14px;"></span><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">8&nbsp; serviceRegister</span>示意图</span></p><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">DNSServiceRegister</span>内部将把请求发送给<span style="font-family:'Times New Roman';">mdnsd</span>去处理,处理的结果通过<span style="font-family:'Times New Roman';">MDnsSdListenerRegisterCallback</span>返回,该函数最终会通过<span style="font-family:'Times New Roman';">socket</span>把信息传递给<span style="font-family:'Times New Roman';">NsdService</span>去处理。</span></p><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">MDnsSdListener</span>介绍暂且到此,感兴趣的读者不妨亲自看看代码以加深对<span style="font-family:'Times New Roman';">Bonjour API</span>用法的理解。</span></p><h3><a name="t4"></a><span style="font-family:'Times New Roman';font-size:24px;">2.2&nbsp; NsdService</span>介绍</h3><p><span style="font-size:14px;">对所有<span style="font-family:'Times New Roman';">Android App</span>来说,<span style="font-family:'Times New Roman';">NsdService</span>才是背后的<span style="font-family:'Times New Roman';">Boss</span>,其用法(当然,是<span style="font-family:'Times New Roman';">NsdService</span>客户端<span style="font-family:'Times New Roman';">API</span>的封装类<span style="font-family:'Times New Roman';">NsdManager</span>的用法)也是在<span style="font-family:'Times New Roman';">SDK</span>文档中白纸黑字列出来的。<span style="font-family:'Times New Roman';">NsdService</span>的内部结构可由图<span style="font-family:'Times New Roman';">9</span>表示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212785_4858.png" width="487" height="275"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">9&nbsp; NsdService</span>内部结构示意图</span></p><p><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">9</span>列出了<span style="font-family:'Times New Roman';">NsdService</span>中的几个重要成员,其中:</span></p><ul><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">NsdService</span>从<span style="font-family:'Times New Roman';">INsdManager.stub</span>派生。这个类也是<span style="font-family:'Times New Roman';">Android</span>的特色产品,由<span style="font-family:'Times New Roman';">INsdManager.aidl</span>文件生成。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">NsdService</span>内部工作将通过<span style="font-family:'Times New Roman';">NsdStateMachine</span>及内部的三个状态对象(<span style="font-family:'Times New Roman';">DefaultState</span>、<span style="font-family:'Times New Roman';">EnableState</span>、<span style="font-family:'Times New Roman';">DisableState</span>)驱动。让笔者颇为惊讶的是,整个<span style="font-family:'Times New Roman';">NsdService</span>的代码只有<span style="font-family:'Times New Roman';">800</span>来行。而且从理论上说,<span style="font-family:'Times New Roman';">NSD</span>不存在什么状态转换。状态机的出现使得代码理解会相对困难。还好<span style="font-family:'Times New Roman';">NsdStateMachine</span>只有三个状态。读者不妨以它为契机,了解一下<span style="font-family:'Times New Roman';">Android</span>中<span style="font-family:'Times New Roman';">StateMachine</span>的用法。因为它在系统很多地方都被用到。在那些代码中,状态就不止三个了。</span></li><li><span style="font-size:14px;"><span style="font-family:'Times New Roman';">NsdService</span>通过<span style="font-family:'Times New Roman';">NativeDaemonConnector</span>和<span style="font-family:'Times New Roman';">Netd</span>中的<span style="font-family:'Times New Roman';">MDnsSdListener</span>建立<span style="font-family:'Times New Roman';">socket</span>通信。</span></li><li><span style="font-size:14px;">类<span style="font-family:'Times New Roman';">NativeCallbackReceiver</span>用来通知<span style="font-family:'Times New Roman';">NsdService</span>来自<span style="font-family:'Times New Roman';">Netd</span>的消息。</span></li><li><span style="font-size:14px;">当然,<span style="font-family:'Times New Roman';">NsdService</span>费劲心力得到的最重要的产出物就是<span style="font-family:'Times New Roman';">NsdServiceInfo</span>了。它就是<span style="font-family:'Times New Roman';">Network Service</span>在<span style="font-family:'Times New Roman';">Android Bonjour</span>中的代表。其包含的内容有服务名、服务类型、<span style="font-family:'Times New Roman';">IP</span>地址和端口号等。</span></li></ul><p><span style="font-size:14px;">由于篇幅原因,本文不拟对<span style="font-family:'Times New Roman';">NsdService</span>展开详细讨论了。接下来,本文将介绍<span style="font-family:'Times New Roman';">Android SDK</span>中一个关于<span style="font-family:'Times New Roman';">Nsd API</span>使用的小例子<span style="font-family:'Times New Roman';">NsdChat</span>。</span></p><h3><a name="t5"></a><span style="font-family:'Times New Roman';font-size:24px;">2.3&nbsp; NsdChat</span>案例介绍</h3><p><span style="font-size:14px;"><span style="font-family:'Times New Roman';">Android SDK</span>新增了一个<span style="font-family:'Times New Roman';">NsdChat</span>例子用于向开发者介绍<span style="font-family:'Times New Roman';">Android</span>平台中<span style="font-family:'Times New Roman';">Nsd</span>的使用方法。相关文档位于</span><a href="http://developer.android.com/training/connect-devices-wirelessly/nsd.html" rel="nofollow" target="_blank"><span style="font-family:'Times New Roman';font-size:14px;">http://developer.android.com/training/connect-devices-wirelessly/nsd.html</span></a><span style="font-size:14px;">。案例的源码位于<span style="font-family:'Times New Roman';">Android4.1</span>源码根目录<span style="font-family:'Times New Roman';">/development/samples/training/NsdChat</span>下。</span></p><p><span style="font-size:14px;">该例描述了一个简单的聊天程序,故其命名为<span style="font-family:'Times New Roman';">NsdChat</span>。<span style="font-family:'Times New Roman';">Nsd</span>在此例中的作用就是注册并搜索网络内的聊天服务。所以,在本例中有一个<span style="font-family:'Times New Roman';">NsdChat</span>进程将通过<span style="font-family:'Times New Roman';">NsdService</span>的<span style="font-family:'Times New Roman';">registerService</span>函数注册一个聊天服务。相关代码如图<span style="font-family:'Times New Roman';">10</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212841_6305.png" width="567" height="159"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">10&nbsp; NsdChat</span>注册聊天服务</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">10</span>可知:</span></p><ul><li><span style="font-size:14px;">应用程序要注册的<span style="font-family:'Times New Roman';">Nsd</span>服务将通过<span style="font-family:'Times New Roman';">NsdServiceInfo</span>类来表达。结合前文背景知识,在此<span style="font-family:'Times New Roman';">NsdServiceInfo</span>中,最重要就是服务的端口号、服务名(根据<span style="font-family:'Times New Roman';">Bonjour</span>的要求,网络内部不能有同名服务)以及服务的类型。</span></li><li><span style="font-size:14px;">接着,应用程序通过<span style="font-family:'Times New Roman';">NsdManager</span>的<span style="font-family:'Times New Roman';">registerService</span>函数注册此服务。注册的结果通过<span style="font-family:'Times New Roman';">NsdManager</span>的内部接口类<span style="font-family:'Times New Roman';">RegistrationListener</span>来通知。</span></li></ul><p><span style="font-size:14px;">两人聊天才有意义,所以另外一个运行着<span style="font-family:'Times New Roman';">NsdChat</span>的客户端进程将搜索网络内部的”<span style="font-family:'Times New Roman';">NsdChat</span>“服务,相关代码如图<span style="font-family:'Times New Roman';">11</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212883_8347.png" width="589" height="88"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">11&nbsp; </span>寻找“<span style="font-family:'Times New Roman';">NsdChat</span>”服务</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">11</span>可知:</span></p><ul><li><span style="font-size:14px;">应用进程只需调用<span style="font-family:'Times New Roman';">NsdManager</span>的<span style="font-family:'Times New Roman';">discoveryServices</span>函数并传递要找的服务类型即可。搜索的结果通过<span style="font-family:'Times New Roman';">NsdManager</span>的内部接口类<span style="font-family:'Times New Roman';">DiscoveryListener</span>返回。</span></li></ul><p><span style="font-size:14px;">注意,<span style="font-family:'Times New Roman';">Nsd</span>只能根据服务类型进行搜索。当网络中有多个同属于一种服务类型(本例中,服务类型是<span style="font-family:'Times New Roman';">"_http._tcp."</span>)的服务时,应用程序还需根据<span style="font-family:'Times New Roman';">DiscoveryListener</span>返回的信息进行筛选。这部分代码如图<span style="font-family:'Times New Roman';">12</span>所示:</span></p><p align="center"><img alt="" src="https://img-my.csdn.net/uploads/201303/02/1362212911_4121.png" width="497" height="209"></p><p align="center"><span style="font-size:14px;">图<span style="font-family:'Times New Roman';">12&nbsp; NsdChat</span>的<span style="font-family:'Times New Roman';">DiscoveryListener</span>处理</span></p><p><span style="font-size:14px;">由图<span style="font-family:'Times New Roman';">12</span>可知,<span style="font-family:'Times New Roman';">discoveryServices</span>的结果通过<span style="font-family:'Times New Roman';">DiscoveryListener</span>接口类提供的回调函数返回。注意其中<span style="font-family:'Times New Roman';">onServiceFound</span>函数对同类型服务的筛选处理(值得特别指出的是,<span style="font-family:'Times New Roman';">Android SDK</span>中并未对此处极易疏忽的地方做任何说明)。</span></p><p><span style="font-size:14px;">当客户端成功找到<span style="font-family:'Times New Roman';">NsdChat</span>服务后,下一步工作就是解析该服务的<span style="font-family:'Times New Roman';">IP</span>地址和端口号。这是通过<span style="font-family:'Times New Roman';">NsdManager</span>的<span style="font-family:'Times New Roman';">resolveService</span>函数(注意图<span style="font-family:'Times New Roman';">12</span>中的红框)来完成的。这个函数的处理结果将通过<span style="font-family:'Times New Roman';">NsdManager</span>定义的另外一个接口类<span style="font-family:'Times New Roman';">ResolveListener</span>返回。</span></p><p><span style="font-size:14px;">通过对<span style="font-family:'Times New Roman';">NsdChat</span>的研究,读者会发现:</span></p><ul><li><span style="font-size:14px;">总体而言,<span style="font-family:'Times New Roman';">NsdManager</span>的使用并不复杂,相关类也比较简单。</span></li><li><span style="font-size:14px;">唯一特别之处是其主要<span style="font-family:'Times New Roman';">API</span>都被设计成异步调用的方式,这将增大应用程序开发的难度。请读取务必注意这点。</span></li></ul><h2><a name="t6"></a>三总结</h2><p><span style="font-size:14px;">本文对<span style="font-family:'Times New Roman';">Android</span>中<span style="font-family:'Times New Roman';">Bonjour</span>的实现进行了一番介绍。<span style="font-family:'Times New Roman';">Bonjour</span>的原理知识还请读者阅读</span><a href="https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html%23//apple_ref/doc/uid/TP40002445-SW1" rel="nofollow" target="_blank"><span style="font-family:'Times New Roman';font-size:14px;color:#0000ff;">https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html#//apple_ref/doc/uid/TP40002445-SW1</span></a><span style="font-size:14px;">。该网站是关于<span style="font-family:'Times New Roman';">Bonjure</span>基础知识的入口,包含《<span style="font-family:'Times New Roman';">About Bonjour</span>》、《<span style="font-family:'Times New Roman';">Bonjour API Architecture</span>》等文档。</span></p><p><span style="font-size:14px;">另外,<span style="font-family:'Times New Roman';">Android</span>中的<span style="font-family:'Times New Roman';">Bonjour</span>主要是为了支持<span style="font-family:'Times New Roman';">Network Service Discovery</span>功能。与其类似的还有<span style="font-family:'Times New Roman';">UPnP</span>技术中使用的<span style="font-family:'Times New Roman';">Simple Service Discovery Protocol</span>(<span style="font-family:'Times New Roman';">SSDP</span>)。相比<span style="font-family:'Times New Roman';">Bonjour</span>而言,<span style="font-family:'Times New Roman';">UPnP</span>不仅实现了<span style="font-family:'Times New Roman';">NSD</span>,还在后续客户端和服务端交互方面支持标准<span style="font-family:'Times New Roman';">SOAP</span>协议,极大方便了客户端和服务端的代码逻辑实现。所以,笔者在此提醒开发者,如果想使用<span style="font-family:'Times New Roman';">Bonjour</span>技术,要特别注意<span style="font-family:'Times New Roman';">Nsd</span>只能简化服务注册及寻找这一步骤,后续还需重点考虑客户端和服务端交互的协议及实现。</span></p><span style="font-size:14px;">关于DLNA,读者可参考笔者的博客</span><a href="http://blog.csdn.net/innost/article/details/7078539" rel="nofollow" target="_blank"><span style="font-size:14px;">http://blog.csdn.net/innost/article/details/7078539</span></a><span style="font-size:14px;">。</span>          </div>
              </div>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值