NSD服务介绍

什么是NSD?

NSD全称为: Network Service Discovery.也就是网络服务发现的意思。(可以在局域网内发现同样使用nsd注册了的应用设备的网络信息)

NSD应用于哪?

通常应用于局域网内不同应用设备的互联

  1. 发现配置打印机
  2. 应用小游戏等的互联

NSD简介

NSD(NsdManager)是Android SDK中自带的类库,可以集成直接使用。
使用 NSD服务需要(android4.1及以上) minSdkVersion >16

NSD主要包含两个功能:

1. NSD 注册功能:
进行NSD注册:自定义服务名、端口号,IP地址注册到NSD服务中
2. NSD 扫描功能:
扫描到当前局域网内所有已通过NSD注册了的应用设备的网络信息(服务名、端口号、IP地址)

NSD基本原理

实现了网络发现服务NsdService,其基于苹果的Bonjour服务发现协议

Bonjour协议主要包括:

  • 服务的发现
  • 服务名称与地址的转换

Bonjour协议流程和DNS流程近似,包括:
1. 服务登记过程
2. 服务发现过程
3. 服务地址解析过程
4. 建立连接等过程

服务发现采用的协议也和DNS类似,不过与DNS协议采用的单播方式不同的是采用了组播方式,因此被称为mDNS。

什么是mDNS?

mDNS multicast DNS (组播Dns)

首先,在 IP 协议里规定了一些保留地址,其中有一个是 224.0.0.251,对应的 IPv6 地址是 [FF02::FB]。
mDNS 协议规定了一个端口,5353。
mDNS 基于 UDP 协议。

mDNS注册扫描流程:A主机进入局域网,开启了 mDNS 服务,并向 mDNS 服务注册以下信息:我的服务名是AiXue,我的IP是 192.168.1.101,端口是 21。当B主机进入局域网,并向 mDNS 服务进行扫描请求,扫描到mDNS服务中所有已注册的主机后,从中过滤出服务名是AiXue的主机,并解析获得到它的网络信息为IP地址为 192.168.1.101,端口号是 21 。

ANDROID借助第三方开源工程mDNSResponder实现了Bonjour协议。
ANDROID对网络服务发现的实现架构包括四层:

  1. NSD应用层
  2. 服务发现服务框架层(对应NsdService)
  3. MDns后台监听层(对应运行在netd本地服务进程的MDnsSdListener类 )
  4. MDns后台服务(对应mdnsd本地服务进程)。

架构的每层作为其上一层的服务端对上一层提供服务,四层分别运行在不同的进程,采用相应的跨进程通讯方式进行交互,上层通过connect与下层服务建立连接。其中NsdService 和NSD应用层采用JAVA语言实现 ,MDns后台监视采用C++实现,而MDns后台服务为采用C语言的开源代码。

image.png
  • NSD应用层通过NsdService层提供的NsdManager类,对NSD进行注册、扫描、接收响应等操作。

  • NsdService处于整个层次的承上启下层,其通过NsdManager对应用层提供调用和回调服务,NsdManager和NsdService服务之间采用AsyncChannel异步通道进行消息交互。NsdService服务对下在其NativeDaemonConnector线程对象中使用UNIX SOCKET接口与MDnsSdListener建立跨进程连接。

  • 在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执行文件用于测试。

NSD 注册功能开发

1.注册NSD

  private NsdManager mNsdManager;
  //NSD注册
   private void registerService(Context context, String serviceName, int port) {
        mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
        NsdServiceInfo serviceInfo = new NsdServiceInfo();
        serviceInfo.setServiceName("AiXue");
        serviceInfo.setPort(21);
        serviceInfo.setServiceType("_http._tcp.");//扫描是需要对应的这个Type字符串
        mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
    }

到这里如果其他设备马上进行扫描就能看到注册了NSD的服务器网络信息

2.注销NSD

  public void stopNSDServer() {
        mNsdManager.unregisterService(mRegistrationListener);
  }

可以取消掉注册NSD服务器,就是让别人扫描不到你的NSD服务器

3.注册监听器

private NsdManager.RegistrationListener mRegistrationListener;

//实例化注册监听器
    private void initializeRegistrationListener() {
        mRegistrationListener = new NsdManager.RegistrationListener() {

            @Override
            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
                Log.i(TAG, "onServiceRegistered: " + serviceInfo);

            }

           @Override
            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.e(TAG, "NsdServiceInfo onRegistrationFailed");

            }

            @Override
            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
                Log.i(TAG, "onServiceUnregistered serviceInfo: " + serviceInfo);

            }
 
            @Override
            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.i(TAG, "onUnregistrationFailed serviceInfo: " + serviceInfo + " ,errorCode:" + errorCode);

            }

        };
    }

NSD扫描功能开发

开始NSD扫描

private var mNsdManager: NsdManager? = null

     /**
     * 启动nsd扫描
     * @param mServiceName 服务名与注册者保持一致
     * @param mIDiscoverState 扫描状态回调
     */
    fun startNsdClient() {
        mNsdManager = mContext.getSystemService(Context.NSD_SERVICE) as NsdManager
        mNsdManager?.discoverServices("_http._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener)
    }

进行发现服务操作后,会在扫描监听器对应的方法得到数据。

停止NSD扫描

fun stopNsdServer() {
        mNsdManager?.stopServiceDiscovery(mDiscoveryListener)
}

注册扫描监听器

private fun initializeDiscoveryListener() {
        mDiscoveryListener = object : NsdManager.DiscoveryListener {
            override fun onDiscoveryStarted(serviceType: String) {}

            override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
                mNsdManager?.stopServiceDiscovery(this)
            }

            override fun onDiscoveryStopped(serviceType: String) {}

            override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
                mNsdManager?.stopServiceDiscovery(this)
            }
            override fun onServiceFound(serviceInfo: NsdServiceInfo) {
                if (serviceInfo.serviceType == "_http._tcp." && serviceInfo.serviceName == "AiXue") {
                    // 解析
                    mNsdManager?.resolveService(serviceInfo, object : NsdManager.ResolveListener {
                        override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {"onResolveFailed")
                        }

                        override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
                            val port = serviceInfo.port
                            val host = serviceInfo.host
                        }
                    })
                }
            }

            override fun onServiceLost(serviceInfo: NsdServiceInfo) {
                LiveLocalLog.e("onServiceLost: serviceInfo=$serviceInfo")
                mIDiscoverState?.onDiscoverFail(100, "onServiceLost")
            }
        }
}

注册流程源码分析

  1. NsdManager的实例化
    应用通过调用Context.getSystemService(Context.NSD_SERVICE)获得NsdManager的实例。在NsdManager的实例化过程中对使用到的资源进行实例化,包括调用NsdService的getMessenger函数获得服务的Messenger对象用作客户端消息的发送目标,实例化和启动事件处理线程HandlerThread及实例化事件接收处理对象ServiceHandlerAsyncChannel对象的实例化并且调用AsyncChannel对象的connec函数与NsdService建立连接
    在NsdService服务接收到连接消息后,实例化一个服务端的AsyncChannel对象,并根据消息的源和服务端的AsyncChannel对象实例化一个ClientInfo对象放入mClients HashMap数组中。
    public NsdManager(Context context, INsdManager service) {
        mService = service;
        mContext = context;
        init();
    }

    private void init() {
        final Messenger messenger = getMessenger();
        if (messenger == null) {
            fatal("Failed to obtain service Messenger");
        }
        HandlerThread t = new HandlerThread("NsdManager");
        t.start();
        mHandler = new ServiceHandler(t.getLooper());
        mAsyncChannel.connect(mContext, mHandler, messenger);
        try {
            mConnected.await();
        } catch (InterruptedException e) {
            fatal("Interrupted wait at init");
        }
    }
  1. NsdManager的注册
    应用调用NsdManager实例的registerService接口,registerService接口参数中包含一个NsdServiceInfo参数(指示要登记的服务信息)、一个protocolType参数(指定协议类型)以及一个监听对象listener,用来接收响应事件回调。
    在registerService接口中调用putListener函数分别把NsdServiceInfo参数和监听对象listener保存到mServiceMap和mListenerMap的映射数组中,并返回数组的键值key;然后registerService通过NsdManager的AsyncChannel对象向目标发送REGISTER_SERVICE消息,发送的消息参数包括putListener函数返回的key以及NsdServiceInfo信息。
    public void registerService(NsdServiceInfo serviceInfo, int protocolType,
            RegistrationListener listener) {
        checkArgument(serviceInfo.getPort() > 0, "Invalid port number");
        checkServiceInfo(serviceInfo);
        checkProtocol(protocolType);
        int key = putListener(listener, serviceInfo);
        mAsyncChannel.sendMessage(REGISTER_SERVICE, 0, key, serviceInfo);
    }
    private int putListener(Object listener, NsdServiceInfo s) {
        checkListener(listener);
        final int key;
        synchronized (mMapLock) {
            int valueIndex = mListenerMap.indexOfValue(listener);
            checkArgument(valueIndex == -1, "listener already in use");
            key = nextListenerKey();
            mListenerMap.put(key, listener);
            mServiceMap.put(key, s);
        }
        return key;
    }
  1. NsdService注册及监听
    NsdService服务收到REGISTER_SERVICE消息后,首先根据消息源从mClients数组中获得clientInfo对象,然后调用getUniqueId获得一个UniqueId作为登记请求ID;接着调用服务端的registerService函数,registerService的参数为UniqueId和消息传进来的NsdServiceInfo信息。
case NsdManager.REGISTER_SERVICE:
                        if (DBG) Slog.d(TAG, "Register service");
                        clientInfo = mClients.get(msg.replyTo);
                        if (requestLimitReached(clientInfo)) {
                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
                                    NsdManager.FAILURE_MAX_LIMIT);
                            break;
                        }

                        id = getUniqueId();
                        if (registerService(id, (NsdServiceInfo) msg.obj)) {
                            if (DBG) Slog.d(TAG, "Register " + msg.arg2 + " " + id);
                            storeRequestMap(msg.arg2, id, clientInfo, msg.what);
                            // Return success after mDns reports success
                        } else {
                            unregisterService(id);
                            replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED,
                                    NsdManager.FAILURE_INTERNAL_ERROR);
                        }
                        break;

NsdService的服务注册:在registerService函数中调用NativeDaemonConnector对象的execute函数,execute函数的命令参数为”mdnssd”,其它参数包括登记命令名称标示"register"、登记ID、从NsdServiceInfo中获得的ServiceName、ServiceType和port等参数。
NativeDaemonConnector对象在NsdService服务实例化时实例化, NativeDaemonConnector对象实例化mSocket参数为"mdns",mCallbacks参数指向NsdService服务内部NativeCallbackReceiver对象。NativeDaemonConnector对象本身是一个派生自Runnable的线程对象,因此其线程函数run也在实例化后启动。

    private boolean registerService(int regId, NsdServiceInfo service) {
        if (DBG) {
            Slog.d(TAG, "registerService: " + regId + " " + service);
        }
        String name = service.getServiceName();
        String type = service.getServiceType();
        int port = service.getPort();
        byte[] textRecord = service.getTxtRecord();
        String record = Base64.encodeToString(textRecord, Base64.DEFAULT).replace("\n", "");
        return mDaemon.execute("register", regId, name, type, port, record);
    }

    public static NsdService create(Context context) throws InterruptedException {
        NsdSettings settings = NsdSettings.makeDefault(context);
        HandlerThread thread = new HandlerThread(TAG);
        thread.start();
        Handler handler = new Handler(thread.getLooper());
        NsdService service = new NsdService(context, settings, handler, DaemonConnection::new);
        service.mDaemonCallback.awaitConnection();
        return service;
    }

   public static class DaemonConnection {
        final NativeDaemonConnector mNativeConnector;

        DaemonConnection(NativeCallbackReceiver callback) {
            mNativeConnector = new NativeDaemonConnector(callback, "mdns", 10, MDNS_TAG, 25, null);
            new Thread(mNativeConnector, MDNS_TAG).start();
        }

        public boolean execute(Object... args) {
            if (DBG) {
                Slog.d(TAG, "mdnssd " + Arrays.toString(args));
            }
            try {
                mNativeConnector.execute("mdnssd", args);
            } catch (NativeDaemonConnectorException e) {
                Slog.e(TAG, "Failed to execute mdnssd " + Arrays.toString(args), e);
                return false;
            }
            return true;
        }
    }

Socket读操作来监听mDnsListener层发来的消息: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回调函数。

 @Override
    public void run() {
        mCallbackHandler = new Handler(mLooper, this);

        while (true) {
            if (isShuttingDown()) break;
            try {
                listenToSocket();
            } catch (Exception e) {
                loge("Error in NativeDaemonConnector: " + e);
                if (isShuttingDown()) break;
                SystemClock.sleep(5000);
            }
        }
    }
private void listenToSocket() throws IOException {
        LocalSocket socket = null;

        try {
            socket = new LocalSocket();
            LocalSocketAddress address = determineSocketAddress();

            socket.connect(address);

            InputStream inputStream = socket.getInputStream();
            synchronized (mDaemonLock) {
                mOutputStream = socket.getOutputStream();
            }

            mCallbacks.onDaemonConnected();

            FileDescriptor[] fdList = null;
            byte[] buffer = new byte[BUFFER_SIZE];
            int start = 0;

            while (true) {
                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
                if (count < 0) {
                    loge("got " + count + " reading with start = " + start);
                    break;
                }
                fdList = socket.getAncillaryFileDescriptors();

                // Add our starting point to the count and reset the start.
                count += start;
                start = 0;

                for (int i = 0; i < count; i++) {
                    if (buffer[i] == 0) {
                        // Note - do not log this raw message since it may contain
                        // sensitive data
                        final String rawEvent = new String(
                                buffer, start, i - start, StandardCharsets.UTF_8);

                        boolean releaseWl = false;
                        try {
                            final NativeDaemonEvent event =
                                    NativeDaemonEvent.parseRawEvent(rawEvent, fdList);

                            log("RCV <- {" + event + "}");

                            if (event.isClassUnsolicited()) {
                                // TODO: migrate to sending NativeDaemonEvent instances
                                if (mCallbacks.onCheckHoldWakeLock(event.getCode())
                                        && mWakeLock != null) {
                                    mWakeLock.acquire();
                                    releaseWl = true;
                                }
                                Message msg = mCallbackHandler.obtainMessage(
                                        event.getCode(), uptimeMillisInt(), 0, event.getRawEvent());
                                if (mCallbackHandler.sendMessage(msg)) {
                                    releaseWl = false;
                                }
                            } else {
                                mResponseQueue.add(event.getCmdNumber(), event);
                            }
                        } catch (IllegalArgumentException e) {
                            log("Problem parsing message " + e);
                        } finally {
                            if (releaseWl) {
                                mWakeLock.release();
                            }
                        }

                        start = i + 1;
                    }
                }

                if (start == 0) {
                    log("RCV incomplete");
                }

                // We should end at the amount we read. If not, compact then
                // buffer and read again.
                if (start != count) {
                    final int remaining = BUFFER_SIZE - start;
                    System.arraycopy(buffer, start, buffer, 0, remaining);
                    start = remaining;
                } else {
                    start = 0;
                }
            }
        } catch (IOException ex) {
            loge("Communications error: " + ex);
            throw ex;
        } finally {
            synchronized (mDaemonLock) {
                if (mOutputStream != null) {
                    try {
                        loge("closing stream for " + mSocket);
                        mOutputStream.close();
                    } catch (IOException e) {
                        loge("Failed closing output stream: " + e);
                    }
                    mOutputStream = null;
                }
            }

            try {
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException ex) {
                loge("Failed closing socket: " + ex);
            }
        }
    }
  1. NsdServer通过Socket往mDnsListener层写操作
    在NativeDaemonConnector对象的execute函数中首先根据传进的参数调用makeCommand函数生成一个字符串类型的命令,然后调用本地socket的输出流对象 mOutputStream的write函数来发送命令。
public NativeDaemonEvent[] executeForList(long timeoutMs, String cmd, Object... args)
            throws NativeDaemonConnectorException {
       ...
        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);

        final String rawCmd = rawBuilder.toString();
        final String logCmd = logBuilder.toString();

        log("SND -> {" + logCmd + "}");

        synchronized (mDaemonLock) {
            if (mOutputStream == null) {
                throw new NativeDaemonConnectorException("missing output stream");
            } else {
                try {
                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
                } catch (IOException e) {
                    throw new NativeDaemonConnectorException("problem sending command", e);
                }
            }
        }

        ...
    }

static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
            String cmd, Object... args) {
        if (cmd.indexOf('\0') >= 0) {
            throw new IllegalArgumentException("Unexpected command: " + cmd);
        }
        if (cmd.indexOf(' ') >= 0) {
            throw new IllegalArgumentException("Arguments must be separate from command");
        }

        rawBuilder.append(sequenceNumber).append(' ').append(cmd);
        logBuilder.append(sequenceNumber).append(' ').append(cmd);
        for (Object arg : args) {
            final String argString = String.valueOf(arg);
            if (argString.indexOf('\0') >= 0) {
                throw new IllegalArgumentException("Unexpected argument: " + arg);
            }

            rawBuilder.append(' ');
            logBuilder.append(' ');

            appendEscaped(rawBuilder, argString);
            if (arg instanceof SensitiveArg) {
                logBuilder.append("[scrubbed]");
            } else {
                appendEscaped(logBuilder, argString);
            }
        }

        rawBuilder.append('\0');
    }
  1. 在本地服务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函数。

  2. 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函数

  3. 在Handler对象的runCommand函数中进行命令参数的匹配,这里匹配的是"register",因此在获得命令参数后调用serviceRegister函数,serviceRegister函数参数包括匹配的SocketClient对象以及命令参数信息。

  4. 在serviceRegister函数中,首先调用mMonitor的allocateServiceRef函数根据请求ID实例化一个Element对象放入链表中,并返回Element对象的DNSServiceRef指针,DNSServiceRef指向_DNSServiceRef_t结构,其成员包括DNS操作或应答类型,接收消息回调接口、客户端回调和上下文、客户端与服务端连接socket等参数。
    然后调用DNSServiceRegister函数,DNSServiceRegister函数用来向本地MDns后台服务发起连接和消息请求,DNSServiceRegister函数的参数包括allocateServiceRef函数返回的DNSServiceRef指针变量以及serviceRegister传进来的命令请求参数,以及事件接收回调函数MDnsSdListenerRegisterCallback。

  5. 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函数提交请求。

  6. 在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。

  7. 在DNSServiceProcessResult函数中读取响应事件和数据,并调用DNSServiceRef参数的事件回调ProcessReply函数,即对于服务登记请求对应的是MDnsSdListenerRegisterCallback函数。
    在MDnsSdListenerRegisterCallback中向Handler对象的监听对象的sendBroadcast函数发送ResponseCode::ServiceRegistrationSucceeded应答消息,Handler对象的监听对象为MDnsSdListener对象本身,因此这里调用SocketListener的sendBroadcast函数。
    在sendBroadcast函数中遍历mClients对象的成员对象,并调用其调用sendMsg函数,即调用SocketClient的sendMsg函数。
    在sendMsg函数中通过与客户端(即NsdService服务的NativeDaemonConnector对象)建立的SOCKET向客户端发送应答消息。

  8. 在NsdService的NativeDaemonConnector对象的listenToSocket函数 收到服务端的应答消息后,调用NsdService服务内部NativeCallbackReceiver对象的onEvent回调函数。
    在onEvent回调函数中向NsdService服务的状态机发送NsdManager.NATIVE_DAEMON_EVENT事件,假如这时NsdService服务处于EnabledState状态,状态机收到NsdManager.NATIVE_DAEMON_EVENT事件后调用handleNativeEvent函数。

class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks {
        ...
        @Override
        public boolean onEvent(int code, String raw, String[] cooked) {
            // TODO: NDC translates a message to a callback, we could enhance NDC to
            // directly interact with a state machine through messages
            NativeEvent event = new NativeEvent(code, raw, cooked);
            mNsdStateMachine.sendMessage(NsdManager.NATIVE_DAEMON_EVENT, event);
            return true;
        }
    }

handleNativeEvent函数首先根据响应消息的请求ID从mIdToClientInfoMap中获得先前应用层建立连接时保存的clientInfo对象及从clientInfo对象获得clientId,然后执行响应事件代码为NativeResponseCode.SERVICE_REGISTERED的事件处理,事件处理先根据返回的响应事件实例化一个NsdServiceInfo对象,然后通过clientInfo中的AsyncChannel对象成员向NsdService服务的应用层发送NsdManager.REGISTER_SERVICE_SUCCEEDED响应事件。

private boolean handleNativeEvent(int code, String raw, String[] cooked) {
                NsdServiceInfo servInfo;
                int id = Integer.parseInt(cooked[1]);
                ClientInfo clientInfo = mIdToClientInfoMap.get(id);
                if (clientInfo == null) {
                    String name = NativeResponseCode.nameOf(code);
                    Slog.e(TAG, String.format("id %d for %s has no client mapping", id, name));
                    return false;
                }

                /* This goes in response as msg.arg2 */
                int clientId = clientInfo.getClientId(id);
                if (clientId < 0) {
                    // This can happen because of race conditions. For example,
                    // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY,
                    // and we may get in this situation.
                    String name = NativeResponseCode.nameOf(code);
                    Slog.d(TAG, String.format(
                            "Notification %s for listener id %d that is no longer active",
                            name, id));
                    return false;
                }
                if (DBG) {
                    String name = NativeResponseCode.nameOf(code);
                    Slog.d(TAG, String.format("Native daemon message %s: %s", name, raw));
                }
                switch (code) {
                 ...
                    case NativeResponseCode.SERVICE_REGISTERED:
                        /* NNN regId serviceName regType */
                        servInfo = new NsdServiceInfo(cooked[2], null);
                        clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
                                id, clientId, servInfo);
                        break;
                    ...
                }
                return true;
            }
  1. NsdManager的事件接收对象ServiceHandler接收到NsdManager.REGISTER_SERVICE_SUCCEEDED响应事件,在其handleMessage函数中调用其监听对象(NSD应用层)的onServiceRegistered回调。到此整个服务登记流程结束。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容