Netzwerkdiensterkennung verwenden

Mit der Netzwerkdiensterkennung (Network Service Discovery, NSD) erhält Ihre Anwendung Zugriff auf Dienste, die andere Geräte in einem lokalen Netzwerk bereitstellen. Zu den Geräten, die NSD unterstützen, gehören Drucker, Webcams, HTTPS-Server und andere Mobilgeräte.

NSD implementiert den DNS-basierten Service Discovery-Mechanismus (DNS-SD), mit dem deine Anwendung Dienste anfordern kann. Dazu wird ein Diensttyp und der Name einer Geräteinstanz angegeben, die den gewünschten Diensttyp bereitstellt. DNS-SD wird sowohl unter Android als auch auf anderen mobilen Plattformen unterstützt.

Wenn Sie Ihrer Anwendung NSD hinzufügen, können Ihre Nutzer andere Geräte im lokalen Netzwerk identifizieren, die die Dienste unterstützen, die von Ihrer Anwendung angefordert werden. Dies ist nützlich für eine Vielzahl von Peer-to-Peer-Anwendungen wie die Dateifreigabe oder Multiplayer-Spiele. Die NSD APIs von Android vereinfachen die Implementierung solcher Funktionen.

In dieser Lektion erfahren Sie, wie Sie eine Anwendung erstellen, die Name- und Verbindungsinformationen an das lokale Netzwerk übertragen und nach Informationen von anderen Anwendungen sucht, die dies ebenfalls tun. In dieser Lektion erfahren Sie, wie Sie eine Verbindung zur selben Anwendung herstellen, die auf einem anderen Gerät ausgeführt wird.

Dienst im Netzwerk registrieren

Hinweis : Dieser Schritt ist optional. Wenn Sie die Dienste Ihrer Anwendung nicht über das lokale Netzwerk übertragen möchten, können Sie mit dem nächsten Abschnitt Dienste im Netzwerk finden fortfahren.

Erstellen Sie zuerst ein NsdServiceInfo-Objekt, um Ihren Dienst im lokalen Netzwerk zu registrieren. Dieses Objekt stellt die Informationen bereit, die andere Geräte im Netzwerk verwenden, wenn sie entscheiden, ob sie eine Verbindung zu Ihrem Dienst herstellen möchten.

Kotlin

fun registerService(port: Int) {
    // Create the NsdServiceInfo object, and populate it.
    val serviceInfo = NsdServiceInfo().apply {
        // The name is subject to change based on conflicts
        // with other services advertised on the same network.
        serviceName = "NsdChat"
        serviceType = "_nsdchat._tcp"
        setPort(port)
        ...
    }
}

Java

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("_nsdchat._tcp");
    serviceInfo.setPort(port);
    ...
}

Mit diesem Code-Snippet wird der Dienstname auf „NsdChat“ festgelegt. Der Dienstname ist der Instanzname. Er ist für andere Geräte im Netzwerk sichtbar. Der Name ist für jedes Gerät im Netzwerk sichtbar, das NSD für die Suche nach lokalen Diensten verwendet. Beachten Sie, dass der Name für jeden Dienst im Netzwerk eindeutig sein muss und dass die Konfliktlösung automatisch von Android behoben wird. Wenn auf zwei Geräten im Netzwerk auf beiden Geräten die NsdChat-Anwendung installiert ist, ändert eines davon den Dienstnamen automatisch in "NsdChat (1)".

Der zweite Parameter legt den Diensttyp fest und gibt an, welche Protokoll- und Transportschicht die Anwendung verwendet. Die Syntax lautet "_<protocol>._<transportlayer>". Im Code-Snippet verwendet der Dienst das HTTP-Protokoll, das über TCP ausgeführt wird. Eine Anwendung, die einen Druckerdienst anbietet (z. B. einen Netzwerkdrucker), würde den Diensttyp auf "_ipp._tcp" setzen.

Hinweis : Die International Assigned Numbers Authority (IANA) verwaltet eine zentrale, verbindliche Liste von Diensttypen, die von Diensterkennungsprotokollen wie NSD und Bonjour verwendet werden. Sie können die Liste aus der IANA-Liste der Dienstnamen und Portnummern herunterladen. Wenn Sie einen neuen Diensttyp verwenden möchten, sollten Sie ihn reservieren. Dazu füllen Sie das Anmeldeformular für die IANA-Ports und Dienste aus.

Vermeiden Sie beim Festlegen des Ports für Ihren Dienst eine Hartcodierung, da dies zu Konflikten mit anderen Anwendungen führt. Wenn Sie zum Beispiel davon ausgehen, dass Ihre Anwendung immer Port 1337 verwendet, kann dies zu einem Konflikt mit anderen installierten Anwendungen führen, die denselben Port verwenden. Verwenden Sie stattdessen den nächsten verfügbaren Port des Geräts. Da diese Informationen über einen Dienst-Broadcast anderen Anwendungen bereitgestellt werden, muss der von Ihrer Anwendung verwendete Port bei der Kompilierung den anderen Anwendungen nicht bekannt sein. Stattdessen können die Anwendungen diese Informationen vom Dienst-Broadcast abrufen, bevor sie eine Verbindung zu Ihrem Dienst herstellen.

Wenn Sie mit Sockets arbeiten, können Sie einen Socket für jeden verfügbaren Port initialisieren, indem Sie ihn einfach auf 0 setzen.

Kotlin

fun initializeServerSocket() {
    // Initialize a server socket on the next available port.
    serverSocket = ServerSocket(0).also { socket ->
        // Store the chosen port.
        mLocalPort = socket.localPort
        ...
    }
}

Java

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

    // Store the chosen port.
    localPort = serverSocket.getLocalPort();
    ...
}

Nachdem Sie das NsdServiceInfo-Objekt definiert haben, müssen Sie die RegistrationListener-Schnittstelle implementieren. Diese Schnittstelle enthält Callbacks, die von Android verwendet werden, um Ihre App über Erfolg oder Fehler bei der Registrierung oder Aufhebung der Registrierung zu informieren.

Kotlin

private val registrationListener = object : NsdManager.RegistrationListener {

    override fun 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.serviceName
    }

    override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
        // Registration failed! Put debugging code here to determine why.
    }

    override fun onServiceUnregistered(arg0: NsdServiceInfo) {
        // Service has been unregistered. This only happens when you call
        // NsdManager.unregisterService() and pass in this listener.
    }

    override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
        // Unregistration failed. Put debugging code here to determine why.
    }
}

Java

public void initializeRegistrationListener() {
    registrationListener = 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.
            serviceName = 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.
        }
    };
}

Jetzt haben Sie alle Bestandteile für die Registrierung Ihres Dienstes. Rufen Sie die Methode registerService() auf.

Diese Methode ist asynchron. Code, der nach der Registrierung des Dienstes ausgeführt werden muss, muss also in die onServiceRegistered()-Methode eingefügt werden.

Kotlin

fun registerService(port: Int) {
    // Create the NsdServiceInfo object, and populate it.
    val serviceInfo = NsdServiceInfo().apply {
        // The name is subject to change based on conflicts
        // with other services advertised on the same network.
        serviceName = "NsdChat"
        serviceType = "_nsdchat._tcp"
        setPort(port)
    }

    nsdManager = (getSystemService(Context.NSD_SERVICE) as NsdManager).apply {
        registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener)
    }
}

Java

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

    nsdManager = Context.getSystemService(Context.NSD_SERVICE);

    nsdManager.registerService(
            serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
}

Dienste im Netzwerk finden

Das Netzwerk wimmelt von Leben, von den faszinierenden Netzwerkdruckern über die sanftmütigen Netzwerk-Webcams bis zu den brutalen, heftigen Schlachten von Tic-Tac-Toe-Spielern in der Nähe. Service Discovery ist entscheidend, damit Ihre Anwendung diese vielseitigen Funktionen nutzen kann. Ihre Anwendung muss auf Dienstübertragungen im Netzwerk warten, um zu sehen, welche Dienste verfügbar sind, und alles herauszufiltern, mit dem die Anwendung nicht kompatibel ist.

Die Diensterkennung umfasst wie die Dienstregistrierung zwei Schritte: Einrichtung eines Erkennungs-Listeners mit den relevanten Callbacks und Ausführung eines einzelnen asynchronen API-Aufrufs an discoverServices().

Instanziieren Sie zuerst eine anonyme Klasse, die NsdManager.DiscoveryListener implementiert. Das folgende Snippet zeigt ein einfaches Beispiel:

Kotlin

// Instantiate a new DiscoveryListener
private val discoveryListener = object : NsdManager.DiscoveryListener {

    // Called as soon as service discovery begins.
    override fun onDiscoveryStarted(regType: String) {
        Log.d(TAG, "Service discovery started")
    }

    override fun onServiceFound(service: NsdServiceInfo) {
        // A service was found! Do something with it.
        Log.d(TAG, "Service discovery success$service")
        when {
            service.serviceType != SERVICE_TYPE -> // Service type is the string containing the protocol and
                // transport layer for this service.
                Log.d(TAG, "Unknown Service Type: ${service.serviceType}")
            service.serviceName == 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")
            service.serviceName.contains("NsdChat") -> nsdManager.resolveService(service, resolveListener)
        }
    }

    override fun onServiceLost(service: NsdServiceInfo) {
        // When the network service is no longer available.
        // Internal bookkeeping code goes here.
        Log.e(TAG, "service lost: $service")
    }

    override fun onDiscoveryStopped(serviceType: String) {
        Log.i(TAG, "Discovery stopped: $serviceType")
    }

    override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
        Log.e(TAG, "Discovery failed: Error code:$errorCode")
        nsdManager.stopServiceDiscovery(this)
    }

    override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
        Log.e(TAG, "Discovery failed: Error code:$errorCode")
        nsdManager.stopServiceDiscovery(this)
    }
}

Java

public void initializeDiscoveryListener() {

    // Instantiate a new DiscoveryListener
    discoveryListener = 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(serviceName)) {
                // 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: " + serviceName);
            } else if (service.getServiceName().contains("NsdChat")){
                nsdManager.resolveService(service, resolveListener);
            }
        }

        @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);
            nsdManager.stopServiceDiscovery(this);
        }

        @Override
        public void onStopDiscoveryFailed(String serviceType, int errorCode) {
            Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            nsdManager.stopServiceDiscovery(this);
        }
    };
}

Die NSD API verwendet die Methoden in dieser Schnittstelle, um Ihre Anwendung darüber zu informieren, wann die Erkennung gestartet wird, wann sie fehlschlägt und wann Dienste gefunden und verloren werden (verloren bedeutet, dass sie nicht mehr verfügbar ist). Dieses Snippet führt mehrere Prüfungen durch, wenn ein Dienst gefunden wird.

  1. Der Dienstname des gefundenen Dienstes wird mit dem Dienstnamen des lokalen Dienstes verglichen, um festzustellen, ob das Gerät gerade eine eigene Broadcasting-Übertragung empfangen hat (die gültig ist).
  2. Der Diensttyp ist markiert, um zu bestätigen, dass es sich um einen Diensttyp handelt, zu dem Ihre Anwendung eine Verbindung herstellen kann.
  3. Der Dienstname wird überprüft, um die Verbindung zur richtigen Anwendung zu verifizieren.

Das Prüfen des Dienstnamens ist nicht immer erforderlich und nur relevant, wenn Sie eine Verbindung zu einer bestimmten Anwendung herstellen möchten. Beispielsweise möchte die Anwendung möglicherweise nur eine Verbindung zu eigenen Instanzen herstellen, die auf anderen Geräten ausgeführt werden. Wenn die Anwendung jedoch eine Verbindung zu einem Netzwerkdrucker herstellen möchte, reicht es aus, zu sehen, dass der Diensttyp „_ipp._tcp“ ist.

Rufen Sie nach dem Einrichten des Listeners discoverServices() auf und übergeben Sie den Diensttyp, nach dem Ihre Anwendung suchen soll, das zu verwendende Erkennungsprotokoll und den soeben erstellten Listener.

Kotlin

nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)

Java

nsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);

Verbindung zu Diensten im Netzwerk herstellen

Wenn die Anwendung im Netzwerk einen Dienst findet, zu dem sie eine Verbindung herstellen kann, muss sie zuerst mit der Methode resolveService() die Verbindungsinformationen für diesen Dienst ermitteln. Implementieren Sie einen NsdManager.ResolveListener, der an diese Methode übergeben wird, und rufen Sie damit ein NsdServiceInfo mit den Verbindungsinformationen ab.

Kotlin

private val resolveListener = object : NsdManager.ResolveListener {

    override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
        // Called when the resolve fails. Use the error code to debug.
        Log.e(TAG, "Resolve failed: $errorCode")
    }

    override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
        Log.e(TAG, "Resolve Succeeded. $serviceInfo")

        if (serviceInfo.serviceName == mServiceName) {
            Log.d(TAG, "Same IP.")
            return
        }
        mService = serviceInfo
        val port: Int = serviceInfo.port
        val host: InetAddress = serviceInfo.host
    }
}

Java

public void initializeResolveListener() {
    resolveListener = 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(serviceName)) {
                Log.d(TAG, "Same IP.");
                return;
            }
            mService = serviceInfo;
            int port = mService.getPort();
            InetAddress host = mService.getHost();
        }
    };
}

Sobald der Dienst aufgelöst ist, erhält Ihre Anwendung detaillierte Dienstinformationen, einschließlich einer IP-Adresse und Portnummer. Dies ist alles, was Sie benötigen, um Ihre eigene Netzwerkverbindung zum Dienst herzustellen.

Dienstregistrierung beim Schließen der Anwendung aufheben

Es ist wichtig, die NSD-Funktionalität während des Lebenszyklus der Anwendung entsprechend zu aktivieren und zu deaktivieren. Wenn Sie die Registrierung Ihrer Anwendung beim Beenden aufheben, verhindern Sie, dass andere Anwendungen sie als noch aktiv wahrnehmen und versuchen, eine Verbindung zu ihr herzustellen. Außerdem ist die Diensterkennung ein teurer Vorgang und sollte beendet werden, wenn die übergeordnete Aktivität pausiert, und wieder aktiviert werden, wenn die Aktivität fortgesetzt wird. Überschreiben Sie die Lebenszyklusmethoden Ihrer Hauptaktivität und fügen Sie Code ein, um die Dienstübertragung und -erkennung entsprechend zu starten und zu stoppen.

Kotlin

    // In your application's Activity

    override fun onPause() {
        nsdHelper?.tearDown()
        super.onPause()
    }

    override fun onResume() {
        super.onResume()
        nsdHelper?.apply {
            registerService(connection.localPort)
            discoverServices()
        }
    }

    override fun onDestroy() {
        nsdHelper?.tearDown()
        connection.tearDown()
        super.onDestroy()
    }

    // NsdHelper's tearDown method
    fun tearDown() {
        nsdManager.apply {
            unregisterService(registrationListener)
            stopServiceDiscovery(discoveryListener)
        }
    }

Java

    // In your application's Activity

    @Override
    protected void onPause() {
        if (nsdHelper != null) {
            nsdHelper.tearDown();
        }
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (nsdHelper != null) {
            nsdHelper.registerService(connection.getLocalPort());
            nsdHelper.discoverServices();
        }
    }

    @Override
    protected void onDestroy() {
        nsdHelper.tearDown();
        connection.tearDown();
        super.onDestroy();
    }

    // NsdHelper's tearDown method
    public void tearDown() {
        nsdManager.unregisterService(registrationListener);
        nsdManager.stopServiceDiscovery(discoveryListener);
    }