在我们的实际开发中,经常会有点对点通讯的需求,我们知道,通讯的基础是知道彼此的标识,在网络中便是IP,如果具体到某一个应用,还需要端口号。那我们的NSD技术其实就是实现在同一wifi的局域网情况下,实现查询设备网络信息(IP和端口)的。NSD的全称是Network Service Discovery。从名称我们也可以看出来其是通过一个服务实现上述功能--Demo源码下载。那我们就来简单看一下其使用流程。关于使用流程,我觉得NSD相关API中NsdManager(系统服务)中的描述可以非常详细和简单的概括了:
/** * The Network Service Discovery Manager class provides the API to discover services * on a network. As an example, if device A and device B are connected over a Wi-Fi * network, a game registered on device A can be discovered by a game on device * B. Another example use case is an application discovering printers on the network. * *
The API currently supports DNS based service discovery and discovery is currently * limited to a local network over Multicast DNS. DNS service discovery is described at * http://files.dns-sd.org/draft-cheshire-dnsext-dns-sd.txt * *
The API is asynchronous and responses to requests from an application are on listener * callbacks on a seperate internal thread. * *
There are three main operations the API supports - registration, discovery and resolution. *
* Application start * | * | * | onServiceRegistered() * Register any local services / * to be advertised with \ * registerService() onRegistrationFailed() * | * | * discoverServices() * | * Maintain a list to track * discovered services * | * |---------> * | | * | onServiceFound() * | | * | add service to list * | | * |<---------- * | * |---------> * | | * | onServiceLost() * | | * | remove service from list * | | * |<---------- * | * | * | Connect to a service * | from list ? * | * resolveService() * | * onServiceResolved() * | * Establish connection to service * with the host and port information * ** An application that needs to advertise itself over a network for other applications to * discover it can do so with a call to {@link #registerService}. If Example is a http based * application that can provide HTML data to peer services, it can register a name "Example" * with service type "_http._tcp". A successful registration is notified with a callback to * {@link RegistrationListener#onServiceRegistered} and a failure to register is notified * over {@link RegistrationListener#onRegistrationFailed} * *A peer application looking for http services can initiate a discovery for "_http._tcp" * with a call to {@link #discoverServices}. A service found is notified with a callback * to {@link DiscoveryListener#onServiceFound} and a service lost is notified on * {@link DiscoveryListener#onServiceLost}. * *
Once the peer application discovers the "Example" http service, and either needs to read the * attributes of the service or wants to receive data from the "Example" application, it can * initiate a resolve with {@link #resolveService} to resolve the attributes, host, and port * details. A successful resolve is notified on {@link ResolveListener#onServiceResolved} and a * failure is notified on {@link ResolveListener#onResolveFailed}. * * Applications can reserve for a service type at * http://www.iana.org/form/ports-service. Existing services can be found at * http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xml * * {@see NsdServiceInfo} */
上面是对NsdManager的描述,我觉得看了这个再加上些许整理基本上就掌握了Nsd使用的整体流程。在看之前,我们先做一些说明,首先同一台设备无法实现API功能演示,为了便于理解,我们使用两台设备来说明。一台我们定义为S,另一台定义为C,接着我们来看看上面的描述:
上面的五步就是上面的英文的概括,对于其功能我们已经知道了,我们主要关注的如何使用以及具体的实现,所以重点是第4和第5。我们接着来看一下第4部分的内容:
*
There are three main operations the API supports - registration, discovery and resolution. *
* Application start * | * | * | onServiceRegistered() * Register any local services / * to be advertised with \ * registerService() onRegistrationFailed() * | * | * discoverServices() * | * Maintain a list to track * discovered services * | * |---------> * | | * | onServiceFound() * | | * | add service to list * | | * |<---------- * | * |---------> * | | * | onServiceLost() * | | * | remove service from list * | | * |<---------- * | * | * | Connect to a service * | from list ? * | * resolveService() * | * onServiceResolved() * | * Establish connection to service * with the host and port information
上面说到这NsdManager主要实现三个功能:
具体的使用步骤如下:
因为NsdManager既完成了注册服务由完成了发现与解析服务的功能,所以上面的步骤我们还需要分解一下,对于被发现设备S,我们只需要完成第一步即可;对于发现设备C,我们需要实现(2)(3)(4),且最好都在子线程中执行。下面就来看看具体的实现。考虑到实际的应用,我们这里分为服务端和客户端,分别对应于设备S和设备C。我们来看看注册服务的具体实现:
private void registerService(Context context, String serviceName, int port) {
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName(serviceName);
serviceInfo.setPort(port);
serviceInfo.setServiceType(mServerType);
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
其中,就是通过NsdManager的registerService来实现,其中的mRegistrationListener的初始化如下:
//初始化化注册监听器
private void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "NsdServiceInfo onRegistrationFailed");
if (registerState != null) {
registerState.onRegistrationFailed(serviceInfo, errorCode);
}
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.i(TAG, "onUnregistrationFailed serviceInfo: " + serviceInfo + " ,errorCode:" + errorCode);
if (registerState != null) {
registerState.onUnRegistrationFailed(serviceInfo, errorCode);
}
}
@Override
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
mServerName = serviceInfo.getServiceName();
Log.i(TAG, "onServiceRegistered: " + serviceInfo.toString());
Log.i(TAG, "mServerName onServiceRegistered: " + mServerName);
if (registerState != null) {
registerState.onServiceRegistered(serviceInfo);
}
}
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
Log.i(TAG, "onServiceUnregistered serviceInfo: " + serviceInfo);
if (registerState != null) {
registerState.onServiceUnregistered(serviceInfo);
}
}
};
}
上述两步其实就是注册服务的实现了,在实际的应用中结合线程即可完成注册服务的工作,下面就来看看客户端发现并解析服务的实现,首先是初始化发现监听:
/**
* 扫描解析前的 NsdServiceInfo
* 用于服务发现的回调调用接口
*/
private void initializeDiscoveryListener() {
mDiscoveryListener = new NsdManager.DiscoveryListener() {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
mNsdManager.stopServiceDiscovery(this);
Log.e(TAG, "onStartDiscoveryFailed():");
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
mNsdManager.stopServiceDiscovery(this);
Log.e(TAG, "onStopDiscoveryFailed():");
}
@Override
public void onDiscoveryStarted(String serviceType) {
Log.e(TAG, "onDiscoveryStarted():");
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.e(TAG, "onDiscoveryStopped():");
}
/**
*
* @param serviceInfo
*/
@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
Log.e(TAG, "onServiceFound: "+serviceInfo );
discoveryList.add(serviceInfo.toString());
//根据咱服务器的定义名称,指定解析该 NsdServiceInfo
if (serviceInfo.getServiceName().equals(mServiceName)) {
mNsdManager.resolveService(serviceInfo, mResolverListener);
}
}
@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
Log.e(TAG, "onServiceLost(): serviceInfo=" + serviceInfo);
discoveryList.remove(serviceInfo.toString());
}
};
}
初始化解析器监听器:
/**
* 解析发现的NsdServiceInfo
*/
private void initializeResolveListener() {
mResolverListener = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
int port = serviceInfo.getPort();
String serviceName = serviceInfo.getServiceName();
String hostAddress = serviceInfo.getHost().getHostAddress();
Message message=Message.obtain();
message.what=1;
message.obj=serviceInfo.toString();
mHandler.sendMessage(message);
Log.e(TAG, "onServiceResolved 已解析:" + " host:" + hostAddress + ":" + port + " ----- serviceName: " + serviceName);
resolveList.add(" host:" + hostAddress + ":" + port );
//TODO 建立网络连接
}
};
}
开始扫描:
public void startNSDClient(Handler handler) {
mHandler=handler;
mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
initializeDiscoveryListener();//初始化监听器
initializeResolveListener();//初始化解析器
mNsdManager.discoverServices(NSD_SERVER_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);//开启扫描
}
上面只是一个流程,没有形成一个完整的示例,下面我就结合上面的介绍给出一个完整的示例代码功能,示例中由服务端和客户端组成,服务端有以下功能:
客户端主要有以下功能:
服务端代码:
NsdServer.java:
package aoto.com.operationnsd;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;
/**
* @author why
* @date 2019.04.28
*/
public class NSDServer {
public static final String TAG = "NSDServerWhy";
private NsdManager mNsdManager;
private NsdManager.RegistrationListener mRegistrationListener;
private IRegisterState registerState; //NSD服务接口对象
private String mServerName;
private final String mServerType = "_http._tcp."; // 服务器type,要客户端扫描服务器的一致
public NSDServer() {
}
public void startNSDServer(Context context, String serviceName, int port) {
initializeRegistrationListener();
registerService(context, serviceName, port);
}
//初始化化注册监听器
private void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.e(TAG, "NsdServiceInfo onRegistrationFailed");
if (registerState != null) {
registerState.onRegistrationFailed(serviceInfo, errorCode);
}
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
Log.i(TAG, "onUnregistrationFailed serviceInfo: " + serviceInfo + " ,errorCode:" + errorCode);
if (registerState != null) {
registerState.onUnRegistrationFailed(serviceInfo, errorCode);
}
}
@Override
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
mServerName = serviceInfo.getServiceName();
Log.i(TAG, "onServiceRegistered: " + serviceInfo.toString());
Log.i(TAG, "mServerName onServiceRegistered: " + mServerName);
if (registerState != null) {
registerState.onServiceRegistered(serviceInfo);
}
}
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
Log.i(TAG, "onServiceUnregistered serviceInfo: " + serviceInfo);
if (registerState != null) {
registerState.onServiceUnregistered(serviceInfo);
}
}
};
}
private void registerService(Context context, String serviceName, int port) {
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
NsdServiceInfo serviceInfo = new NsdServiceInfo();
serviceInfo.setServiceName(serviceName);
serviceInfo.setPort(port);
serviceInfo.setServiceType(mServerType);
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
}
public void stopNSDServer() {
mNsdManager.unregisterService(mRegistrationListener);
}
//NSD服务注册监听接口
public interface IRegisterState {
void onServiceRegistered(NsdServiceInfo serviceInfo); //注册NSD成功
void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode); //注册NSD失败
void onServiceUnregistered(NsdServiceInfo serviceInfo); //取消NSD注册成功
void onUnRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode); //取消NSD注册失败
}
//设置NSD服务接口对象
public void setRegisterState(IRegisterState registerState) {
this.registerState = registerState;
}
}
独立线程处理注册任务:
package aoto.com.operationnsd;
import android.content.Context;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;
/**
* author:why
* created on: 2019/3/4 15:11
* description:
*/
public class NsdServerRunnable implements Runnable {
private static final String TAG = "NsdServerRunnableWhy";
/**
* 注册 NSD 服务的名称 和 端口 这个可以设置默认固定址,用于客户端通过 NSD_SERVER_NAME 筛选得到服务端地址和端口
*/
private String nsdServerName ;
private final int NSD_PORT = 8088;
private NSDServer nsdServer;
private Context context;
public NsdServerRunnable(String nsdServerName, NSDServer nsdServer, Context context){
this.nsdServerName=nsdServerName;
this.nsdServer=nsdServer;
this.context=context;
}
@Override
public void run() {
nsdServer.startNSDServer(context, nsdServerName, NSD_PORT);
nsdServer.setRegisterState(new NSDServer.IRegisterState() {
@Override
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
Log.e(TAG, "onServiceRegistered: " + serviceInfo.toString());
//已经注册可停止该服务
// nsdServer.stopNSDServer();
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
}
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
}
@Override
public void onUnRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
}
});
}
}
对外接口管理类:
package aoto.com.operationnsd;
import android.content.Context;
import android.util.Log;
/**
* author:why
* created on: 2019/3/4 15:15
* description:
*/
public class NsdServerManager {
private static final String TAG = "NsdServerManagerWhy";
private volatile static NsdServerManager nsdServerManager;
private static NSDServer nsdServer;
private Context context;
public NsdServerManager(Context context){
this.context=context;
}
public static NsdServerManager getInstance(Context context){
if(nsdServerManager==null){
synchronized (NsdServerManager.class){
if (nsdServerManager==null) {
nsdServer=new NSDServer();
nsdServerManager = new NsdServerManager(context);
}
}
}
return nsdServerManager;
}
/**
* 注册Nsd服务
* @param nsdServerName
*/
public void registerNsdServer(String nsdServerName) {
new Thread(new NsdServerRunnable(nsdServerName,nsdServer,context)).start();
}
/**
* 取消注册NsdServer
*/
public void unRegisterNsdServer(){
Log.e(TAG, "unRegisterNsdServer: " );
nsdServer.stopNSDServer();
}
}
调用代码:
package aoto.com.operationnsd;
import android.Manifest;
import android.net.nsd.NsdServiceInfo;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
/**
* 无论两端哪边先结束应用再打开 Netty 都可以 可实现断线重连
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = "ServerWhy";
TextView clientMessage;
TextView serverTitle;
EditText newNsdServerView;
NsdServerManager nsdServerManager;
/**
* 注册 NSD 服务的名称 和 端口 这个可以设置默认固定址,用于客户端通过 NSD_SERVER_NAME 筛选得到服务端地址和端口
*/
private String nsd_server_name = "WhySystem";
private int nsd_server_port = 8088;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initUI();
nsdServerManager = NsdServerManager.getInstance(this);
nsdServerManager.registerNsdServer(nsd_server_name);
}
private void initUI() {
clientMessage = findViewById(R.id.message_from_client);
serverTitle = findViewById(R.id.server_title);
serverTitle.append("----" + nsd_server_name);
newNsdServerView = findViewById(R.id.nsd_server_name);
}
/**
* 重置NSD服务器名称
*
* @param view
*/
public void resetServerName(View view) {
nsd_server_name = newNsdServerView.getText().toString();
serverTitle.setText("Nsd 服务端----" + nsd_server_name);
nsdServerManager.registerNsdServer(nsd_server_name);
}
public void unRegister(View view) {
nsdServerManager.unRegisterNsdServer();
}
@Override
protected void onDestroy() {
super.onDestroy();
nsdServerManager.unRegisterNsdServer();
}
}
客户端代码如下:
package aoto.com.nsdclient;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import java.util.ArrayList;
/**
* @author why
* @date 2019.04.28
*/
public class NsdClient {
public static final String TAG = "NsdClientWhy";
private final String NSD_SERVER_TYPE = "_http._tcp.";
private NsdManager.DiscoveryListener mDiscoveryListener;
private NsdManager.ResolveListener mResolverListener;
public NsdManager mNsdManager;
private Context mContext;
private String mServiceName;
private Handler mHandler;
private IServerFound mIServerFound;
private ArrayList discoveryList=new ArrayList<>();
private ArrayList resolveList=new ArrayList<>();
/**
* @param context:上下文对象
* @param serviceName 客户端扫描 指定的地址
* @param iServerFound 回调
*/
public NsdClient(Context context, String serviceName, IServerFound iServerFound) {
mContext = context;
mServiceName = serviceName;
mIServerFound = iServerFound;
}
public void startNSDClient(final Handler handler) {
new Thread(){
@Override
public void run() {
mHandler=handler;
mNsdManager = (NsdManager) mContext.getSystemService(Context.NSD_SERVICE);
initializeDiscoveryListener();//初始化监听器
initializeResolveListener();//初始化解析器
mNsdManager.discoverServices(NSD_SERVER_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);//开启扫描
}
}.start();
}
/**
* 扫描解析前的 NsdServiceInfo
*/
private void initializeDiscoveryListener() {
mDiscoveryListener = new NsdManager.DiscoveryListener() {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
mNsdManager.stopServiceDiscovery(this);
Log.e(TAG, "onStartDiscoveryFailed():");
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
mNsdManager.stopServiceDiscovery(this);
Log.e(TAG, "onStopDiscoveryFailed():");
}
@Override
public void onDiscoveryStarted(String serviceType) {
Log.e(TAG, "onDiscoveryStarted():");
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.e(TAG, "onDiscoveryStopped():");
}
/**
*
* @param serviceInfo
*/
@Override
public void onServiceFound(NsdServiceInfo serviceInfo) {
Log.e(TAG, "onServiceFound: "+serviceInfo );
discoveryList.add(serviceInfo.toString());
//根据咱服务器的定义名称,指定解析该 NsdServiceInfo
if (serviceInfo.getServiceName().equals(mServiceName)) {
mNsdManager.resolveService(serviceInfo, mResolverListener);
}
}
@Override
public void onServiceLost(NsdServiceInfo serviceInfo) {
Log.e(TAG, "onServiceLost(): serviceInfo=" + serviceInfo);
discoveryList.remove(serviceInfo.toString());
}
};
}
/**
* 解析发现的NsdServiceInfo
*/
private void initializeResolveListener() {
mResolverListener = new NsdManager.ResolveListener() {
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
int port = serviceInfo.getPort();
String serviceName = serviceInfo.getServiceName();
String hostAddress = serviceInfo.getHost().getHostAddress();
Message message=Message.obtain();
message.what=1;
message.obj=serviceInfo.toString();
mHandler.sendMessage(message);
Log.e(TAG, "onServiceResolved 已解析:" + " host:" + hostAddress + ":" + port + " ----- serviceName: " + serviceName);
resolveList.add(" host:" + hostAddress + ":" + port );
//TODO 建立网络连接
}
};
}
public void stopNSDServer() {
mNsdManager.stopServiceDiscovery(mDiscoveryListener);
}
public interface IServerFound {
/**
* 回調 指定解析的结果
*/
void onServerFound(NsdServiceInfo serviceInfo, int port);
/**
* 無合適 回調失敗
*/
void onServerFail();
}
}
对外接口管理类:
package aoto.com.nsdclient;
import android.content.Context;
import android.net.nsd.NsdServiceInfo;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
/**
* author:why
* created on: 2019/4/28 15:17
* description:
*/
public class NsdClientManager {
private static final String TAG = "NsdClientManagerWhy";
/**
* Nsd 客户端搜索
*/
private NsdClient nsdClient;
private Context context;
private Handler mHandler;
private volatile static NsdClientManager mNsdClientManager=null;
private NsdClientManager(Context context,Handler handler){
this.context=context;
this.mHandler=handler;
}
/**
* DCL Single Instance
* @param context
* @param handler
* @return
*/
public static NsdClientManager getInstance(Context context,Handler handler){
if(mNsdClientManager==null){
synchronized (NsdClientManager.class){
if(mNsdClientManager==null){
mNsdClientManager=new NsdClientManager(context,handler);
}
}
}
return mNsdClientManager;
}
/**
* 通过Nsd 搜索注册过的服务器相关参数进行Socket连接(IP和Port)
*/
public void searchNsdServer(final String nsdServerName) {
nsdClient = new NsdClient(context, nsdServerName, new NsdClient.IServerFound() {
@Override
public void onServerFound(NsdServiceInfo info, int port) {
if (info != null) {
Log.e(TAG, "onServerFound: "+info.toString() );
if (info.getServiceName().equals(nsdServerName)) {
//扫描到指定的server停止扫描
nsdClient.stopNSDServer();
}
}
}
@Override
public void onServerFail() {
}
});
nsdClient.startNSDClient(mHandler);
}
}
上面就是逻辑的主要代码,在我们获取到服务端的IP和Port之后,就可以通过网络技术进行连接和通讯了。需要注意的是,上面的搜索NsdService的时候,都是搜索特定名称的,对于服务丢失我也没有在客户端界面做相应的处理,这个可以根据具体的需求来设置和处理。此外,在测试的时候一定要处于同一网络下且不要忘了网络权限哟。结果不便于展示,可以自己下载源码测试调优。
注:欢迎扫码关注