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