0. 前言
本文介绍了什么是NSD协议,并介绍了如何在Android中实现NSD的服务端和客户端,实现局域网内的设备发现功能。
1. NSD是什么
在Android开发中,NSD(Network Service Discovery)是一种用于在局域网内发现其他设备提供的网络服务的技术,NSD是Android SDK中自带的类库。
NSD扫描时,Android设备会在局域网内主动搜索已经通过NSD注册的服务。这些服务可能由打印机、摄像头、HTTPS服务器或其他移动设备提供。通过NSD扫描,Android应用可以获取到这些服务的名称、端口号和IP地址,从而为后续的网络通信或连接做准备。
2. NSD扫描的应用场景
NSD扫描在Android开发中有着广泛的应用场景,包括但不限于:
- 局域网设备互联:通过NSD扫描,Android设备可以发现局域网内的其他设备,并实现设备间的互联和通信。
- 发现网络打印机:在办公室或家庭环境中,Android设备可以通过NSD扫描发现网络打印机,并进行打印操作。
- 多人游戏:在多人游戏场景中,NSD扫描可以帮助玩家发现同一局域网内的其他玩家设备,从而实现多人在线游戏。
- 智能家居:在智能家居领域,NSD扫描可以用于发现和控制局域网内的智能家居设备,如智能灯泡、智能插座等。
3. NSD协议的原理
NSD协议即网络服务发现协议,其工作原理是基于DNS-SD (Domain Name System - Service Discovery)协议的服务发现机制,用于在网络中自动发现可用的服务和设备。
- 基于DNS-SD的服务注册:设备作为服务提供者,会将自身提供的服务信息按照DNS-SD的格式要求,在DNS服务器上进行注册。这些信息包括服务类型、服务名称以及服务所在的主机地址等 。例如,一个提供打印服务的设备,会将打印服务的相关信息注册到DNS服务器,其中服务类型可能是“_http._tcp”,服务名称可以自定义为“打印机1”,同时还会注册该设备在网络中的IP地址。
- 基于DNS-SD的服务查询:当设备作为服务使用者需要查找特定服务时,会向DNS服务器发送基于DNS-SD的查询请求。查询请求中会指定要查找的服务类型等信息,DNS服务器收到请求后,会根据注册的信息进行匹配查找,并将符合条件的服务实例信息返回给查询者。
- 利用DNS基础设施:DNS-SD充分利用了现有的DNS基础设施来实现服务发现功能,无需额外构建复杂的网络架构和通信协议。这使得NSD协议能够在各种网络环境中较为便捷地部署和应用,只要网络中存在可用的DNS服务器,并且支持DNS-SD的相关记录类型,就可以实现服务的发布和发现。
4. NSD协议只能发现Android设备吗
NSD协议是Android系统中提供的一种网络服务发现机制,它允许应用在同一局域网内自动发现其他设备提供的服务,并与之建立连接。
而根据上文,我们已经知道NSD协议是基于DNS-SD协议的,所以NSD协议不仅限于发现Android设备,而是包括所有支持NSD协议或兼容DNS-SD(基于DNS的服务发现)的设备。
5. Android中如何实现NSD协议
Android中实现NSD协议需要使用NSDManager这个类,它用来发现网络服务的API。
这个API支持三个主要的操作 : registerService、discoverServices和resolveService。
registerService用来注册NSD服务,discoverServices用来发现NSD服务,resolveService用来解析NSD服务。
5.1 服务端的实现
5.1.1 添加权限
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
5.1.2 获取 NsdManager 管理类
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
//...
NsdManager mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
5.1.3 注册服务
首先会创建NsdServiceInfo对象来描述服务信息,包括服务名称(setServiceName)、服务类型(setServiceType)和端口号(setPort)。然后通过mNsdManager.registerService方法来注册服务,RegistrationListener用于接收注册过程中的各种事件。
private fun registerService() {
val serviceInfo = NsdServiceInfo()
serviceInfo.serviceName = "MyService"
serviceInfo.serviceType = "_http._tcp."
//实际项目开发中,端口号不应该写死,应该动态获取端口号,防止端口号已经被占用
serviceInfo.port = 8586
mNsdManager.registerService(
serviceInfo,
NsdManager.PROTOCOL_DNS_SD,
registrationListener
)
}
private val registrationListener = object : RegistrationListener {
override fun onRegistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// 注册服务失败时调用
}
override fun onUnregistrationFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// 注销服务失败时调用
}
override fun onServiceRegistered(serviceInfo: NsdServiceInfo) {
// 服务注册成功时调用
}
override fun onServiceUnregistered(serviceInfo: NsdServiceInfo) {
// 服务注销成功时调用
}
}
5.1.3.1 动态获取端口号
实际项目开发中,端口号不应该写死,应该动态获取端口号,防止端口号已经被占用
import java.io.IOException;
import java.net.ServerSocket;
public class PortFinder {
public static int getAvailablePort() {
try {
ServerSocket serverSocket = new ServerSocket(0);
int port = serverSocket.getLocalPort();
serverSocket.close();
return port;
} catch (IOException e) {
e.printStackTrace();
return -1;
}
}
}
5.1.4 停止发现服务
当不再需要发现服务时,可以使用mNsdManager.stopDiscovery方法来停止服务发现。
override fun onDestroy() {
super.onDestroy()
mNsdManager.unregisterService(registrationListener)
}
5.2 客户端的实现
5.2.1 添加权限
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
5.2.2 获取 NsdManager 管理类
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
//...
NsdManager mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
5.2.3 发现服务
可以使用NsdManager的discoverServices方法来发现服务。
private fun discoverServices() {
mNsdManager.discoverServices(
"_http._tcp.",
NsdManager.PROTOCOL_DNS_SD,
object : DiscoveryListener {
override fun onDiscoveryStarted(regType: String) {
// 当服务发现开始时调用
}
override fun onServiceFound(service: NsdServiceInfo) {
// 当发现一个服务时调用
mNsdManager.resolveService(service, object : NsdManager.ResolveListener {
override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
// 解析服务失败时调用
}
override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
// 服务解析成功时调用
val serviceName = serviceInfo.serviceName
val host = serviceInfo.host.hostAddress
val port = serviceInfo.port
// 在这里可以使用获取到的服务信息,例如连接到服务
Log.i("Heiko", "serviceName:$serviceName host:$host port:$port")
runOnUiThread {
Toast.makeText(this@MainActivity,"serviceName:$serviceName host:$host port:$port",Toast.LENGTH_SHORT).show()
}
}
})
}
override fun onServiceLost(service: NsdServiceInfo) {
// 当服务丢失时调用
}
override fun onDiscoveryStopped(serviceType: String) {
// 当服务发现停止时调用
}
override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
// 开始服务发现失败时调用
}
override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
// 停止服务发现失败时调用
}
})
}
- 在discoverServices方法中,第一个参数"_http._tcp."是服务类型。这是一种按照 DNS - SD(Domain Name System - Service Discovery)格式定义的服务类型。NsdManager.PROTOCOL_DNS_SD表示使用 DNS - SD 协议。DiscoveryListener是一个回调接口,用于接收服务发现过程中的各种事件。
- 当发现一个服务(onServiceFound方法被调用)时,需要调用resolveService方法来解析服务,以获取更详细的信息,如服务的主机地址(
host
)和端口号(port
)。
5.3 运行程序
将服务端Android设备和客户端的Android设备都连接到同一个局域网中。然后先启动服务端App,再启动客户端App,点击按钮进行查找,会走onServiceFound
回调,并在resolveService
中走onServiceResolved
回调,这里可以获取到服务的主机地址(host
)和端口号(port
),至此,NSD扫描的整个流程就都走通了。
6. 本文源码
https://gitee.com/EthanCo/my-nsd-test
7. 参考
官方文档 : NsdManager
Android网络服务发现(NSD)使用