標籤雲

搜尋此網誌

2016/02/23

Network Service Discovery (NSD)

* Register Your Service on the Network

要在 local 網路註冊 service
首先得創建一個 NsdServiceInfo 物件
這物件提供給 local 網路內的其他裝置一些資訊
讓它們決定是否與你的裝置連接
NsdManager mNsdManager;
public void registerService(int port) {
    // 創建 NsdServiceInfo 物件並填充它
    NsdServiceInfo serviceInfo  = new NsdServiceInfo();
    // 若在同一網路中其他裝置也有相同服務名稱,Android會自動把其中一台的 ServiceName 改變避免衝突
    // 例如 "NsdChat" 可能會被改名成 "NsdChat(1)" 這樣的名稱
    serviceInfo.setServiceName("NsdChat");
    // ServiceType 的語法為 "_<protocol>._<transportlayer>"
    // 所以 uses HTTP protocol running over TCP 寫成 "_http._tcp."
    serviceInfo.setServiceType("_http._tcp.");
    serviceInfo.setPort(port);

    mNsdManager = Context.getSystemService(Context.NSD_SERVICE);
    //註冊 service
    mNsdManager.registerService(
            serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}

為了避免你的 service 使用的 port 發生衝突,我們要取得一個可用 port
如下例是設定 port 為 0 讓 socket 可以使用任意可用的 port
public void initializeServerSocket() {
    // 初始化 server socket 於下個可用 port
    mServerSocket = new ServerSocket(0);
    // 儲存被選到的 port
    mLocalPort =  mServerSocket.getLocalPort();
    ...
}

在 registerService 前我們必須實作 RegistrationListener 以接收註冊 service 成功與否的訊息
因為 registerService 是非同步的,所以 service 建立後才要做的事情必須放在 onServiceRegistered 裡
public void initializeRegistrationListener() {
    mRegistrationListener = new NsdManager.RegistrationListener() {
        @Override
        public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
            // 儲存 service name. 因為名稱有可能與原本設定的不同所以這裡要更新為實際名稱
            mServiceName = NsdServiceInfo.getServiceName();
        }
        @Override
        public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // 註冊失敗! 依錯誤碼進行處理
        }
        @Override
        public void onServiceUnregistered(NsdServiceInfo arg0) {
            // Service 反註冊。在我們呼叫 NsdManager.unregisterService() 後才可能被觸發
        }
        @Override
        public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // Service 反註冊失敗。依錯誤碼進行處理
        }
    };
}

* Discover Services on the Network

Service discovery 跟 service registration 一樣有兩個步驟:
建立 discovery 的 listener 跟 callback 然後呼叫非同步執行的 discoverServices()
public void initializeDiscoveryListener() {

    // new 一個 DiscoveryListener 實體
    mDiscoveryListener = new NsdManager.DiscoveryListener() {
        //  Discovery 開始時就會被呼叫
        @Override
        public void onDiscoveryStarted(String regType) {
            Log.d(TAG, "Service discovery started");
        }
        @Override
        public void onServiceFound(NsdServiceInfo service) {
            // 發現 Service
            Log.d(TAG, "Service discovery success" + service);
            if (!service.getServiceType().equals(SERVICE_TYPE)) {
                // 還記得嗎?Service type 是包含 protocol 跟 transport layer 的字串
                Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
            } else if (service.getServiceName().equals(mServiceName)) {
                // service 名稱
                Log.d(TAG, "Same machine: " + mServiceName);
            } else if (service.getServiceName().contains("NsdChat")){
                mNsdManager.resolveService(service, mResolveListener);
            }
        }
        @Override
        public void onServiceLost(NsdServiceInfo service) {
            // 當網路失效
            Log.e(TAG, "service lost" + service);
        }
        @Override
        public void onDiscoveryStopped(String serviceType) {
            Log.i(TAG, "Discovery stopped: " + serviceType);
        }
        @Override
        public void onStartDiscoveryFailed(String serviceType, int errorCode) {
            Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            mNsdManager.stopServiceDiscovery(this);
        }
        @Override
        public void onStopDiscoveryFailed(String serviceType, int errorCode) {
            Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            mNsdManager.stopServiceDiscovery(this);
        }
    };
}
NSD API 透過 DiscoveryListener 去通知你 discovery 的狀態
注意上例在找到 service 時進行了一些檢查:
1. 找到的 service name 必須與 local 的 service name 比對以確定裝置是否接收到自己的 broadcast
2. service type 會被檢查以驗證你的應用程式可以連接
3. service name 會被檢查以確定連接到正確的應用程式
然而檢查 service name 並不是必要的,例如應用程式可能只想要連接到其他裝置上相同應用程式的實體
(像網路印表機就只需要檢查service type 是 "_ipp._tcp")

設定好 DiscoveryListener 後就可以呼叫 discoverServices()
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

* Connect to Services on the Network

當找到可以連接的網路服務,首先要用 resolveService() 判讀連線資訊
我們必須實作 NsdManager.ResolveListener 接收結果並取得包含連線資訊的 NsdServiceInfo
public void initializeResolveListener() {
    mResolveListener = new NsdManager.ResolveListener() {

        @Override
        public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // 解析失敗。使用錯誤碼進行除錯
            Log.e(TAG, "Resolve failed" + errorCode);
        }

        @Override
        public void onServiceResolved(NsdServiceInfo serviceInfo) {
            Log.e(TAG, "Resolve Succeeded. " + serviceInfo);

            if (serviceInfo.getServiceName().equals(mServiceName)) {
                Log.d(TAG, "Same IP.");
                return;
            }
            //
            mService = serviceInfo;
            int port = mService.getPort();
            InetAddress host = mService.getHost();
        }
    };
}

* Unregister Your Service on Application Close

在應用程式的生命週期裡適當的啟用和停用 NSD 功能是很重要的
當應用程式關閉時將 NSD 反註冊有助於防止其他應用程式認為它仍在活躍中並嘗試連接它
而 service discovery 是很耗效能的操作,所以應該在 activity pause 的時候停止、在 resume 時重新啟用
//In your application's Activity
    @Override
    protected void onPause() {
        if (mNsdHelper != null) {
            mNsdHelper.tearDown();
        }
        super.onPause();
    }
    @Override
    protected void onResume() {
        super.onResume();
        if (mNsdHelper != null) {
            mNsdHelper.registerService(mConnection.getLocalPort());
            mNsdHelper.discoverServices();
        }
    }
    @Override
    protected void onDestroy() {
        mNsdHelper.tearDown();
        mConnection.tearDown();
        super.onDestroy();
    }
    // NsdHelper's tearDown method
        public void tearDown() {
        mNsdManager.unregisterService(mRegistrationListener);
        mNsdManager.stopServiceDiscovery(mDiscoveryListener);
    }

相關資料:

Connecting Devices Wirelessly
YouTube-DevBytes: Network Service Discovery
Using Network Service Discovery

沒有留言: