Использование обнаружения сетевых сервисов

Добавление Обнаружения сетевых служб (NSD) для вашего приложения позволяет пользователям определить другие устройства в локальной сети, которые поддерживают службы, необходимые вашему приложению. Это полезно для различных P2P приложений, таких как обмен файлов или многопользовательских игр. Интерфейсы Обнаружения сетевых служб Android упрощают усилия, необходимые для реализации таких функций.

Этот урок покажет вам, как создать приложение, которое может транслировать свое название и информацию о соединении с локальной сетью, и выполнять поиск информации из других приложений, делающих то же самое. А так же, этот урок покажет вам, как подключиться к этому же приложению, работающем на другом устройстве.

Регистрация вашего сервиса в сети

Примечание: Этот шаг является необязательным. Если вы не заботитесь о трансляции услуг вашего приложения в локальной сети, вы можете пропустить этот раздел и перейти вперед к следующему разделу, Обнаружение сервисов в сети.

Чтобы зарегистрировать свой сервис для локальной сети, сначала создайте NsdServiceInfo объект. Этот объект предоставляет информацию, которую другие устройства в сети используют, когда они решают подключаться ли к вашей службе.

public void registerService(int port) {
    // Create the NsdServiceInfo object, and populate it.
    NsdServiceInfo serviceInfo  = new NsdServiceInfo();

    // The name is subject to change based on conflicts
    // with other services advertised on the same network.
    serviceInfo.setServiceName("NsdChat");
    serviceInfo.setServiceType("_http._tcp.");
    serviceInfo.setPort(port);
    ....
}

Этот фрагмент кода задает имя службы "NsdChat". Это имя будет видно любому устройству в сети, которое использует NSD для поиска локальных служб. Имейте в виду, что имя должно быть уникальным для любой службы в сети, и Android автоматически обрабатывает разрешение конфликтов. Если два устройства в сети и имеют установленное NsdChat приложение, один из них изменяет имя службы автоматически, на что-то вроде "NsdChat(1)".

Второй параметр задает тип службы, определяя какой протокол и транспортный уровень использует приложение. Синтаксис "_<протокол>._<транспортный уровень>". Во фрагменте кода, служба использует протокол HTTP работающий по TCP. Приложение предлагающее услуги принтера (например, сетевой принтер) установит тип услуги в "_ipp._tcp".

Примечание: Международный администрация адресного пространства (IANA) управляет централизованным, авторитетным списоком типов услуг, используемых протоколом обнаружения услуг, таких как NSD и Bonjour. Вы можете скачать список по адресу Список IANA имен сервисов и номеров портов. Если вы собираетесь использовать новый тип услуги, вы должны зарезервировать его, заполнив Регистрационную форма служб и портов IANA.

При установке портов для служб, избегайте жесткого кодирования, так как возможны конфликты с другими приложениями. Например, если предположить, что ваше приложение всегда использует порт 1337, и это приводит к потенциальному конфликту с другим установленным приложением, которое используют тот же порт. Вместо этого, используйте следующий доступный порт на устройстве. Т.к. эта информация предоставляется другим приложениям использую широковещательную рассылку, другим приложениям нет необходимости во время компиляции знать порт, который будет использовать ваше приложение. Вместо этого, приложения могут получить эту информацию использую широковещательную рассылку о вашей службе, прямо перед подключением к вашему сервису.

Если вы работаете с сокетами, вот как можно инициализировать сокет, чтобы занять любой свободный порт, просто установив его равным 0.

public void initializeServerSocket() {
    // Initialize a server socket on the next available port.
    mServerSocket = new ServerSocket(0);

    // Store the chosen port.
    mLocalPort =  mServerSocket.getLocalPort();
    ...
}

Теперь, когда вы определили NsdServiceInfo объект, необходимо реализовать RegistrationListener интерфейс. Этот интерфейс содержит методы обратного вызова, используемые Android для извещения об успехе или неудаче регистрации услуг и отмены регистрации.

public void initializeRegistrationListener() {
    mRegistrationListener = new NsdManager.RegistrationListener() {

        @Override
        public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
            // Save the service name.  Android may have changed it in order to
            // resolve a conflict, so update the name you initially requested
            // with the name Android actually used.
            mServiceName = NsdServiceInfo.getServiceName();
        }

        @Override
        public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // Registration failed!  Put debugging code here to determine why.
        }

        @Override
        public void onServiceUnregistered(NsdServiceInfo arg0) {
            // Service has been unregistered.  This only happens when you call
            // NsdManager.unregisterService() and pass in this listener.
        }

        @Override
        public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // Unregistration failed.  Put debugging code here to determine why.
        }
    };
}

Теперь у вас есть все части, чтобы зарегистрировать ваш сервис. Вызовите метод registerService().

Обратите внимание, что этот метод является асинхронным, так что любой код, который необходимо запустить после регистрации службы необходимо выполнить в onServiceRegistered() методе.

public void registerService(int port) {
    NsdServiceInfo serviceInfo  = new NsdServiceInfo();
    serviceInfo.setServiceName("NsdChat");
    serviceInfo.setServiceType("_http._tcp.");
    serviceInfo.setPort(port);

    mNsdManager = Context.getSystemService(Context.NSD_SERVICE);

    mNsdManager.registerService(
            serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}

Обнаружение сервисов в сети

Сеть заполнена жизнью, от сетевых принтеров до сетевых веб-камер, а так же пламенными битвами игроков в крестики-нолики. Ключом к разрешению приложению видеть эту оживленную экосистему является функциональность обнаружения служб. Ваше приложения должно слушать рассылку запросов сетевых служб в сети, чтобы узнать какие службы предоставляются, и отфильтровать всё с чем приложение не может работать.

Обнаружение служб, как регистрации сервиса, имеет два шага: создание обработчика событие обнаружения на основе соответствующих обратных вызовов, и выполнение одного асинхронного вызова API discoverServices().

Во-первых, создайте экземпляр анонимного класса, реализующего NsdManager.DiscoveryListener. В следующем фрагменте показан простой пример:

public void initializeDiscoveryListener() {

    // Instantiate a new DiscoveryListener
    mDiscoveryListener = new NsdManager.DiscoveryListener() {

        //  Called as soon as service discovery begins.
        @Override
        public void onDiscoveryStarted(String regType) {
            Log.d(TAG, "Service discovery started");
        }

        @Override
        public void onServiceFound(NsdServiceInfo service) {
            // A service was found!  Do something with it.
            Log.d(TAG, "Service discovery success" + service);
            if (!service.getServiceType().equals(SERVICE_TYPE)) {
                // Service type is the string containing the protocol and
                // transport layer for this service.
                Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
            } else if (service.getServiceName().equals(mServiceName)) {
                // The name of the service tells the user what they'd be
                // connecting to. It could be "Bob's Chat App".
                Log.d(TAG, "Same machine: " + mServiceName);
            } else if (service.getServiceName().contains("NsdChat")){
                mNsdManager.resolveService(service, mResolveListener);
            }
        }

        @Override
        public void onServiceLost(NsdServiceInfo service) {
            // When the network service is no longer available.
            // Internal bookkeeping code goes here.
            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 использует методы этого интерфейса, чтобы сообщить приложению: когда начнется обнаружение, когда оно завершилось неудачно, и когда сервис был обнаружен и потерян (потерян означает "больше не доступен"). Обратите внимание, что данный фрагмент делает несколько проверок, когда сервис найден.

  1. Имя найденной службы сравнивается с именем локальной службы, чтобы определить, что устройство просто получило обратно свою же рассылку (что является корректным).
  2. Проверяется тип службы, чтобы убедиться, что приложение может подключиться к данному виду сервиса.
  3. Имя службы проверяется, чтобы убедиться, что подключаемся к нужному приложению.

Проверка имени службы не всегда необходима, и обычно делается только, если вы хотите подключиться к определенному приложению. Например, приложение может хотеть подключаться только к своим экземплярам, работающих на других устройствах. Тем не менее, если приложение хочет подключиться к сетевому принтеру, достаточно проверить, что тип услуги является "_ipp._tcp".

После настройки обработчика событий, вызовите discoverServices(), передав тип службы, которую ваше приложение должно искать, используемый протокол обнаружения, и обработчик, который вы только что создали.

    mNsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

Подключение к сервису в сети

Когда приложение находит сервис в сети для подключения, оно должно сначала получить сведения о подключении к этой службе, с помощью resolveService() метода. Реализуйте NsdManager.ResolveListener для передачи в этот метод, и используйте его для получения NsdServiceInfo , содержащего информацию о соединении.

public void initializeResolveListener() {
    mResolveListener = new NsdManager.ResolveListener() {

        @Override
        public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // Called when the resolve fails.  Use the error code to debug.
            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();
        }
    };
}

Как только служба будет определена, приложение получает подробную информацию о службе, включая IP-адрес и номер порта. Это все, что нужно, чтобы создать своё собственное сетевое подключение к службе.

Отмена регистрации вашего сервиса при закрытии приложения

Важно включать и выключать функциональность NSD в соответствующих случаях жизненного цикла приложения. Отмена регистрации вашего приложения, когда оно закрывается, помогает предотвратить желание других приложений подключиться к нему, считая что оно по-прежнему активно. Кроме того, обнаружение сервисов дорогая операция, и должна быть остановлена, когда родительская деятельность приостанавливается, и вновь включаться, когда активность возобновляется. Переопределите методы жизненного цикла вашей основной деятельности, и вставьте код для запуска и остановки вещания и обнаружение служб соответствующим образом.

//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);
    }