ใช้การสำรวจบริการเครือข่าย

Network Service Discovery (NSD) ทำให้แอปของคุณเข้าถึงบริการที่ผู้อื่น ในเครือข่ายภายใน อุปกรณ์ที่รองรับ NSD รวมถึงเครื่องพิมพ์ เว็บแคม เซิร์ฟเวอร์ HTTPS และอุปกรณ์เคลื่อนที่อื่นๆ

NSD นำกลไก Service Discovery (DNS-SD) ที่ใช้ DNS ซึ่ง อนุญาตให้แอปขอบริการด้วยการระบุประเภทบริการและชื่อ ของอินสแตนซ์อุปกรณ์ที่ให้บริการประเภทที่ต้องการ DNS-SD คือ ทั้งบน Android และ แพลตฟอร์มมือถืออื่นๆ

การเพิ่ม NSD ลงในแอปของคุณจะช่วยให้ผู้ใช้สามารถระบุอุปกรณ์อื่นๆ บน เครือข่ายท้องถิ่นที่รองรับบริการที่แอปของคุณร้องขอ ซึ่งมีประโยชน์สำหรับ แอปพลิเคชันมากมายระหว่างเครื่องผู้ใช้ เช่น การแชร์ไฟล์หรือผู้เล่นหลายคน การเล่นเกม NSD API ของ Android ช่วยให้ติดตั้งใช้งานได้ง่ายขึ้น ฟีเจอร์ดังกล่าว

บทเรียนนี้แสดงวิธีสร้างแอปพลิเคชันที่สามารถเผยแพร่ ชื่อและข้อมูลการเชื่อมต่อกับเครือข่าย LAN และสแกนหาข้อมูล จากแอปพลิเคชันอื่นๆ ที่กระทำเช่นเดียวกัน สุดท้ายนี้ บทเรียนนี้จะแสดงวิธีการ เพื่อเชื่อมต่อกับแอปพลิเคชันเดียวกันที่ทำงานอยู่บนอุปกรณ์อีกเครื่องหนึ่ง

ลงทะเบียนบริการบนเครือข่าย

หมายเหตุ: ขั้นตอนนี้เป็นแบบไม่บังคับ ถ้า คุณไม่สนใจการเผยแพร่บริการของแอปผ่านเครือข่าย LAN คุณสามารถข้ามไปยัง ส่วนถัดไปคือค้นพบบริการในเครือข่าย

หากต้องการลงทะเบียนบริการในเครือข่ายภายใน ให้สร้างออบเจ็กต์ NsdServiceInfo ก่อน ออบเจ็กต์นี้ให้ข้อมูล ที่อุปกรณ์อื่นๆ ในเครือข่ายใช้เมื่อตัดสินใจว่าจะเชื่อมต่อกับเครือข่ายเดียวกันหรือไม่ service.

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

ข้อมูลโค้ดนี้จะตั้งชื่อบริการเป็น "NsdChat" ชื่อบริการ คือชื่ออินสแตนซ์ ซึ่งเป็นชื่อที่อุปกรณ์อื่นๆ ในเครือข่ายจะเห็น อุปกรณ์ในเครือข่ายที่ใช้ NSD ในการค้นหาชื่อนี้จะเห็นชื่อนี้ บริการในพื้นที่ โปรดทราบว่าชื่อต้องไม่ซ้ำกันสำหรับบริการใดๆ ใน เครือข่าย และ Android จะจัดการแก้ไขข้อขัดแย้งโดยอัตโนมัติ ถ้า อุปกรณ์ทั้งสองในเครือข่าย ทั้งสองเครื่องมี แอปพลิเคชัน NsdChat ติดตั้งไว้ หนึ่งใน จะเปลี่ยนชื่อบริการโดยอัตโนมัติ เป็นชื่อเช่น "NsdChat (1)"

พารามิเตอร์ที่ 2 จะกำหนดประเภทบริการ โดยระบุว่าโปรโตคอลใดและการรับส่งข้อมูลใด ที่แอปพลิเคชันใช้ได้อีกด้วย ไวยากรณ์คือ "_<โปรโตคอล>._<transportlayer>" ใน บริการจะใช้โปรโตคอล HTTP ที่ทำงานผ่าน TCP แอปพลิเคชัน ที่เสนอบริการเครื่องพิมพ์ (เช่น เครื่องพิมพ์ในเครือข่าย) ประเภทบริการเป็น "_ipp._tcp"

หมายเหตุ: International Assigned Numbers องค์กร (IANA) เป็นผู้จัดการระบบ รายการประเภทบริการที่เชื่อถือได้ที่โปรโตคอลการสำรวจบริการใช้ เช่น NSD และ Bonjour คุณสามารถดาวน์โหลดรายการจาก รายชื่อบริการและหมายเลขพอร์ตของ IANA หากต้องการใช้บริการประเภทใหม่ คุณควรจองโดยกรอก ท่าเรือและบริการ IANA แบบฟอร์มการลงทะเบียน

เมื่อตั้งค่าพอร์ตสำหรับบริการของคุณ ให้หลีกเลี่ยงการฮาร์ดโค้ดพอร์ตแบบนี้ ขัดแย้งกับแอปพลิเคชันอื่น ตัวอย่างเช่น สมมติว่า ที่แอปพลิเคชันของคุณใช้พอร์ต 1337 เสมอ อาจทำให้เกิดข้อขัดแย้ง แอปพลิเคชันอื่นๆ ที่ติดตั้งซึ่งใช้พอร์ตเดียวกัน ให้ใช้อุปกรณ์ พอร์ตถัดไปที่ใช้ได้ เนื่องจากข้อมูลนี้ให้ข้อมูลแก่แอปอื่นๆ โดย ออกอากาศทันที จึงไม่จำเป็นต้องใช้พอร์ตที่แอปพลิเคชันใช้ ซึ่งแอปพลิเคชันอื่นรู้จักในเวลาคอมไพล์ แต่แอปพลิเคชันอาจได้รับ ข้อมูลนี้จากการเผยแพร่บริการของคุณก่อนที่จะเชื่อมต่อกับ service.

หากคุณทำงานกับ Socket ให้ดำเนินการดังนี้ พอร์ตที่พร้อมใช้งานได้ง่ายๆ โดยตั้งค่าเป็น 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();
    ...
}

เมื่อกำหนดออบเจ็กต์ NsdServiceInfo แล้ว คุณจะต้องติดตั้งใช้งานอินเทอร์เฟซ RegistrationListener ช่วงเวลานี้ ของอินเทอร์เฟซมี Callback ที่ Android ใช้เพื่อแจ้งเตือนแอปพลิเคชันของคุณ การจดทะเบียนและการยกเลิกการลงทะเบียนบริการล้มเหลวหรือสำเร็จ

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

ตอนนี้คุณก็มีข้อมูลทั้งหมดสำหรับลงทะเบียนบริการแล้ว เรียกใช้เมธอด registerService()

โปรดทราบว่าวิธีนี้เป็นแบบไม่พร้อมกัน ดังนั้นโค้ดใดๆ ก็ตามที่จำเป็นต้องเรียกใช้ หลังจากลงทะเบียนบริการแล้ว ต้องใช้เมธอด 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);
}

สำรวจบริการในเครือข่าย

เครือข่ายนี้เต็มไปด้วยชีวิตชีวา ตั้งแต่เครื่องพิมพ์เครือข่ายที่ร้ายกาจไปจนถึง เว็บแคมเครือข่ายที่ดื้อรั้น ไปจนถึงการต่อสู้อันดุเดือดอันดุเดือดของเกมโอเอกซ์ในบริเวณใกล้เคียง ผู้เล่น กุญแจสำคัญที่จะทำให้แอปพลิเคชันของคุณได้เห็นระบบนิเวศที่มีชีวิตชีวาของ ฟังก์ชันการค้นหาบริการ แอปพลิเคชันของคุณต้องฟังบริการ ออกอากาศบนเครือข่ายเพื่อดูว่ามีบริการใดบ้าง และกรองออก สิ่งต่างๆ ที่แอปพลิเคชันใช้งานไม่ได้

การสำรวจบริการมี 2 ขั้นตอนดังนี้ ตั้งค่า Listener การสำรวจด้วย Callback ที่เกี่ยวข้อง และทำให้อะซิงโครนัสรายการเดียว การเรียก API ไปยัง discoverServices()

ขั้นแรก ให้สร้างคลาสที่ไม่ระบุตัวตนซึ่งใช้งาน NsdManager.DiscoveryListener ข้อมูลโค้ดต่อไปนี้แสดง ตัวอย่างง่ายๆ:

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

NSD API ใช้วิธีการในอินเทอร์เฟซนี้เพื่อแจ้งแอปพลิเคชันของคุณเมื่อพบ เริ่มทำงาน ล้มเหลว และเมื่อพบบริการแล้วสูญหาย (หายหมายถึง ไม่พร้อมใช้งานอีกต่อไป") โปรดสังเกตว่าข้อมูลโค้ดนี้ทำการตรวจสอบหลายครั้ง เมื่อพบบริการ

  1. ชื่อบริการของบริการที่พบถูกเปรียบเทียบกับบริการ ชื่อของบริการในท้องถิ่นเพื่อพิจารณาว่าอุปกรณ์เพิ่งได้รับอุปกรณ์มาเองหรือไม่ ออกอากาศ (ซึ่งถูกต้อง)
  2. มีการทำเครื่องหมายประเภทบริการ เพื่อยืนยันว่าเป็นประเภทบริการ ที่เชื่อมต่อได้
  3. ระบบจะเลือกชื่อบริการเพื่อยืนยันการเชื่อมต่อกับที่ถูกต้อง แอปพลิเคชัน

การตรวจสอบชื่อบริการไม่จำเป็นเสมอไปและจะใช้เฉพาะกรณี ต้องการเชื่อมต่อกับแอปพลิเคชันที่เฉพาะเจาะจง ตัวอย่างเช่น แอปพลิเคชันอาจ ต้องการเชื่อมต่อกับอินสแตนซ์ของตัวแอปเองที่ทำงานในอุปกรณ์อื่นเท่านั้น อย่างไรก็ตาม หาก แอปพลิเคชันต้องการจะเชื่อมต่อกับเครื่องพิมพ์เครือข่าย ทำให้เห็นได้ว่าบริการประเภทนี้ คือ "_ipp._tcp"

หลังจากตั้งค่า Listener แล้ว ให้เรียก discoverServices() ผ่านประเภทบริการ โปรโตคอลการค้นหาที่จะใช้ และ Listener ที่คุณเพิ่งสร้างขึ้น

Kotlin

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

Java

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

เชื่อมต่อกับบริการในเครือข่าย

เมื่อแอปพลิเคชันพบบริการในเครือข่ายที่ต้องการเชื่อมต่อ ต้องระบุข้อมูลการเชื่อมต่อสำหรับบริการนั้นก่อน โดยใช้ resolveService() วิธี ใช้ NsdManager.ResolveListener เพื่อส่งผ่านข้อมูลนี้ และใช้เพื่อรับ NsdServiceInfo ที่มี ข้อมูลการเชื่อมต่อ

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

เมื่อแก้ไขปัญหาเรียบร้อยแล้ว แอปพลิเคชันของคุณจะได้รับรายละเอียด ข้อมูลบริการ ซึ่งรวมถึงที่อยู่ IP และหมายเลขพอร์ต สำคัญที่สุด คุณต้องสร้างการเชื่อมต่อเครือข่ายของคุณเองกับบริการ

ยกเลิกการลงทะเบียนบริการในแอปพลิเคชัน

คุณต้องเปิดและปิดใช้ NSD ฟังก์ชันการทำงานที่เหมาะสมในระหว่างการใช้งาน ใหม่ การยกเลิกการลงทะเบียนแอปพลิเคชันเมื่อปิดลงจะช่วยป้องกัน จากแอปพลิเคชันอื่น ๆ ที่คิดว่าแอปพลิเคชันนั้นทำงานอยู่และพยายามที่จะเชื่อมต่อกับ ได้ นอกจากนี้ การสำรวจบริการก็เป็นการดำเนินการที่มีค่าใช้จ่ายสูง และควรหยุดใช้ เมื่อกิจกรรมของผู้ปกครองหยุดชั่วคราว และเปิดใช้อีกครั้งเมื่อกิจกรรมนั้น ทำงานต่อ ลบล้างวิธีการใช้วงจรของกิจกรรมหลักและแทรกโค้ด เพื่อเริ่มและหยุดการออกอากาศและการค้นพบบริการตามความเหมาะสม

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