استخدام ميزة "رصد خدمات الشبكة"

تتيح ميزة "اكتشاف خدمة الشبكة" (NSD) لتطبيقك إمكانية الوصول إلى الخدمات التي الأجهزة التي تقدمها على شبكة محلية. تشمل الأجهزة التي تدعم NSD الطابعات، كاميرات الويب وخوادم HTTPS وأجهزة الجوال الأخرى.

تطبِّق NSD آلية اكتشاف الخدمة المستندة إلى نظام أسماء النطاقات (DNS-SD)، والتي يسمح لتطبيقك بطلب الخدمات من خلال تحديد نوع الخدمة والاسم. من مثيل الجهاز الذي يوفر النوع المطلوب من الخدمة. DNS-SD هو معتمد على كل من Android وأنظمة الهاتف الأساسية الأخرى.

من خلال إضافة ميزة NSD إلى تطبيقك، يمكن للمستخدمين التعرّف على الأجهزة الأخرى في شبكة محلية تدعم الخدمات التي يطلبها تطبيقك. يعد ذلك مفيدًا مجموعة متنوعة من تطبيقات شبكة الند للند مثل مشاركة الملفات أو الألعاب متعددة اللاعبين الألعاب. تبسّط واجهات برمجة التطبيقات 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 يعمل عبر بروتوكول التحكم بالنقل. تطبيق سيؤدي تقديم خدمة طابعة (على سبيل المثال، طابعة شبكة) إلى تعيين إلى " _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. هذا النمط على طلبات معاودة الاتصال التي يستخدمها 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);
}

اكتشاف الخدمات على الشبكة

إن الشبكة مليئة بالحياة، من طابعات الشبكة الوحشية إلى كاميرات ويب قابلة للطيّ، وصولاً إلى المعارك الوحشية في لعبة tic-tac-toe القريبة اللاعبين. السر في السماح لتطبيقك بمشاهدة هذه المنظومة المتكاملة النابضة بالحياة هي اكتشاف الخدمة. يحتاج التطبيق إلى الاستماع إلى الخدمة عمليات البث على الشبكة لمعرفة الخدمات المتاحة وتصفية أي شيء لا يمكن للتطبيق العمل معه.

تتألف عملية اكتشاف الخدمة، مثل تسجيل الخدمة، من خطوتَين: وإعداد مستمع أثناء التصفّح باستخدام عمليات الاستدعاء ذات الصلة، وإجراء استدعاء غير متزامن طلب بيانات من واجهة برمجة التطبيقات إلى 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 الطرق المتوفّرة في هذه الواجهة لإبلاغ تطبيقك عند اكتشافه. في حال تعطلها، وعند العثور على الخدمات وفقدانها (تعني كلمة "مفقودة" لم تعد متاحة"). لاحظ أن هذا المقتطف يجري عدة عمليات تحقق عند العثور على الخدمة.

  1. تتم مقارنة اسم الخدمة التي تم العثور عليها بالخدمة. اسم الخدمة المحلية لتحديد ما إذا كان الجهاز قد حصل للتو على جهازه الخاص البث (وهو صالح).
  2. يتم تحديد نوع الخدمة، للتحقق من أنها نوع من الخدمات التي الاتصال بها.
  3. يتم التحقّق من اسم الخدمة للتأكّد من الاتصال بالخدمة الصحيحة التطبيق.

ليس من الضروري دائمًا التحقّق من اسم الخدمة، وهو ملائم فقط في حال: الاتصال بتطبيق معين. على سبيل المثال، قد يتضمن التطبيق الاتصال بالمثيلات نفسها التي تعمل على أجهزة أخرى فقط. ومع ذلك، إذا كانت الاتصال بطابعة شبكة، ويكفي معرفة أن نوع الخدمة هي " _ipp._tcp".

بعد إعداد خدمة الاستماع، يُرجى الاتصال بخدمة discoverServices() مع تضمين نوع الخدمة. الذي ينبغي أن يبحث عنه التطبيق، وبروتوكول الاكتشاف الذي يجب استخدامه، مستمع أنشأته للتو.

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