שימוש בגילוי שירותי רשת

גילוי שירותי רשת (NSD) מאפשר לאפליקציה שלך גישה לשירותים אחרים מספקים ברשת מקומית. מכשירים שתומכים ב-NSD כוללים מדפסות, מצלמות אינטרנט, שרתי HTTPS ומכשירים ניידים אחרים.

NSD מיישם את מנגנון Discovery Service Discovery (DNS-SD), המבוסס על DNS, מאפשרת לאפליקציה לבקש שירותים על ידי ציון סוג השירות והשם של מכשיר שמספק את סוג השירות הרצוי. DNS-SD הוא נתמכים גם ב-Android וגם בפלטפורמות אחרות לנייד.

הוספת NSD לאפליקציה שלך תאפשר למשתמשים לזהות מכשירים אחרים הרשת המקומית שתומכת בשירותים שהאפליקציה מבקשת. זה שימושי במקרה של מגוון של אפליקציות עמית-לעמית, כמו שיתוף קבצים או משחקים מרובי-משתתפים גיימינג. ממשקי ה-API של NSD ב-Android מפשטים את המאמץ שנדרש תכונות כאלה.

בשיעור הזה תלמדו כיצד לפתח אפליקציה שיכולה לשדר השם ופרטי החיבור לרשת המקומית וסריקה לקבלת מידע מאפליקציות אחרות שעושות את אותו הדבר. לבסוף, השיעור הזה מסביר איך כדי להתחבר לאותה אפליקציה שפועלת במכשיר אחר.

רישום השירות ברשת

הערה: השלב הזה הוא אופציונלי. אם המיקום לא חשוב לכם לשדר את שירותי האפליקציה ברשת המקומית, אפשר לדלג קדימה בקטע הבא, גילוי שירותים ברשת.

כדי לרשום את השירות ברשת המקומית, קודם צריך ליצור אובייקט NsdServiceInfo. האובייקט הזה מספק את המידע שמשתמשים אחרים ברשת משתמשים בהם כשהם מחליטים אם להתחבר לאחר השיפור.

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)".

הפרמטר השני מגדיר את סוג השירות, ומציין איזה פרוטוקול ותעבורה או שכבות שבה האפליקציה משתמשת. התחביר הוא "_<protocol>._<transportlayer>". ב בקטע הקוד, השירות משתמש בפרוטוקול HTTP שפועל על TCP. אפליקציה הצעת שירות מדפסות (למשל, מדפסת רשת) תגדיר את סוג השירות ל- "_ipp._tcp".

הערה: מספרן שיוקצה הבינלאומי רשות (IANA) מנהלת מערכת מרכזית, רשימה מהימנה של סוגי שירותים המשמשים את פרוטוקולים לגילוי שירותים כגון NSD ו-Bonjour. אפשר להוריד את הרשימה מ רשימת IANA של שמות שירותים ומספרי יציאות. אם אתם מתכוונים להשתמש בסוג שירות חדש, כדאי לשריין אותו על ידי מילוי היציאות והשירות של IANA טופס הרשמה.

כשמגדירים את היציאה לשירות, מומלץ להימנע מכתיבה בתוך הקוד, כי מתנגש עם אפליקציות אחרות. לדוגמה, בהנחה שהאפליקציה תמיד משתמשת ביציאה 1337, יוצרת התנגשות אפליקציות מותקנות אחרות שמשתמשות באותה יציאה. במקום זאת, היציאה הזמינה הבאה. כי המידע הזה נמסר לאפליקציות אחרות על ידי של שידור שירות, אין צורך שהיציאה שבה האפליקציה משתמשת תהיה שידוע באפליקציות אחרות בזמן הידור. במקום זאת, האפליקציות יכולות לקבל מידע זה משידור השירות, ממש לפני ההתחברות אל לאחר השיפור.

אם אתם עובדים עם שקעים, כך תוכלו לאתחל שקע לכל על ידי הגדרת הערך 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. הזה מכיל קריאות חוזרות (callbacks) שמשמשות את 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.
        }
    };
}

עכשיו יש לכם את כל מה שצריך כדי לרשום את השירות. קריאה ל-method registerService()

שימו לב שהשיטה הזו היא אסינכרונית, לכן כל קוד שצריך לרוץ אחרי שהשירות רשום, צריך לעבור ל-method 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);
}

גילוי שירותים ברשת

הרשת שוקקת חיים, ממדפסות רשת חיות ועד מצלמות רשת דומיננטיות לקרבות האכזריים והלוהטים של איקס-עיגול בקרבת מקום לשחקנים. המפתח שמאפשר לאפליקציה לראות את הסביבה העסקית התוססת הזו היא חיפוש שירות. האפליקציה צריכה להאזין לשירות שידורים ברשת כדי לראות אילו שירותים זמינים, ולסנן את הנתונים כל דבר שהאפליקציה לא יכולה לעבוד איתו.

תהליך הגילוי של השירות, כמו רישום השירות, כולל שני שלבים: להגדיר אוזן Discovery עם הקריאות החוזרות (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".

אחרי הגדרת המאזינים, קוראים ל-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);
    }