网络服务发现:一般是指通过此功能,在局域网内来发现同样支持此功能的设备,并跟其他设备建立连接。
Android 提供了一个网络服务发现(NSD),可让应用访问其他设备在本地网路上提供的服务。
NSD 实现了基于 DNS 的服务发现 (DNS-SD) 机制,该机制允许您的应用通过指定服务类型和提供所需类型服务的设备实例的名称来请求服务。Android 和其他移动平台均支持 DNS-SD。其实NSD是基于iOS的Bonjour服务发现协议实现的,所以使用NSD可以与iOS进行跨平台交互。
NSD(NsdManager)是Android SDK中自带的类库,可以集成直接使用。使用 NSD服务需要android4.1及以上 minSdkVersion >16
(1)NSD注册功能
进行NSD注册:自定义服务名、端口号,IP地址注册到NSD服务中。
(2)NSD扫描功能
扫描到当前局域网内所有已通过NSD注册了的应用设备的网络信息(服务名、端口号、IP地址)
步骤如下:
NsdManager nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
try {
ServerSocket mServerSocket = new ServerSocket(0);//设为0,会自动获取没有占用的端口
int mPort = mServerSocket.getLocalPort();
} catch (IOException e) {
e.printStackTrace();
}
NsdServiceInfo mNsdServiceInfo = new NsdServiceInfo();
mNsdServiceInfo.setServiceName(SERVICE_NAME);// 设置服务名
mNsdServiceInfo.setServiceType(SERVICE_TYPE);// 设置服务类型
mNsdServiceInfo.setPort(mPort);// 设置端口号
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();
}
};
}
mNsdManager = (NsdManager) getSystemService(NSD_SERVICE);
mNsdManager.registerService(mNsdServiceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
第2步创建NsdServiceInfo,调用各种set方法,是为了给客户端信息,客户端通过get方法可以拿到相应的信息。
第4步注册中的NsdManager.PROTOCOL_DNS_SD是基于Dns的服务发现协议。
步骤如下:
NsdManager
初始化nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
ResolveListener
和DiscoveryListener
分别来监听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方法。
nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
第一个参数要和NSD服务器端定的ServerType一样.
第二个参数是固定的
第三个参数是扫描监听器
nsdManager.resolveService(mmNsdServiceInfo, mResolverListener);
主动调用该方法,会触发
ResolveListener()
,从而获取到NsdServiceInfo信息。
nsdManager.stopServiceDiscovery(mDiscoveryListener);
(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中的 setAttribute 和 getAttributes 两个方法来设置和获取键值对属性值。
tips:服务类型语法必须正确,不然注册不了。