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

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

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