استفاده از کشف سرویس شبکه

کاوش سرویس شبکه (NSD) به برنامه شما امکان می‌دهد به سرویس‌هایی که دستگاه‌های دیگر در شبکه محلی ارائه می‌دهند دسترسی داشته باشد. دستگاه‌هایی که از NSD پشتیبانی می‌کنند شامل چاپگرها، وب‌بین‌ها، سرورهای HTTPS، و دیگر دستگاه‌های همراه می‌شود.

NSD سازوکار «یافتن سرویس مبتنی بر DNS»‏ (DNS-SD) را پیاده‌سازی می‌کند که به برنامه شما امکان می‌دهد با مشخص کردن نوع سرویس و نام نمونه دستگاهی که نوع سرویس موردنظر را ارائه می‌دهد، سرویس‌ها را درخواست کند. DNS-SD هم در Android و هم در پلاتفرم‌های تلفن همراه دیگر پشتیبانی می‌شود.

افزودن NSD به برنامه‌تان به کاربران امکان می‌دهد دستگاه‌های دیگری را در شبکه محلی شناسایی کنند که از سرویس‌های درخواستی برنامه‌تان پشتیبانی می‌کنند. این ویژگی برای انواع برنامه‌های همتا به همتا مانند هم‌رسانی فایل یا بازی چندنفره مفید است. میاناهای برنامه‌سازی کاربردی NSD در Android تلاش لازم برای پیاده‌سازی چنین ویژگی‌هایی را برای شما ساده می‌کند.

این درس به شما نشان می‌دهد که چگونه برنامه‌ای بسازید که بتواند نام و اطلاعات اتصال خود را در شبکه محلی پخش کند و اطلاعات سایر برنامه‌هایی را که همین کار را انجام می‌دهند اسکن کند. درنهایت، این درس به شما نشان می‌دهد که چگونه به همان برنامه درحال اجرا در دستگاه دیگری متصل شوید.

ثبت سرویس شما در شبکه

توجه: این مرحله اختیاری است. اگر برایتان مهم نیست که سرویس‌های برنامه‌تان را در شبکه محلی همه‌فرستی کنید، می‌توانید به بخش بعدی، یافتن سرویس‌ها در شبکه، بروید.

برای ثبت سرویس خود در شبکه محلی، ابتدا یک شیء 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 (۱)» تغییر می‌دهد.

پارامتر دوم نوع سرویس را تنظیم می‌کند و مشخص می‌کند برنامه از کدام پروتکل و لایه حمل‌ونقل استفاده می‌کند. نحو آن «_<protocol>._<transportlayer>» است. در تکه‌کد، سرویس از پروتکل HTTP که روی TCP اجرا می‌شود استفاده می‌کند. برنامه‌ای که سرویس چاپگر (برای مثال، چاپگر شبکه) ارائه می‌دهد، نوع سرویس را روی «_ipp._tcp» تنظیم می‌کند.

توجه: «مرجع واگذاری اعداد در اینترنت» (IANA) فهرستی متمرکز و معتبر از انواع سرویس‌های مورداستفاده پروتکل‌های کشف سرویس مانند NSD و Bonjour را مدیریت می‌کند. می‌توانید این فهرست را از فهرست نام‌های سرویس و شماره‌های درگاه IANA بارگیری کنید. اگر قصد دارید از نوع سرویس جدیدی استفاده کنید، باید آن را با تکمیل کردن فرم ثبت‌نام سرویس و درگاه‌های IANA رزرو کنید.

هنگام تنظیم درگاه برای سرویس خود، از کدنویسی ثابت آن خودداری کنید زیرا این کار با برنامه‌های دیگر تداخل دارد. برای مثال، فرض اینکه برنامه‌تان همیشه از درگاه ۱۳۳۷ استفاده می‌کند، آن را در تضاد احتمالی با دیگر برنامه‌های نصب‌شده‌ای قرار می‌دهد که از همان درگاه استفاده می‌کنند. به‌جای آن، از درگاه دردسترس بعدی دستگاه استفاده کنید. چون این اطلاعات ازطریق همه‌فرستی سرویس به برنامه‌های دیگر ارائه می‌شود، نیازی نیست که در زمان ترجمه، برنامه‌های دیگر از درگاهی که برنامه شما استفاده می‌کند مطلع باشند. درعوض، برنامه‌ها می‌توانند این اطلاعات را درست قبل‌از اتصال به سرویس شما، از پخش سرویس شما دریافت کنند.

اگر با سوکت‌ها کار می‌کنید، در اینجا نحوه مقداردهی اولیه سوکت به هر درگاه دردسترس با تنظیم آن روی 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 از روش‌های موجود در این میانای کاربری استفاده می‌کند تا وقتی کاوش شروع می‌شود، وقتی ناموفق است، و وقتی سرویس‌ها پیدا و گم می‌شوند (گم شدن یعنی «دیگر دردسترس نیست») به برنامه شما اطلاع دهد. توجه داشته باشید که این قطعه کد چندین بررسی را انجام می‌دهد وقتی سرویسی پیدا می‌شود.

  1. نام سرویس سرویس پیداشده با نام سرویس سرویس محلی مقایسه می‌شود تا مشخص شود آیا دستگاه به‌تازگی پخش خود را دریافت کرده است یا نه (که معتبر است).
  2. نوع سرویس بررسی می‌شود تا تأیید شود که این نوع سرویس می‌تواند به برنامه شما متصل شود.
  3. نام سرویس بررسی می‌شود تا اتصال به برنامه درست درستی‌سنجی شود.

بررسی نام سرویس همیشه ضروری نیست و فقط درصورتی اهمیت دارد که بخواهید به برنامه خاصی متصل شوید. برای مثال، ممکن است برنامه فقط بخواهد به نمونه‌هایی از خودش که در دستگاه‌های دیگر اجرا می‌شود متصل شود. بااین‌حال، اگر برنامه بخواهد به چاپگر شبکه متصل شود، کافی است ببیند که نوع سرویس «_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);
    }