NSD服務(wù)介紹

什么是NSD土全?

NSD全稱(chēng)為: Network Service Discovery.也就是網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)的意思捎琐。(可以在局域網(wǎng)內(nèi)發(fā)現(xiàn)同樣使用nsd注冊(cè)了的應(yīng)用設(shè)備的網(wǎng)絡(luò)信息)

NSD應(yīng)用于哪?

通常應(yīng)用于局域網(wǎng)內(nèi)不同應(yīng)用設(shè)備的互聯(lián)

  1. 發(fā)現(xiàn)配置打印機(jī)
  2. 應(yīng)用小游戲等的互聯(lián)

NSD簡(jiǎn)介

NSD(NsdManager)是Android SDK中自帶的類(lèi)庫(kù)裹匙,可以集成直接使用瑞凑。
使用 NSD服務(wù)需要(android4.1及以上) minSdkVersion >16

NSD主要包含兩個(gè)功能:

1. NSD 注冊(cè)功能:
進(jìn)行NSD注冊(cè):自定義服務(wù)名、端口號(hào)概页,IP地址注冊(cè)到NSD服務(wù)中
2. NSD 掃描功能:
掃描到當(dāng)前局域網(wǎng)內(nèi)所有已通過(guò)NSD注冊(cè)了的應(yīng)用設(shè)備的網(wǎng)絡(luò)信息(服務(wù)名籽御、端口號(hào)、IP地址)

NSD基本原理

實(shí)現(xiàn)了網(wǎng)絡(luò)發(fā)現(xiàn)服務(wù)NsdService惰匙,其基于蘋(píng)果的Bonjour服務(wù)發(fā)現(xiàn)協(xié)議

Bonjour協(xié)議主要包括:

  • 服務(wù)的發(fā)現(xiàn)
  • 服務(wù)名稱(chēng)與地址的轉(zhuǎn)換

Bonjour協(xié)議流程和DNS流程近似技掏,包括:
1. 服務(wù)登記過(guò)程
2. 服務(wù)發(fā)現(xiàn)過(guò)程
3. 服務(wù)地址解析過(guò)程
4. 建立連接等過(guò)程

服務(wù)發(fā)現(xiàn)采用的協(xié)議也和DNS類(lèi)似,不過(guò)與DNS協(xié)議采用的單播方式不同的是采用了組播方式项鬼,因此被稱(chēng)為mDNS哑梳。

什么是mDNS?

mDNS multicast DNS (組播Dns)

首先绘盟,在 IP 協(xié)議里規(guī)定了一些保留地址鸠真,其中有一個(gè)是 224.0.0.251,對(duì)應(yīng)的 IPv6 地址是 [FF02::FB]龄毡。
mDNS 協(xié)議規(guī)定了一個(gè)端口吠卷,5353。
mDNS 基于 UDP 協(xié)議沦零。

mDNS注冊(cè)掃描流程:A主機(jī)進(jìn)入局域網(wǎng)祭隔,開(kāi)啟了 mDNS 服務(wù),并向 mDNS 服務(wù)注冊(cè)以下信息:我的服務(wù)名是AiXue,我的IP是 192.168.1.101路操,端口是 21序攘。當(dāng)B主機(jī)進(jìn)入局域網(wǎng),并向 mDNS 服務(wù)進(jìn)行掃描請(qǐng)求寻拂,掃描到mDNS服務(wù)中所有已注冊(cè)的主機(jī)后程奠,從中過(guò)濾出服務(wù)名是AiXue的主機(jī),并解析獲得到它的網(wǎng)絡(luò)信息為IP地址為 192.168.1.101祭钉,端口號(hào)是 21 瞄沙。

ANDROID借助第三方開(kāi)源工程mDNSResponder實(shí)現(xiàn)了Bonjour協(xié)議。
ANDROID對(duì)網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)的實(shí)現(xiàn)架構(gòu)包括四層:

  1. NSD應(yīng)用層
  2. 服務(wù)發(fā)現(xiàn)服務(wù)框架層(對(duì)應(yīng)NsdService)
  3. MDns后臺(tái)監(jiān)聽(tīng)層(對(duì)應(yīng)運(yùn)行在netd本地服務(wù)進(jìn)程的MDnsSdListener類(lèi) )
  4. MDns后臺(tái)服務(wù)(對(duì)應(yīng)mdnsd本地服務(wù)進(jìn)程)慌核。

架構(gòu)的每層作為其上一層的服務(wù)端對(duì)上一層提供服務(wù)距境,四層分別運(yùn)行在不同的進(jìn)程,采用相應(yīng)的跨進(jìn)程通訊方式進(jìn)行交互,上層通過(guò)connect與下層服務(wù)建立連接垮卓。其中NsdService 和NSD應(yīng)用層采用JAVA語(yǔ)言實(shí)現(xiàn) 垫桂,MDns后臺(tái)監(jiān)視采用C++實(shí)現(xiàn),而MDns后臺(tái)服務(wù)為采用C語(yǔ)言的開(kāi)源代碼粟按。

image.png
  • NSD應(yīng)用層通過(guò)NsdService層提供的NsdManager類(lèi)诬滩,對(duì)NSD進(jìn)行注冊(cè)霹粥、掃描、接收響應(yīng)等操作疼鸟。

  • NsdService處于整個(gè)層次的承上啟下層后控,其通過(guò)NsdManager對(duì)應(yīng)用層提供調(diào)用和回調(diào)服務(wù),NsdManager和NsdService服務(wù)之間采用AsyncChannel異步通道進(jìn)行消息交互空镜。NsdService服務(wù)對(duì)下在其N(xiāo)ativeDaemonConnector線程對(duì)象中使用UNIX SOCKET接口與MDnsSdListener建立跨進(jìn)程連接浩淘。

  • 在MDnsSdListener類(lèi)中調(diào)用mDNSResponder開(kāi)源工程提供的客戶端樁接口與MDns后臺(tái)服務(wù)建立本地SOCKET通訊,并采用Monitor對(duì)象來(lái)啟動(dòng)MDns后臺(tái)服務(wù)吴攒,實(shí)現(xiàn)MDns后臺(tái)服務(wù)的事件監(jiān)聽(tīng)和事件回調(diào)處理等工作张抄。MDnsSdListener及Monitor對(duì)象與MDns后臺(tái)服務(wù)的交互也是采用UNIX SOCKET機(jī)制進(jìn)行跨進(jìn)程交互。

  • MDns后臺(tái)服務(wù)的整個(gè)實(shí)現(xiàn)代碼及客戶端的樁實(shí)現(xiàn)由第三方工程mDNSResponder提供洼怔,代碼位于 external目錄下 的mdnsresponder中欣鳖,包括mDNSCore(包括MDNS核心協(xié)議引擎代碼)、mDNSShared多個(gè)平臺(tái)共享的非核心引擎代碼茴厉、mDNSPosix Posix平臺(tái)相關(guān)代碼泽台、Clients包括如何使用后臺(tái)服務(wù)提供的API的客戶端例子代碼等四個(gè)目錄,整個(gè)工程編譯生成一個(gè)mdnsd后臺(tái)服務(wù)和一個(gè)MDns監(jiān)視層使用的庫(kù)libmdnssd矾缓,而Clients中的代碼生成一個(gè)dnssd執(zhí)行文件用于測(cè)試怀酷。

NSD 注冊(cè)功能開(kāi)發(fā)

1.注冊(cè)NSD

  private NsdManager mNsdManager;
  //NSD注冊(cè)
   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.");//掃描是需要對(duì)應(yīng)的這個(gè)Type字符串
        mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
    }

到這里如果其他設(shè)備馬上進(jìn)行掃描就能看到注冊(cè)了NSD的服務(wù)器網(wǎng)絡(luò)信息

2.注銷(xiāo)NSD

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

可以取消掉注冊(cè)NSD服務(wù)器,就是讓別人掃描不到你的NSD服務(wù)器

3.注冊(cè)監(jiān)聽(tīng)器

private NsdManager.RegistrationListener mRegistrationListener;

//實(shí)例化注冊(cè)監(jiān)聽(tīng)器
    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掃描功能開(kāi)發(fā)

開(kāi)始NSD掃描

private var mNsdManager: NsdManager? = null

     /**
     * 啟動(dòng)nsd掃描
     * @param mServiceName 服務(wù)名與注冊(cè)者保持一致
     * @param mIDiscoverState 掃描狀態(tài)回調(diào)
     */
    fun startNsdClient() {
        mNsdManager = mContext.getSystemService(Context.NSD_SERVICE) as NsdManager
        mNsdManager?.discoverServices("_http._tcp.", NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener)
    }

進(jìn)行發(fā)現(xiàn)服務(wù)操作后嗜闻,會(huì)在掃描監(jiān)聽(tīng)器對(duì)應(yīng)的方法得到數(shù)據(jù)蜕依。

停止NSD掃描

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

注冊(cè)掃描監(jiān)聽(tīng)器

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")
            }
        }
}

注冊(cè)流程源碼分析

  1. NsdManager的實(shí)例化
    應(yīng)用通過(guò)調(diào)用Context.getSystemService(Context.NSD_SERVICE)獲得NsdManager的實(shí)例。在NsdManager的實(shí)例化過(guò)程中對(duì)使用到的資源進(jìn)行實(shí)例化琉雳,包括調(diào)用NsdService的getMessenger函數(shù)獲得服務(wù)的Messenger對(duì)象用作客戶端消息的發(fā)送目標(biāo)样眠,實(shí)例化和啟動(dòng)事件處理線程HandlerThread及實(shí)例化事件接收處理對(duì)象ServiceHandlerAsyncChannel對(duì)象的實(shí)例化并且調(diào)用AsyncChannel對(duì)象的connec函數(shù)與NsdService建立連接翠肘。
    在NsdService服務(wù)接收到連接消息后檐束,實(shí)例化一個(gè)服務(wù)端的AsyncChannel對(duì)象,并根據(jù)消息的源和服務(wù)端的AsyncChannel對(duì)象實(shí)例化一個(gè)ClientInfo對(duì)象放入mClients HashMap數(shù)組中束倍。
    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的注冊(cè)
    應(yīng)用調(diào)用NsdManager實(shí)例的registerService接口被丧,registerService接口參數(shù)中包含一個(gè)NsdServiceInfo參數(shù)(指示要登記的服務(wù)信息)、一個(gè)protocolType參數(shù)(指定協(xié)議類(lèi)型)以及一個(gè)監(jiān)聽(tīng)對(duì)象listener绪妹,用來(lái)接收響應(yīng)事件回調(diào)甥桂。
    在registerService接口中調(diào)用putListener函數(shù)分別把NsdServiceInfo參數(shù)和監(jiān)聽(tīng)對(duì)象listener保存到mServiceMap和mListenerMap的映射數(shù)組中,并返回?cái)?shù)組的鍵值key邮旷;然后registerService通過(guò)NsdManager的AsyncChannel對(duì)象向目標(biāo)發(fā)送REGISTER_SERVICE消息黄选,發(fā)送的消息參數(shù)包括putListener函數(shù)返回的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注冊(cè)及監(jiān)聽(tīng)
    NsdService服務(wù)收到REGISTER_SERVICE消息后婶肩,首先根據(jù)消息源從mClients數(shù)組中獲得clientInfo對(duì)象办陷,然后調(diào)用getUniqueId獲得一個(gè)UniqueId作為登記請(qǐng)求ID貌夕;接著調(diào)用服務(wù)端的registerService函數(shù),registerService的參數(shù)為UniqueId和消息傳進(jìn)來(lái)的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的服務(wù)注冊(cè):在registerService函數(shù)中調(diào)用NativeDaemonConnector對(duì)象的execute函數(shù)蜂嗽,execute函數(shù)的命令參數(shù)為”mdnssd”苗膝,其它參數(shù)包括登記命令名稱(chēng)標(biāo)示"register"殃恒、登記ID、從NsdServiceInfo中獲得的ServiceName辱揭、ServiceType和port等參數(shù)离唐。
NativeDaemonConnector對(duì)象在NsdService服務(wù)實(shí)例化時(shí)實(shí)例化, NativeDaemonConnector對(duì)象實(shí)例化mSocket參數(shù)為"mdns"问窃,mCallbacks參數(shù)指向NsdService服務(wù)內(nèi)部NativeCallbackReceiver對(duì)象亥鬓。NativeDaemonConnector對(duì)象本身是一個(gè)派生自Runnable的線程對(duì)象,因此其線程函數(shù)run也在實(shí)例化后啟動(dòng)域庇。

    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讀操作來(lái)監(jiān)聽(tīng)mDnsListener層發(fā)來(lái)的消息:listenToSocket首先實(shí)例化一個(gè)本地socket對(duì)象嵌戈,LocalSocket對(duì)象的LocalSocketAddress地址的 Socket名稱(chēng)為已初始化的mSocket,并使用該地址調(diào)用connect函數(shù)听皿,從init.rc 可以看到名稱(chēng)為"mdns"的Socket對(duì)應(yīng)的本地服務(wù)為netd熟呛,因此NativeCallbackReceiver對(duì)象與netd服務(wù)建立了連接;然后listenToSocket函數(shù)調(diào)用socket的getInputStream和getOutputStream函數(shù)獲得輸入和輸出流對(duì)象尉姨;最后listenToSocket函數(shù)進(jìn)入while循環(huán)不斷從輸入流讀取事件進(jìn)行分析庵朝。解析后的事件發(fā)給HandlerThread線程的Handler函數(shù)進(jìn)行處理,在Handler函數(shù)中調(diào)用mCallbacks的onEvent回調(diào)函數(shù)又厉,即NsdService服務(wù)內(nèi)部NativeCallbackReceiver對(duì)象的onEvent回調(diào)函數(shù)九府。

 @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通過(guò)Socket往mDnsListener層寫(xiě)操作
    在NativeDaemonConnector對(duì)象的execute函數(shù)中首先根據(jù)傳進(jìn)的參數(shù)調(diào)用makeCommand函數(shù)生成一個(gè)字符串類(lèi)型的命令,然后調(diào)用本地socket的輸出流對(duì)象 mOutputStream的write函數(shù)來(lái)發(fā)送命令覆致。
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. 在本地服務(wù)netd的進(jìn)程中調(diào)用其MDnsSdListener對(duì)象的startListener函數(shù)啟動(dòng)命令的監(jiān)聽(tīng)侄旬。
    MDnsSdListener對(duì)象通過(guò)FrameworkListener間接派生自SocketListener,在MDnsSdListener對(duì)象實(shí)例化時(shí)其成員mSocketName初始化 為"mdns"煌妈,因此對(duì)應(yīng)的socket通道和NativeCallbackReceiver對(duì)象中的socket通道相同勾怒。MDnsSdListener實(shí)例化時(shí)還初始化一個(gè)Monitor對(duì)象和一個(gè)FrameworkCommand類(lèi)型的Handler對(duì)象。
    Handler對(duì)象初始化時(shí)其mCommand屬性賦值為"mdnssd"声旺,用來(lái)和發(fā)送來(lái)的命令匹配笔链,Handler對(duì)象也保存到FrameworkCommand命令列表對(duì)象中mCommands。
    Monitor對(duì)象實(shí)例化時(shí)調(diào)用socketpair函數(shù)建立一個(gè)Socket組mCtrlSocketPair腮猖,還創(chuàng)建一個(gè)監(jiān)聽(tīng)線程鉴扫,線程中調(diào)用Monitor對(duì)象的run函數(shù)。

  2. startListener函數(shù)首先調(diào)用android_get_control_socket函數(shù)根據(jù)mSocketName名稱(chēng)獲得其SOCKET fd澈缺;然后調(diào)用listen函數(shù)監(jiān)聽(tīng)socket通道坪创;
    然后創(chuàng)建一個(gè)線程炕婶,在線程中執(zhí)行runListener函數(shù),在runListener函數(shù)循環(huán)調(diào)用accept接收客戶端連接莱预。當(dāng)有客戶端連接后柠掂,根據(jù)accept返回的socket fd實(shí)例化一個(gè)SocketClient對(duì)象保存到SocketClient對(duì)象列表中mClients,并調(diào)用onDataAvailable函數(shù)依沮。
    onDataAvailable函數(shù)調(diào)用read函數(shù)讀取客戶端發(fā)送的命令涯贞,并調(diào)用dispatchCommand函數(shù)提交命令
    在dispatchCommand函數(shù)中解析命令參數(shù)危喉,并與mCommands命令對(duì)象列表進(jìn)行命令匹配宋渔,并調(diào)用匹配后命令對(duì)象的runCommand函數(shù),這里即調(diào)用MDnsSdListener對(duì)象中的Handler對(duì)象的runCommand函數(shù)辜限。

  3. 在Handler對(duì)象的runCommand函數(shù)中進(jìn)行命令參數(shù)的匹配皇拣,這里匹配的是"register",因此在獲得命令參數(shù)后調(diào)用serviceRegister函數(shù)薄嫡,serviceRegister函數(shù)參數(shù)包括匹配的SocketClient對(duì)象以及命令參數(shù)信息氧急。

  4. 在serviceRegister函數(shù)中,首先調(diào)用mMonitor的allocateServiceRef函數(shù)根據(jù)請(qǐng)求ID實(shí)例化一個(gè)Element對(duì)象放入鏈表中毫深,并返回Element對(duì)象的DNSServiceRef指針吩坝,DNSServiceRef指向_DNSServiceRef_t結(jié)構(gòu),其成員包括DNS操作或應(yīng)答類(lèi)型费什,接收消息回調(diào)接口钾恢、客戶端回調(diào)和上下文、客戶端與服務(wù)端連接socket等參數(shù)鸳址。
    然后調(diào)用DNSServiceRegister函數(shù)瘩蚪,DNSServiceRegister函數(shù)用來(lái)向本地MDns后臺(tái)服務(wù)發(fā)起連接和消息請(qǐng)求,DNSServiceRegister函數(shù)的參數(shù)包括allocateServiceRef函數(shù)返回的DNSServiceRef指針變量以及serviceRegister傳進(jìn)來(lái)的命令請(qǐng)求參數(shù)稿黍,以及事件接收回調(diào)函數(shù)MDnsSdListenerRegisterCallback疹瘦。

  5. DNSServiceRegister函數(shù)為mDNSResponder開(kāi)源工程提供的客戶端調(diào)用API接口,用來(lái)與MDns后臺(tái)服務(wù)建立連接巡球,并向其提交請(qǐng)求言沐。
    在DNSServiceRegister函數(shù)中首先通過(guò)ConnectToServer函數(shù)與MDns后臺(tái)服務(wù)建立連接。
    在ConnectToServer函數(shù)首先實(shí)例和初始化一個(gè)_DNSServiceRef_t類(lèi)型DNSServiceOp變量酣栈,然后創(chuàng)建一個(gè)本地socket险胰,且新建socket的文件句柄賦值給DNSServiceOp對(duì)象的sockfd。
    然后調(diào)用connect與MDns后臺(tái)服務(wù)建立連接矿筝,最后把實(shí)例化后的DNSServiceOp對(duì)象通過(guò)DNSServiceRef參數(shù)帶回起便。
    ConnectToServer函數(shù)返回后接著調(diào)用create_hdr函數(shù)為實(shí)例化一個(gè)ipc_msg_hdr類(lèi)型的請(qǐng)求消息,并對(duì)請(qǐng)求消息賦值后連同ConnectToServer函數(shù)帶回的DNSServiceRef參數(shù)一同傳給deliver_request函數(shù),通過(guò)deliver_request函數(shù)提交請(qǐng)求榆综。

  6. 在Monitor對(duì)象的run函數(shù)中循環(huán)對(duì)mPollFds進(jìn)行poll操作妙痹。
    在startMonitoring函數(shù)通過(guò)向mCtrlSocketPair[1]寫(xiě)入RESCAN命令后,由于mPollFds[0].fd指向mCtrlSocketPair[0]鼻疮,因此mMonitor的run函數(shù)在mPollFds[0]通道讀取到RESCAN命令并調(diào)用RESCAN函數(shù)怯伊,在RESCAN函數(shù)中根據(jù)已建立的與服務(wù)器的連接為mPollFds的其它通道賦值,這些mPollFds通道的文件句柄位賦值為服務(wù)器已建立連接的socket 的句柄判沟。
    在服務(wù)端的響應(yīng)事件到來(lái)時(shí)在這些通道poll到事件耿芹,然后調(diào)用DNSServiceProcessResult函數(shù),參數(shù)為DNSServiceRef水评。

  7. 在DNSServiceProcessResult函數(shù)中讀取響應(yīng)事件和數(shù)據(jù)猩系,并調(diào)用DNSServiceRef參數(shù)的事件回調(diào)ProcessReply函數(shù)媚送,即對(duì)于服務(wù)登記請(qǐng)求對(duì)應(yīng)的是MDnsSdListenerRegisterCallback函數(shù)中燥。
    在MDnsSdListenerRegisterCallback中向Handler對(duì)象的監(jiān)聽(tīng)對(duì)象的sendBroadcast函數(shù)發(fā)送ResponseCode::ServiceRegistrationSucceeded應(yīng)答消息,Handler對(duì)象的監(jiān)聽(tīng)對(duì)象為MDnsSdListener對(duì)象本身塘偎,因此這里調(diào)用SocketListener的sendBroadcast函數(shù)求厕。
    在sendBroadcast函數(shù)中遍歷mClients對(duì)象的成員對(duì)象须床,并調(diào)用其調(diào)用sendMsg函數(shù),即調(diào)用SocketClient的sendMsg函數(shù)。
    在sendMsg函數(shù)中通過(guò)與客戶端(即NsdService服務(wù)的NativeDaemonConnector對(duì)象)建立的SOCKET向客戶端發(fā)送應(yīng)答消息肌毅。

  8. 在NsdService的NativeDaemonConnector對(duì)象的listenToSocket函數(shù) 收到服務(wù)端的應(yīng)答消息后,調(diào)用NsdService服務(wù)內(nèi)部NativeCallbackReceiver對(duì)象的onEvent回調(diào)函數(shù)俄周。
    在onEvent回調(diào)函數(shù)中向NsdService服務(wù)的狀態(tài)機(jī)發(fā)送NsdManager.NATIVE_DAEMON_EVENT事件亡电,假如這時(shí)NsdService服務(wù)處于EnabledState狀態(tài),狀態(tài)機(jī)收到NsdManager.NATIVE_DAEMON_EVENT事件后調(diào)用handleNativeEvent函數(shù)壮池。

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函數(shù)首先根據(jù)響應(yīng)消息的請(qǐng)求ID從mIdToClientInfoMap中獲得先前應(yīng)用層建立連接時(shí)保存的clientInfo對(duì)象及從clientInfo對(duì)象獲得clientId偏瓤,然后執(zhí)行響應(yīng)事件代碼為NativeResponseCode.SERVICE_REGISTERED的事件處理,事件處理先根據(jù)返回的響應(yīng)事件實(shí)例化一個(gè)NsdServiceInfo對(duì)象椰憋,然后通過(guò)clientInfo中的AsyncChannel對(duì)象成員向NsdService服務(wù)的應(yīng)用層發(fā)送NsdManager.REGISTER_SERVICE_SUCCEEDED響應(yīng)事件厅克。

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的事件接收對(duì)象ServiceHandler接收到NsdManager.REGISTER_SERVICE_SUCCEEDED響應(yīng)事件,在其handleMessage函數(shù)中調(diào)用其監(jiān)聽(tīng)對(duì)象(NSD應(yīng)用層)的onServiceRegistered回調(diào)橙依。到此整個(gè)服務(wù)登記流程結(jié)束证舟。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市窗骑,隨后出現(xiàn)的幾起案子女责,更是在濱河造成了極大的恐慌,老刑警劉巖创译,帶你破解...
    沈念sama閱讀 211,348評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抵知,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)辛藻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)碘橘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吱肌,你說(shuō)我怎么就攤上這事痘拆。” “怎么了氮墨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,936評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵纺蛆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我规揪,道長(zhǎng)桥氏,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,427評(píng)論 1 283
  • 正文 為了忘掉前任猛铅,我火速辦了婚禮字支,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘奸忽。我一直安慰自己堕伪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布栗菜。 她就那樣靜靜地躺著欠雌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疙筹。 梳的紋絲不亂的頭發(fā)上富俄,一...
    開(kāi)封第一講書(shū)人閱讀 49,785評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音而咆,去河邊找鬼霍比。 笑死,一個(gè)胖子當(dāng)著我的面吹牛翘盖,可吹牛的內(nèi)容都是我干的桂塞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼馍驯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼阁危!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起汰瘫,我...
    開(kāi)封第一講書(shū)人閱讀 37,696評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狂打,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后混弥,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體趴乡,經(jīng)...
    沈念sama閱讀 44,141評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡对省,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晾捏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蒿涎。...
    茶點(diǎn)故事閱讀 38,625評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖惦辛,靈堂內(nèi)的尸體忽然破棺而出劳秋,到底是詐尸還是另有隱情,我是刑警寧澤胖齐,帶...
    沈念sama閱讀 34,291評(píng)論 4 329
  • 正文 年R本政府宣布玻淑,位于F島的核電站,受9級(jí)特大地震影響呀伙,放射性物質(zhì)發(fā)生泄漏补履。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評(píng)論 3 312
  • 文/蒙蒙 一剿另、第九天 我趴在偏房一處隱蔽的房頂上張望箫锤。 院中可真熱鬧,春花似錦驰弄、人聲如沸麻汰。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至溺职,卻和暖如春岔擂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浪耘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工乱灵, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人七冲。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓痛倚,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親澜躺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蝉稳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容