Android NSD

网络服务发现 (NSD) 可让您的应用访问其他设备在本地网络上提供的服务。支持 NSD 的设备包括打印机、网络摄 像头、HTTPS 服务器以及其他移动设备。

NSD 实现了基于 DNS 的服务发现 (DNS-SD) 机制,该机制允许您的应用通过指定服务类型和提供所需类型服务的 设备实例的名称来请求服务。Android 和其他移动平台均支持 DNS-SD。

将 NSD 添加到应用中,可让您的用户识别本地网络上是否有其他设备支持您的应用所请求的服务。这对于各种点 对点应用非常有用,例如文件共享或多人游戏。Android 的 NSD API 简化了实现此类功能所需的工作。

本节课向您介绍如何构建应用,使其可向本地网络广播其名称和连接信息并扫描功能相同的其他应用中的信息。最后,本节课将介绍如何连接到在其他设备上运行的相同应用。

在网络上注册您的服务

注意:此步骤是可选的。如果您不想在本地网络上广播应用的服务,可以直接跳至下一部分发现网络上的服务。
如需在本地网络上注册服务,请先创建一个 NsdServiceInfo 对象。此对象提供网络上其他设备在决定是否连接 到您的服务时使用的信息。

KotlinJava

   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”。

注意:国际编号分配机构 (IANA) 负责管理服务发现协议(如 NSD 和 Bonjour)所使用的服务类型的集中式权威列 表。您可以从 IANA 服务名称和端口号列表中下载该列表。如果您打算使用新的服务类型,则应填写 IANA 端口和 服务注册表预订该服务。
为服务设置端口时,请避免对其进行硬编码,因为这会与其他应用冲突。例如,假设您的应用始终使用端口 1337,这可能会与使用相同端口的其他已安装应用发生冲突。请改用设备的下一个可用端口。由于此信息通过服 务广播提供给其他应用,因此在编译时无需让其他应用知道您的应用所使用的端口。应用可以在即将连接到您的服 务之前通过您的服务广播获取此信息。
如果您使用套接字,只需将套接字设置为 0 即可将套接字初始化为任何可用端口,如下所示。

KotlinJava

       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 用于向您 的应用提醒服务注册和取消注册是否成功的回调。
#KotlinJava
      {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.
}

  errorCode) {
            }
}; }
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int
    // Unregistration failed. Put debugging code here to determine why.

现在,您已具备注册服务的所有条件。调用 registerService() 方法。 请注意,此方法是异步的,因此任何需要在服务注册后运行的代码都必须包含在 onServiceRegistered() 方法
中。
KotlinJava

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

发现网络上的服务

网络充满生机,既有凶猛的网络打印机,也有温顺的网络摄像头,还有附近井字棋游戏玩家残酷而激烈的战斗。服
务发现是让应用看到这个充满活力的功能生态系统的关键所在。您的应用需要监听网络上的服务广播以查看可用的
服务,并过滤掉应用无法使用的任何服务。
与服务注册一样,服务发现包括两个步骤:设置具有相关回调的发现监听器,以及对 discoverServices() 进行 单个异步 API 调用。
首先,实例化一个实现 NsdManager.DiscoveryListener 的匿名类。以下代码段展示了一个简单的示例:
KotlinJava

       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() ,传入应用应查找的服务类型、要使用的发现协议以及您刚创建的监 听器。
    KotlinJava

连接到网络上的服务

当应用在网络上找到要连接的服务时,它必须首先使用 resolveService() 方法确定该服务的连接信息。实现 NsdManager.ResolveListener 以传入此方法,并使用它获取包含连接信息的 NsdServiceInfo 。
KotlinJava

     nsdManager.discoverServices(
        SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
        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 功能非常重要。当应用关闭时将其取消注册,有助于防止其他应用认 为它仍处于活动状态并尝试连接到该应用。此外,服务发现是一项开销很大的操作,应在父级 Activity 暂停时停 止,并在 Activity 恢复后重新启用。替换主 Activity 的生命周期方法,并插入代码以酌情启动和停止服务广播及发 现。
KotlinJava

     //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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容