Sử dụng chức năng khám phá dịch vụ mạng

Tính năng Khám phá dịch vụ mạng (NSD) cấp cho ứng dụng của bạn quyền truy cập vào các dịch vụ mà trên mạng cục bộ. Các thiết bị hỗ trợ NSD bao gồm máy in, webcam, máy chủ HTTPS và các thiết bị di động khác.

NSD triển khai cơ chế Khám phá dịch vụ dựa trên DNS (DNS-SD), cơ chế này cho phép ứng dụng của bạn yêu cầu các dịch vụ bằng cách chỉ định một loại dịch vụ và tên của một thực thể thiết bị cung cấp loại dịch vụ mong muốn. DNS-SD là được hỗ trợ cả trên Android và trên các nền tảng di động khác.

Việc thêm NSD vào ứng dụng cho phép người dùng xác định các thiết bị khác trên mạng cục bộ hỗ trợ các dịch vụ mà ứng dụng của bạn yêu cầu. Điều này hữu ích cho nhiều ứng dụng ngang hàng, chẳng hạn như chia sẻ tệp hoặc nhiều người chơi trò chơi. API NSD của Android giúp bạn dễ dàng triển khai hơn các tính năng như vậy.

Bài học này sẽ hướng dẫn bạn cách xây dựng một ứng dụng có thể truyền tên và thông tin kết nối đến mạng cục bộ và quét tìm thông tin từ các ứng dụng khác cũng làm tương tự. Cuối cùng, bài học này sẽ hướng dẫn bạn cách để kết nối với cùng một ứng dụng chạy trên thiết bị khác.

Đăng ký dịch vụ của bạn trên mạng

Lưu ý: Đây là bước không bắt buộc. Nếu bạn không quan tâm đến việc phát sóng các dịch vụ của ứng dụng qua mạng cục bộ, bạn có thể chuyển tới phần tiếp theo, Khám phá dịch vụ trên mạng.

Để đăng ký dịch vụ của bạn trên mạng cục bộ, trước tiên hãy tạo một đối tượng NsdServiceInfo. Đối tượng này cung cấp thông tin mà các thiết bị khác trên mạng sử dụng khi chúng quyết định có kết nối với .

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

Đoạn mã này đặt tên dịch vụ thành "NsdChat". Tên dịch vụ là tên thực thể: đây là tên mà các thiết bị khác trên mạng có thể nhìn thấy. Mọi thiết bị trên mạng đang dùng NSD để tìm tên này đều có thể nhìn thấy tên này dịch vụ địa phương. Xin lưu ý rằng tên này phải là duy nhất cho bất kỳ dịch vụ nào trên và Android sẽ tự động xử lý việc giải quyết xung đột. Nếu hai thiết bị trên mạng đều đã cài đặt ứng dụng NsdChat, một trong hai họ sẽ tự động thay đổi tên dịch vụ, thành một tên giống như "NsdChat (1)".

Tham số thứ hai thiết lập loại dịch vụ, chỉ định giao thức và truyền tải nào lớp mà ứng dụng sử dụng. Cú pháp là "_<giao thức>._<transportlayer>". Trong thì dịch vụ sử dụng giao thức HTTP chạy trên TCP. Một ứng dụng cung cấp dịch vụ máy in (ví dụ: máy in mạng) sẽ đặt giá trị thành "_ipp._tcp".

Lưu ý: Số được chỉ định quốc tế Cơ quan cấp chứng nhận (IANA) quản lý một danh sách có thẩm quyền gồm các loại dịch vụ mà các giao thức khám phá dịch vụ như NSD và Bonjour sử dụng. Bạn có thể tải danh sách này xuống từ Danh sách tên dịch vụ và số cổng của IANA. Nếu có ý định sử dụng một loại dịch vụ mới, bạn nên đặt trước bằng cách điền vào Cổng và dịch vụ IANA biểu mẫu đăng ký.

Khi đặt cổng cho dịch vụ của bạn, tránh mã cứng cổng đó xung đột với các ứng dụng khác. Ví dụ: giả sử ứng dụng của bạn luôn sử dụng cổng 1337, có khả năng gây xung đột với các ứng dụng đã cài đặt khác sử dụng cùng một cổng. Thay vào đó, hãy sử dụng cổng có sẵn tiếp theo. Bởi vì thông tin này được cung cấp cho các ứng dụng khác bởi truyền phát dịch vụ, bạn không cần cổng mà ứng dụng của bạn sử dụng mà các ứng dụng khác biết vào thời gian biên dịch. Thay vào đó, ứng dụng có thể lấy thông tin này từ nội dung phát sóng dịch vụ của bạn, ngay trước khi kết nối với .

Nếu bạn đang làm việc với ổ cắm, dưới đây là cách bạn có thể khởi chạy ổ cắm cho bất kỳ cổng có sẵn chỉ cần đặt là 0.

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();
    ...
}

Bây giờ, khi đã xác định được đối tượng NsdServiceInfo, bạn cần triển khai giao diện RegistrationListener. Chiến dịch này chứa các lệnh gọi lại mà Android sử dụng để cảnh báo cho ứng dụng của bạn về đăng ký và huỷ đăng ký dịch vụ thành công hoặc không thành công.

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.
        }
    };
}

Bây giờ, bạn đã có tất cả các thông tin để đăng ký dịch vụ. Gọi phương thức registerService().

Lưu ý rằng phương thức này không đồng bộ, vì vậy, bất kỳ mã nào cần phải chạy sau khi đăng ký dịch vụ, bạn phải sử dụng phương thức onServiceRegistered().

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

Khám phá các dịch vụ trên mạng

Mạng lưới luôn đầy ắp cuộc sống, từ những máy in khổng lồ cho đến webcam mạng lưới an toàn, trước những trận chiến khốc liệt, ác liệt của những trò chơi cờ bàn ở gần người chơi. Chìa khóa để cho phép ứng dụng của bạn thấy được hệ sinh thái sôi động này là khám phá dịch vụ. Ứng dụng của bạn cần theo dõi dịch vụ thông báo truyền tin trên mạng để xem dịch vụ nào có sẵn và lọc ra bất kỳ thứ gì mà ứng dụng không thể làm việc.

Quá trình khám phá dịch vụ, chẳng hạn như đăng ký dịch vụ, có 2 bước: thiết lập một trình nghe khám phá với các lệnh gọi lại có liên quan và thực hiện một lệnh gọi lại không đồng bộ Lệnh gọi API đến discoverServices().

Trước tiên, hãy tạo thực thể cho một lớp ẩn danh sẽ triển khai NsdManager.DiscoveryListener. Đoạn mã sau đây cho thấy ví dụ đơn giản:

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

API NSD sử dụng các phương thức trong giao diện này để thông báo cho ứng dụng khi phát hiện thấy được bắt đầu khi dịch vụ không hoạt động, khi dịch vụ không hoạt động cũng như khi dịch vụ được tìm thấy và bị mất (bị mất có nghĩa là không sử dụng được nữa"). Lưu ý rằng đoạn mã này thực hiện một số bước kiểm tra khi tìm thấy dịch vụ.

  1. Tên dịch vụ của dịch vụ tìm được sẽ được so sánh với dịch vụ tên của dịch vụ cục bộ để xác định xem thiết bị có tự nhận nội dung hay không truyền tin (hợp lệ).
  2. Loại dịch vụ sẽ được đánh dấu để xác minh rằng đó là loại dịch vụ ứng dụng có thể kết nối.
  3. Tên dịch vụ được kiểm tra để xác minh kết nối đến dịch vụ chính xác .

Việc kiểm tra tên dịch vụ không phải lúc nào cũng cần thiết và chỉ cần thiết nếu bạn muốn kết nối với một ứng dụng cụ thể. Ví dụ: ứng dụng có thể chỉ muốn kết nối với các thực thể của chính nó chạy trên thiết bị khác. Tuy nhiên, nếu muốn kết nối với một máy in mạng thì chỉ cần thấy rằng loại dịch vụ là "_ipp._tcp".

Sau khi thiết lập trình nghe, hãy gọi discoverServices(), truyền vào loại dịch vụ mà ứng dụng của bạn sẽ tìm kiếm, giao thức khám phá cần sử dụng và trình nghe bạn vừa tạo.

Kotlin

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

Java

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

Kết nối với các dịch vụ trên mạng

Khi tìm thấy một dịch vụ trên mạng để kết nối, ứng dụng của bạn trước tiên phải xác định thông tin kết nối cho dịch vụ đó, sử dụng resolveService(). Triển khai NsdManager.ResolveListener để truyền vào dữ liệu này và sử dụng phương thức này để lấy NsdServiceInfo chứa thông tin kết nối.

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

Sau khi dịch vụ được giải quyết, ứng dụng của bạn sẽ nhận được thông tin chi tiết thông tin dịch vụ, bao gồm địa chỉ IP và số cổng. Đây là tất cả bạn cần tạo kết nối mạng của riêng mình với dịch vụ.

Đóng ứng dụng để huỷ đăng ký dịch vụ

Bạn cần phải bật và tắt NSD hoạt động một cách thích hợp trong quá trình vòng đời. Việc huỷ đăng ký ứng dụng khi ứng dụng đóng sẽ giúp ngăn chặn các ứng dụng khác không cho rằng thiết bị đó vẫn đang hoạt động và cố gắng kết nối đến nó. Ngoài ra, khám phá dịch vụ là một thao tác tốn kém và nên được dừng lại khi Hoạt động gốc bị tạm dừng và được bật lại khi Hoạt động Đã tiếp tục. Ghi đè các phương thức vòng đời của Hoạt động chính và chèn mã để bắt đầu và dừng phát và khám phá dịch vụ khi phù hợp.

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