当前位置: 首页 > 工具软件 > NSD > 使用案例 >

Android基于NSD实现网络服务发现功能

曹铭晨
2023-12-01

一. 简介

网络服务发现:一般是指通过此功能,在局域网内来发现同样支持此功能的设备,并跟其他设备建立连接。

Android 提供了一个网络服务发现(NSD),可让应用访问其他设备在本地网路上提供的服务。

NSD 实现了基于 DNS 的服务发现 (DNS-SD) 机制,该机制允许您的应用通过指定服务类型和提供所需类型服务的设备实例的名称来请求服务。Android 和其他移动平台均支持 DNS-SD。其实NSD是基于iOS的Bonjour服务发现协议实现的,所以使用NSD可以与iOS进行跨平台交互。

NSD(NsdManager)是Android SDK中自带的类库,可以集成直接使用。使用 NSD服务需要android4.1及以上 minSdkVersion >16

二. NSD的两个功能

(1)NSD注册功能
​ 进行NSD注册:自定义服务名、端口号,IP地址注册到NSD服务中。
(2)NSD扫描功能
​ 扫描到当前局域网内所有已通过NSD注册了的应用设备的网络信息(服务名、端口号、IP地址

三. 需要先了解的

  • NsdManager:系统自带类库,可以通过getSystemService(NSD_SERVICE) 获取这个对象;
  • NsdServiceInfo:此对象提供你的服务时的网络信息(服务名、端口号、IP地址)。

四. NSD 服务端实现

步骤如下:

  1. NsdManager初始化
NsdManager nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
  1. 创建一个server来获取端口
   try {
           ServerSocket mServerSocket = new ServerSocket(0);//设为0,会自动获取没有占用的端口
           int mPort = mServerSocket.getLocalPort();
       } catch (IOException e) {
           e.printStackTrace();
       }   
  1. 创建NsdServiceInfo
NsdServiceInfo mNsdServiceInfo = new NsdServiceInfo();
mNsdServiceInfo.setServiceName(SERVICE_NAME);// 设置服务名
mNsdServiceInfo.setServiceType(SERVICE_TYPE);// 设置服务类型
mNsdServiceInfo.setPort(mPort);// 设置端口号
  1. 创建注册监听
private void createRegistration() {
        mRegistrationListener = new NsdManager.RegistrationListener() {
            @Override
            public void onRegistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
            	// 注册失败回调
                Toast.makeText(MainActivity.this, "onRegistrationFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onUnregistrationFailed(NsdServiceInfo nsdServiceInfo, int i) {
            	// 取消注册失败
                Toast.makeText(MainActivity.this, "onUnregistrationFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceRegistered(NsdServiceInfo nsdServiceInfo) {
            	// 注册服务成功
                Toast.makeText(MainActivity.this, "onServiceRegistered", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceUnregistered(NsdServiceInfo nsdServiceInfo) {
            	// 取消注册成功
                Toast.makeText(MainActivity.this, "onServiceUnregistered", Toast.LENGTH_SHORT).show();
            }
        };
    }
  1. 注册
mNsdManager = (NsdManager) getSystemService(NSD_SERVICE);
mNsdManager.registerService(mNsdServiceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);

第2步创建NsdServiceInfo,调用各种set方法,是为了给客户端信息,客户端通过get方法可以拿到相应的信息。

第4步注册中的NsdManager.PROTOCOL_DNS_SD是基于Dns的服务发现协议。

五. 客户端实现

步骤如下:

  1. NsdManager初始化nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
  2. 创建两个监听器。分别是ResolveListenerDiscoveryListener分别来监听NsdServiceInfo的信息和服务的链接成功和失败。
// 这个主要回调,解析搜索到的设备的NsdServiceInfo信息
private void createResolverListener() {
        mResolverListener = new NsdManager.ResolveListener() {
            @Override
            public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
                Toast.makeText(MainActivity.this, "onResolveFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
                mNsdServiceInfo = nsdServiceInfo;
        
            }
        };
    }
// 这个主要是监听搜索结果
private void createDiscoverListener() {
        mDiscoveryListener = new NsdManager.DiscoveryListener() {
            @Override
            public void onStartDiscoveryFailed(String s, int i) {
            	// 开始搜索失败
                Toast.makeText(MainActivity.this, "onStartDiscoveryFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onStopDiscoveryFailed(String s, int i) {
            	// 结束搜索失败
                Toast.makeText(MainActivity.this, "onStopDiscoveryFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDiscoveryStarted(String s) {
            	// 开始搜索成功
                Toast.makeText(MainActivity.this, "onDiscoveryStarted", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDiscoveryStopped(String s) {
            	// 停止搜索
                Toast.makeText(MainActivity.this, "onDiscoveryStopped", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
            	// 搜索到服务信息
                mmNsdServiceInfo = nsdServiceInfo;
                //这里的nsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法
                Toast.makeText(MainActivity.this, "onServiceFound", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
                // 	服务丢失,也就是对面的服务断开了。
                Toast.makeText(MainActivity.this, "onServiceLost", Toast.LENGTH_SHORT).show();
            }
        };
    }

DiscoveryListener这个监听中的NsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法。

  1. 发现周边的NSD相关网络
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

第一个参数要和NSD服务器端定的ServerType一样.
第二个参数是固定的
第三个参数是扫描监听器

  1. 主动调用resolveService方法
nsdManager.resolveService(mmNsdServiceInfo, mResolverListener);

主动调用该方法,会触发ResolveListener(),从而获取到NsdServiceInfo信息。

  1. 需要停止时调用
nsdManager.stopServiceDiscovery(mDiscoveryListener);

6. 需要注意的点

(1)需要授予网络权限

 <uses-permission android:name="android.permission.INTERNET"/>

(2)服务发现是一项开销很大的操作,不使用时需要取消注册。不然很耗电。

(3)NsdServiceInfo对象

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)”之类的。

第二个参数会设置服务类型,指定应用使用的协议和传输层。语法为“."。在代码段中,该服务使用通过 TCP 运行的 HTTP 协议。提供打印机服务(例如网络打印机)的应用会将服务类型设置为“_ipp._tcp”。

NsdServiceInfo类在Android 21 后,又引入了一个map的变量,可以设置键值对属性。官网对其的描述是如下:

/**
*将服务属性添加为键/值对。

*服务属性包含为DNS-SD TXT记录对。

*键必须是US-ASCII可打印字符,不包括“=”字符。值可以是UTF-8字符串或null。*key+value的总长度必须小于255字节。

*键应该很短,最好不超过9个字符,并且每个NsdServiceInfo实例都是唯一的。用*同一个键调用setAttribute两次将覆盖第一个值。
*/
Add a service attribute as a key/value pair.

Service attributes are included as DNS-SD TXT record pairs.

The key must be US-ASCII printable characters, excluding the '=' character. Values may be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.

Keys should be short, ideally no more than 9 characters, and unique per instance of NsdServiceInfo. Calling setAttribute twice with the same key will overwrite first value.

可通过NsdServiceInfo中的 setAttributegetAttributes 两个方法来设置和获取键值对属性值。

tips:服务类型语法必须正确,不然注册不了。

六. 参考链接

官网文档

gittub的demo

 类似资料: