کشف سرویس شبکه (NSD) به برنامه شما امکان دسترسی به خدماتی را می دهد که دستگاه های دیگر در یک شبکه محلی ارائه می کنند. دستگاه هایی که از NSD پشتیبانی می کنند شامل چاپگرها، وب کم ها، سرورهای HTTPS و سایر دستگاه های تلفن همراه هستند.
NSD مکانیسم کشف سرویس (DNS-SD) مبتنی بر DNS را پیادهسازی میکند که به برنامه شما امکان میدهد با تعیین یک نوع سرویس و نام نمونه دستگاهی که نوع مورد نظر سرویس را ارائه میکند، خدمات درخواست کند. DNS-SD هم در اندروید و هم در سایر سیستم عامل های تلفن همراه پشتیبانی می شود.
افزودن NSD به برنامه شما به کاربران شما امکان می دهد دستگاه های دیگری را در شبکه محلی شناسایی کنند که از خدمات درخواستی برنامه شما پشتیبانی می کنند. این برای انواع برنامه های نظیر به نظیر مانند اشتراک گذاری فایل یا بازی چند نفره مفید است. API های NSD اندروید تلاش لازم برای پیاده سازی چنین ویژگی هایی را ساده می کند.
این درس به شما نشان میدهد که چگونه برنامهای بسازید که بتواند نام و اطلاعات اتصال خود را به شبکه محلی پخش کند و اطلاعات سایر برنامهها را که همین کار را انجام میدهند، جستجو کند. در نهایت، این درس به شما نشان می دهد که چگونه به همان برنامه ای که روی دستگاه دیگری اجرا می شود متصل شوید.
خدمات خود را در شبکه ثبت کنید
توجه: این مرحله اختیاری است. اگر به پخش سرویسهای برنامه خود از طریق شبکه محلی اهمیتی نمیدهید، میتوانید به بخش بعدی، Discover Services on the Network، بروید.
برای ثبت سرویس خود در شبکه محلی، ابتدا یک شی NsdServiceInfo
ایجاد کنید. این شی اطلاعاتی را ارائه می دهد که دستگاه های دیگر در شبکه هنگام تصمیم گیری برای اتصال به سرویس شما از آن استفاده می کنند.
کاتلین
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) ... } }
جاوا
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)" تغییر می دهد.
پارامتر دوم نوع سرویس را تعیین می کند، مشخص می کند که برنامه از کدام پروتکل و لایه انتقال استفاده می کند. نحو "_<پروتکل>._<transportlayer>" است. در قطعه کد، این سرویس از پروتکل HTTP استفاده می کند که روی TCP اجرا می شود. برنامه ای که خدمات چاپگر را ارائه می دهد (به عنوان مثال، چاپگر شبکه) نوع سرویس را روی "_ipp._tcp" تنظیم می کند.
توجه: مرجع بینالمللی شمارههای اختصاصیافته (IANA) فهرستی متمرکز و معتبر از انواع سرویسهای مورد استفاده توسط پروتکلهای کشف سرویس مانند NSD و Bonjour را مدیریت میکند. شما می توانید لیست را از لیست نام سرویس و شماره پورت IANA دانلود کنید. اگر قصد استفاده از نوع سرویس جدید را دارید، باید با پر کردن فرم ثبت نام پورت ها و خدمات IANA، آن را رزرو کنید.
هنگام تنظیم پورت برای سرویس خود، از کدگذاری سخت آن خودداری کنید زیرا با سایر برنامه ها تضاد دارد. به عنوان مثال، با فرض اینکه برنامه شما همیشه از پورت 1337 استفاده می کند، آن را در تضاد احتمالی با سایر برنامه های نصب شده که از همان پورت استفاده می کنند قرار می دهد. در عوض، از پورت بعدی موجود دستگاه استفاده کنید. از آنجایی که این اطلاعات توسط یک پخش سرویس در اختیار سایر برنامهها قرار میگیرد، نیازی نیست درگاهی که برنامه شما استفاده میکند توسط سایر برنامهها در زمان کامپایل شناخته شود. در عوض، برنامه ها می توانند این اطلاعات را از پخش سرویس شما، درست قبل از اتصال به سرویس شما دریافت کنند.
اگر با سوکت ها کار می کنید، در اینجا نحوه تنظیم اولیه سوکت به هر پورت موجود به سادگی با تنظیم آن بر روی 0 آمده است.
کاتلین
fun initializeServerSocket() { // Initialize a server socket on the next available port. serverSocket = ServerSocket(0).also { socket -> // Store the chosen port. mLocalPort = socket.localPort ... } }
جاوا
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 برای هشدار به برنامه شما از موفقیت یا عدم موفقیت ثبت سرویس و لغو ثبت استفاده میشود.
کاتلین
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. } }
جاوا
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()
برود.
کاتلین
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) } }
جاوا
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); }
خدمات را در شبکه کشف کنید
این شبکه مملو از زندگی است، از چاپگرهای شبکه وحشی گرفته تا وب کم های شبکه مطیع، تا نبردهای وحشیانه و آتشین بازیکنان تیک تاک نزدیک. کلیدی که به برنامه شما اجازه می دهد این اکوسیستم عملکردی پر جنب و جوش را ببیند، کشف سرویس است. برنامه شما باید به پخشهای سرویس در شبکه گوش دهد تا ببیند چه سرویسهایی در دسترس هستند و هر چیزی را که برنامه نمیتواند با آن کار کند فیلتر کند.
کشف سرویس، مانند ثبت سرویس، دارای دو مرحله است: راهاندازی یک شنونده کشف با تماسهای مربوطه، و ایجاد یک فراخوانی API ناهمزمان منفرد به discoverServices()
.
ابتدا یک کلاس ناشناس که NsdManager.DiscoveryListener
را پیاده سازی می کند نمونه سازی کنید. قطعه زیر یک مثال ساده را نشان می دهد:
کاتلین
// 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) } }
جاوا
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 از روشهای موجود در این رابط استفاده میکند تا برنامه شما را در هنگام شروع کشف، زمانی که با شکست مواجه میشود و زمانی که سرویسها پیدا میشوند و گم میشوند (از دست رفته به معنای "دیگر در دسترس نیست") مطلع شود. توجه داشته باشید که این قطعه هنگام یافتن یک سرویس چندین بررسی می کند.
- نام سرویس سرویس یافت شده با نام سرویس سرویس محلی مقایسه می شود تا مشخص شود آیا دستگاه به تازگی پخش خود را دریافت کرده است (که معتبر است).
- نوع سرویس علامت زده شده است، برای اینکه تأیید شود این نوعی از سرویس است که برنامه شما می تواند به آن متصل شود.
- نام سرویس برای تأیید اتصال به برنامه صحیح بررسی می شود.
بررسی نام سرویس همیشه ضروری نیست و فقط در صورتی مرتبط است که بخواهید به برنامه خاصی متصل شوید. به عنوان مثال، ممکن است برنامه فقط بخواهد به نمونه هایی از خود در حال اجرا در دستگاه های دیگر متصل شود. با این حال، اگر برنامه بخواهد به یک چاپگر شبکه متصل شود، کافی است ببینید که نوع سرویس "_ipp._tcp" است.
پس از تنظیم شنونده، discoverServices()
را فراخوانی کنید، نوع سرویسی که برنامه شما باید به دنبال آن باشد، پروتکل کشف مورد استفاده و شنونده ای که به تازگی ایجاد کرده اید را ارسال کنید.
کاتلین
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener)
جاوا
nsdManager.discoverServices( SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
به خدمات موجود در شبکه متصل شوید
هنگامی که برنامه شما سرویسی را در شبکه پیدا می کند تا به آن متصل شود، ابتدا باید اطلاعات اتصال آن سرویس را با استفاده از متد resolveService()
تعیین کند. یک NsdManager.ResolveListener
را برای عبور به این روش پیاده سازی کنید و از آن برای دریافت NsdServiceInfo
حاوی اطلاعات اتصال استفاده کنید.
کاتلین
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 } }
جاوا
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 در طول چرخه عمر برنامه بسیار مهم است. لغو ثبت نام برنامه زمانی که بسته میشود، کمک میکند تا دیگر برنامهها فکر نکنند هنوز فعال است و سعی در اتصال به آن ندارند. همچنین، کشف سرویس یک عملیات پرهزینه است و باید با توقف فعالیت والد متوقف شود و زمانی که فعالیت از سر گرفته شد، دوباره فعال شود. روشهای چرخه حیات فعالیت اصلی خود را نادیده بگیرید و کد را برای شروع و توقف پخش و کشف سرویس در صورت لزوم وارد کنید.
کاتلین
// 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) } }
جاوا
// 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); }