Bonjour协议包括IP地址的自动分配、服务名称与地址的转换以及服务的发现三部分内容,ANDROID4.1借助第三方开源工程mDNSResponder实现了Bonjour协议的服务名称与地址的转换以及服务的发现等 Bonjour部分协议的支持。Bonjour协议的服务名称与地址的转换以及服务的发现采用的流程和DNS流程近似包括:登记过程、服务发现过程、服务地址解析过程以及建立连接等过程,服务发现采用的协议也和DNS协议相似,不过与DNS协议采用的单播方式不同的是采用了组播方式,因此被称为mDNS。
ANDROID4.2 对网络服务发现的实现架构包括四层:NSD应用客户端、服务发现服务框架层(对应NsdService)、MDns后台监视层(对应运行在netd本地服务进程的MDnsSdListener类 )以及MDns后台服务(对应mdnsd本地服务进程)。架构的每层作为其上一层的服务端对上一层提供服务,上层通过connect与下层服务建立连接。其中NsdService 和NSD应用客户端采用JAVA语言实现 ,MDns后台监视采用C++实现,而MDns后台服务为采用C语言的开源代码。四层分别运行在不同的进程,采用相应的跨进程通讯方式进行交互。
NsdService处于整个层次的承上启下层,其通过NsdManager对上层应用客户端提供调用和回调服务,NsdManager客户和NsdService服务之间采用AsyncChannel异步通道进行消息交互。NsdService服务对下在其NativeDaemonConnector线程对象中使用UNIX SOCKET接口与MDns后台监视层建立跨进程连接,传输命令和接收响应,MDns后台监视层的MDnsSdListener对象运行在netd本地服务中。
在MDnsSdListener类中调用mDNSResponder开源工程提供的客户端桩接口与MDns后台服务建立本地SOCKET通讯,并采用Monitor对象来启动MDns后台服务,实现MDns后台服务的事件监听和事件回调处理等工作。MDnsSdListener及Monitor对象与MDns后台服务的交互也是采用UNIX SOCKET机制进行跨进程交互。
MDns后台服务的整个实现代码及客户端的桩实现由第三方工程mDNSResponder提供,代码位于 external目录下 的mdnsresponder中,包括mDNSCore(包括MDNS核心协议引擎代码)、mDNSShared多个平台共享的非核心引擎代码、mDNSPosix Posix平台相关代码、Clients包括如何使用后台服务提供的API的客户端例子代码等四个目录,整个工程编译生成一个mdnsd后台服务和一个MDns监视层使用的库libmdnssd,而Clients中的代码生成一个dnssd执行文件用于测试。
一个应用为了让网络上的其它应用发现它需要通过网络声明自己,即服务登记,这通过调用NsdManager的registerService接口实现。
下面分步骤描述服务登记流程。
1、应用通过调用Context.getSystemService(Context.NSD_SERVICE)获得NsdManager的实例。
在NsdManager的实例化过程中对使用到的资源进行实例化,包括调用NsdService的getMessenger函数获得服务的Messenger对象用作客户端消息的发送目标,实例化和启动事件处理线程HandlerThread及实例化事件接收处理对象ServiceHandler,AsyncChannel对象的实例化并且调用AsyncChannel对象的connect函数与目标建立连接。
在NsdService服务接收到连接消息后,实例化一个服务端的AsyncChannel对象,并根据消息的源和服务端的AsyncChannel对象实例化一个ClientInfo对象放入mClients HashMap数组中。
2、应用调用NsdManager实例的registerService接口,registerService接口参数中包含一个NsdServiceInfo参数(指示要登记的服务信息)、一个protocolType参数(指定协议类型)以及一个监听对象listener,用来接收响应事件回调。
在registerService接口中调用putListener函数分别把NsdServiceInfo参数和监听对象listener保存到mServiceMap和mListenerMap的映射数组中,并返回数组的键值key;然后registerService通过NsdManager的AsyncChannel对象向目标发送REGISTER_SERVICE消息,发送的消息参数包括putListener函数返回的key以及NsdServiceInfo信息。
3、NsdService服务收到REGISTER_SERVICE消息后,首先根据消息源从mClients数组中获得clientInfo对象,然后调用getUniqueId获得一个UniqueId作为登记请求ID;接着调用服务端的registerService函数,registerService的参数为UniqueId和消息传进来的NsdServiceInfo信息。在registerService函数中调用NativeDaemonConnector对象的execute函数,execute函数的命令参数为”mdnssd”,其它参数包括登记命令名称标示"register"、登记ID、从NsdServiceInfo中获得的ServiceName、ServiceType和port等参数。
NativeDaemonConnector对象在NsdService服务实例化时实例化, NativeDaemonConnector对象实例化mSocket参数为"mdns",mCallbacks参数指向NsdService服务内部NativeCallbackReceiver对象。NativeDaemonConnector对象本身是一个派生自Runnable的线程对象,因此其线程函数run也在实例化后启动。
在run函数中首先实例化和启动了一个事件处理线程HandlerThread及其事件处理Handler,接着进入while循环调用listenToSocket函数。
listenToSocket首先实例化一个本地socket对象,LocalSocket对象的LocalSocketAddress地址的 Socket名称为已初始化的mSocket,并使用该地址调用connect函数,从init.rc 可以看到名称为"mdns"的Socket对应的本地服务为netd,因此NativeCallbackReceiver对象与netd服务建立了连接;然后listenToSocket函数调用socket的getInputStream和getOutputStream函数获得输入和输出流对象;最后listenToSocket函数进入while循环不断从输入流读取事件进行分析。解析后的事件发给HandlerThread线程的Handler函数进行处理,在Handler函数中调用mCallbacks的onEvent回调函数,即NsdService服务内部NativeCallbackReceiver对象的onEvent回调函数。
4、在NativeDaemonConnector对象的execute函数中首先根据传进的参数调用makeCommand函数生成一个字符串类型的命令,然后调用本地socket的输出流对象 mOutputStream的write函数来发送命令。
5、在本地服务netd的进程中调用其MDnsSdListener对象的startListener函数启动命令的监听。
MDnsSdListener对象通过FrameworkListener间接派生自SocketListener,在MDnsSdListener对象实例化时其成员mSocketName初始化 为"mdns",因此对应的socket通道和NativeCallbackReceiver对象中的socket通道相同。MDnsSdListener实例化时还初始化一个Monitor对象和一个FrameworkCommand类型的Handler对象。
Handler对象初始化时其mCommand属性赋值为"mdnssd",用来和发送来的命令匹配,Handler对象也保存到FrameworkCommand命令列表对象中mCommands。
Monitor对象实例化时调用socketpair函数建立一个Socket组mCtrlSocketPair,还创建一个监听线程,线程中调用Monitor对象的run函数。
6、startListener函数首先调用android_get_control_socket函数根据mSocketName名称获得其SOCKET fd;然后调用listen函数监听socket通道;
然后创建一个线程,在线程中执行runListener函数,在runListener函数循环调用accept接收客户端连接。当有客户端连接后,根据accept返回的socket fd实例化一个SocketClient对象保存到SocketClient对象列表中mClients,并调用onDataAvailable函数。
onDataAvailable函数调用read函数读取客户端发送的命令,并调用dispatchCommand函数提交命令。
在dispatchCommand函数中解析命令参数,并与mCommands命令对象列表进行命令匹配,并调用匹配后命令对象的runCommand函数,这里即调用MDnsSdListener对象中的Handler对象的runCommand函数。
7、在Handler对象的runCommand函数中进行命令参数的匹配,这里匹配的是"register",因此在获得命令参数后调用serviceRegister函数,serviceRegister函数参数包括匹配的SocketClient对象以及命令参数信息。
8、在serviceRegister函数中,首先调用mMonitor的allocateServiceRef函数根据请求ID实例化一个Element对象放入链表中,并返回Element对象的DNSServiceRef指针,DNSServiceRef指向_DNSServiceRef_t结构,其成员包括DNS操作或应答类型,接收消息回调接口、客户端回调和上下文、客户端与服务端连接socket等参数。
然后调用DNSServiceRegister函数,DNSServiceRegister函数用来向本地MDns后台服务发起连接和消息请求,DNSServiceRegister函数的参数包括allocateServiceRef函数返回的DNSServiceRef指针变量以及serviceRegister传进来的命令请求参数,以及事件接收回调函数MDnsSdListenerRegisterCallback。
在DNSServiceRegister函数调用后接着调用mMonitor的startMonitoring函数,参数为请求ID,startMonitoring函数用来准备与服务器已建立连接的SOCKET监视通道和启动监视通道的监听。最后调用SocketClient对象的sendMsg函数向客户端返回CommandOkay应答消息。
9、DNSServiceRegister函数为mDNSResponder开源工程提供的客户端调用API接口,用来与MDns后台服务建立连接,并向其提交请求。
在DNSServiceRegister函数中首先通过ConnectToServer函数与MDns后台服务建立连接。
在ConnectToServer函数首先实例和初始化一个_DNSServiceRef_t类型DNSServiceOp变量,然后创建一个本地socket,且新建socket的文件句柄赋值给DNSServiceOp对象的sockfd。
然后调用connect与MDns后台服务建立连接,最后把实例化后的DNSServiceOp对象通过DNSServiceRef参数带回。
ConnectToServer函数返回后接着调用create_hdr函数为实例化一个ipc_msg_hdr类型的请求消息,并对请求消息赋值后连同ConnectToServer函数带回的DNSServiceRef参数一同传给deliver_request函数,通过deliver_request函数提交请求。
10 、在Monitor对象的run函数中循环对mPollFds进行poll操作。
在startMonitoring函数通过向mCtrlSocketPair[1]写入RESCAN命令后,由于mPollFds[0].fd指向mCtrlSocketPair[0],因此mMonitor的run函数在mPollFds[0]通道读取到RESCAN命令并调用RESCAN函数,在RESCAN函数中根据已建立的与服务器的连接为mPollFds的其它通道赋值,这些mPollFds通道的文件句柄位赋值为服务器已建立连接的socket 的句柄。
在服务端的响应事件到来时在这些通道poll到事件,然后调用DNSServiceProcessResult函数,参数为DNSServiceRef。
11、 在DNSServiceProcessResult函数中读取响应事件和数据,并调用DNSServiceRef参数的事件回调ProcessReply函数,即对于服务登记请求对应的是MDnsSdListenerRegisterCallback函数。
在MDnsSdListenerRegisterCallback中向Handler对象的监听对象的sendBroadcast函数发送ResponseCode::ServiceRegistrationSucceeded应答消息,Handler对象的监听对象为MDnsSdListener对象本身,因此这里调用SocketListener的sendBroadcast函数。
在sendBroadcast函数中遍历mClients对象的成员对象,并调用其调用sendMsg函数,即调用SocketClient的sendMsg函数。
在sendMsg函数中通过与客户端(即NsdService服务的NativeDaemonConnector对象)建立的SOCKET向客户端发送应答消息。
12、在NsdService的NativeDaemonConnector对象的listenToSocket函数 收到服务端的应答消息后,调用NsdService服务内部NativeCallbackReceiver对象的onEvent回调函数。
在onEvent回调函数中向NsdService服务的状态机发送NsdManager.NATIVE_DAEMON_EVENT事件,假如这时NsdService服务处于EnabledState状态,状态机收到NsdManager.NATIVE_DAEMON_EVENT事件后调用handleNativeEvent函数。
handleNativeEvent函数首先根据响应消息的请求ID从mIdToClientInfoMap中获得先前客户端建立连接时保存的clientInfo对象及从clientInfo对象获得clientId,然后执行响应事件代码为NativeResponseCode.SERVICE_REGISTERED的事件处理,事件处理先根据返回的响应事件实例化一个NsdServiceInfo对象,然后通过clientInfo中的AsyncChannel对象成员向NsdService服务的客户端发送NsdManager.REGISTER_SERVICE_SUCCEEDED响应事件。
13、NsdManager的事件接收对象ServiceHandler接收到NsdManager.REGISTER_SERVICE_SUCCEEDED响应事件,在其handleMessage函数中调用其监听对象(NSD应用客户端)的onServiceRegistered回调。到此整个服务登记流程结束。
服务发现和服务地址解析流程采用服务登记流程基本相同的流程。在服务发现和服务地址解析后就可以向服务收发数据了。