Bonjour

news2025/7/21 5:45:21

Bonjour 是苹果的一套零配置网络协议,用于发现局域网内的其他设备并进行通信,比如发现打印机、手机、电视等。

一句话:发现局域网其他设备和让其他设备发现。

Bonjour 可以完成的工作

  • IP 获取
  • 名称解析
  • 搜索服务

2048

实际应用场景示例,HomeKit/Matter 智能设备入网后,需要通过 Bonjour 来发现设备,获取 ip,进行 http 局域网通信,获取只能设备信息,绑定用户账号等。

权限要求

Bonjour 功能需要在info.plist 开启本地网络,和指定 services。

截屏2022-02-25 下午3.17.41

Tips: _hap._tcp遵循了 HAP(HomeKie Accessory Protocol) 智能家居协议的硬件,会在这个服务发送数据。

类似的,Matter 配件使用服务类型为_matter._tcp

检测本地网络是否权限是否开启

苹果并没有提供本地网络权限回调和查询,所有我们并不知道用户是否拒绝了本地网络权限。

可以在StackOverflow的How to check local network permission in iOS14,iOS 14 How to trigger Local Network dialog and check user answer?中找到检测代码,还是有效的。

Bonjour 扫描服务

通过 Bonjour 发现设备的实现分为以下情况

iOS2.0 - 15.0: NSNetServiceBrowser

iOS13.0+: NWBrowser (暂不知解析 ip 方式)

虽然按照苹果的习惯,被标记过期的接口,在未来很长时间都可以使用。但是也说明苹果提供了其他方式(NWBrowser)来实现 Bonjour

流程

先搜索服务,再连接服务,最后解析ip等信息。

NSNetserviceBrowser

在 iOS2.0到 iOS15.0系统中,Bonjour 可与通过 Foundation -> Bonjour 框架中的 NetserviceBrowser 来实现扫描和服务处理。

示例代码

开始扫描

@property (nonatomic, strong) NSNetServiceBrowser *brower;

- (void)startSearch {
    //[self.services removeAllObjects];
    //[self.scanDevices removeAllObjects];
    self.brower = [[NSNetServiceBrowser alloc] init];
    self.brower.delegate = self;
    [self.brower stop];
    [self.brower searchForServicesOfType:@"_hap._tcp" inDomain:@"local."];
}

NetServiceBrowserDelegate代理

/* 
 * 即将查找服务
 */
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser {
    NSLog(@"-----------------netServiceBrowserWillSearch");
}

/* 
 * 停止查找服务
 */
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
    NSLog(@"-----------------netServiceBrowserDidStopSearch");
}

/* 
 * 查找服务失败
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict {
    NSLog(@"----------------netServiceBrowser didNotSearch");
}

/*
 * 发现域名服务
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindDomain:(NSString *)domainString moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didFindDomain");
  // 过滤服务 添加到数组中(避免服务被释放,不走代理),连接服务
  [self.services addObject:service];//此处添加为了避免sevice被过早释放,不走代理方法
    service.delegate = self;
    //解析服务
    [service resolveWithTimeout:5];
}

/* 
 * 发现客户端服务
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didFindService:(NSNetService *)service moreComing:(BOOL)moreComing {
    NSLog(@"didFindService---------=%@  =%@  =%@",service.name,service.addresses,service.hostName);

  [aNetService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  aNetService.delegate = self;
  [aNetService resolveWithTimeout:6];
  CFRunLoopRun();

}

/* 
 * 域名服务移除
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveDomain:(NSString *)domainString moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didRemoveDomain");   
}

/* 
 * 客户端服务移除
 */
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser didRemoveService:(NSNetService *)service moreComing:(BOOL)moreComing {
    NSLog(@"---------------netServiceBrowser didRemoveService");
}

NSNetserviceDelegate

- (void)netServiceDidResolveAddress:(NSNetService *)sender{
  // 连接到服务,可以从 sender 中获取网络信息 比如 ip4 ip6 port 等
  // 进行数据通信,比如 http数据交互
  
}

停止扫描

在业务中应该在一段时间后或者触发某些情况下关闭扫描,避免一致扫描。

[self.brower stop];

从 NSNetservice中解析网络数据

- (NSDictionary *)parsingIP:(NSNetService *)sender{
    int sPort = 0;
    NSString *ipv4;
    NSString *ipv6;
    for (NSData *address in [sender addresses]) {
        typedef union {
            struct sockaddr sa;
            struct sockaddr_in ipv4;
            struct sockaddr_in6 ipv6;
        } ip_socket_address;
        
        struct sockaddr *socketAddr = (struct sockaddr*)[address bytes];
        if(socketAddr->sa_family == AF_INET) {
            sPort = ntohs(((struct sockaddr_in *)socketAddr)->sin_port);
            struct sockaddr_in* pV4Addr = (struct sockaddr_in*)socketAddr;
            int ipAddr = pV4Addr->sin_addr.s_addr;
            char str[INET_ADDRSTRLEN];
            ipv4 = [NSString stringWithUTF8String:inet_ntop( AF_INET, &ipAddr, str, INET_ADDRSTRLEN )];
        }
        
        else if(socketAddr->sa_family == AF_INET6) {
            sPort = ntohs(((struct sockaddr_in6 *)socketAddr)->sin6_port);
            struct sockaddr_in6* pV6Addr = (struct sockaddr_in6*)socketAddr;
            char str[INET6_ADDRSTRLEN];
            ipv6 = [NSString stringWithUTF8String:inet_ntop( AF_INET6, &pV6Addr->sin6_addr, str, INET6_ADDRSTRLEN )];
        }
        else {
            NSLog(@"Socket Family neither IPv4 or IPv6, can't handle...");
        }
    }
    
    NSDictionary *data = @{@"type": [sender type],
                           @"domain": [sender domain],
                           @"name": [sender name],
                           @"ipv4": ipv4,
                           @"ipv6": ipv6,
                           @"port": [NSNumber numberWithInt:sPort]};
    return data;
}

NWBrowser

在iOS13+在NetWork 框架中提供了 NWBrowser 类来实现 Bonjour,且只支持 Swfit.

示例代码

import Foundation
import Network
@available(iOS 13.0, *)
@objc
class BonjourManager: NSObject {
    @objc
    static let shared = BonjourManager()
    let browser = NWBrowser(for: .bonjourWithTXTRecord(type:"_hap._tcp", domain: ""), using: NWParameters())
    /// 搜索, 先搜索,再连接,才能解析到网络信息
    @objc
    func start() {
        browser.browseResultsChangedHandler = { (results, changes) in
            print("Results:")
            
            for result in results // 建议存在数组中,注意去重
            {
                
                if case .service(let name,let type,let domain,let interface) = result.endpoint
                {
                    // Bonjour Service
                    debugPrint("Bonjour设备:\(name)")
                    // 条件过滤
                        let connection = NWConnection(to: result.endpoint, using: .tcp)
                        connection.stateUpdateHandler = { state in
                            switch state {
                            case .ready:
                                if let innerEndpoint = connection.currentPath?.remoteEndpoint, case .hostPort(let host, let port) = innerEndpoint, case .ipv4(let ip4) = host {
                                    switch host {
                                    case .ipv4( let ip4):
                                        print("Bonjour \(name) ip4:\(ip4.debugDescription)")
                                    case .ipv6(let ip6):
                                        print("Bonjour ip6:\(ip6.debugDescription)")
                                    default:
                                        print("Bonjour host:\(host.debugDescription)")
                                    }
                                    
                                }
                            default :
                                break
                            }
                            // 断开连接 connection.cancel()
                        }
                        connection.start(queue: .main)
                } else if case .hostPort(let host, let port) = result.endpoint {
                    debugPrint("\(host)")
                }
                else
                {
                    assert(false, "This nevers gets executed")
                }
            }
            
            //            print("Changes:")
            //
            //            for change in changes
            //            {
            //                if case .added(let added) = change
            //                {
            //                    if case .service(let service) = added.endpoint
            //                    {
            //                        debugPrint(service)
            //                    }
            //                    else
            //                    {
            //                        assert(false, "This nevers gets executed")
            //                    }
            //                }
            //            }
        }
        browser.start(queue: .main)
    }
    /// 取消
    @objc
    func cancel() {
        browser.cancel()
    }
}

参考

官方资料

Support local network privacy in app

WWDC2020视频资料,介绍为什么需要本地网络权限,怎么设置权限,什么情况下可以不使用访问本地网络,而使用其他技术。其中以Boujour技术举例对本地网络权限的使用。

如何在你的 App 中使用组播网络

通过组播网络查看其他设备和通信。

组播在其他平台可能称为:组播DNS、mDNS 或者 DNS 服务发现

介绍本地网络权限和 Boujour info.plist 配置。以及如何利用 NWConnectionGroup 进行组播数据发送与接收。

如何获取 NWEndpoint 的 ip 信息 – stackoverflow

该问题提供了 iOS14 通过 NWBrowser 使用 Bonjour 的方式。虽然得到的是 NWEndpoint,但是可以通过 NWConnection来获取 ip 信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392841.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为云Flexus+DeepSeek征文 | 基于Dify和DeepSeek-R1开发企业级AI Agent全流程指南

作者简介 我是摘星&#xff0c;一名专注于云计算和AI技术的开发者。本次通过华为云MaaS平台体验DeepSeek系列模型&#xff0c;将实际使用经验分享给大家&#xff0c;希望能帮助开发者快速掌握华为云AI服务的核心能力。 目录 1. 前言 2. 环境准备 2.1 华为云资源准备 2.1 实…

HarmonyOS-ArkUI固定样式弹窗(1)

固定样式弹窗指的就是ArkUI中为我们提供的一些具备界面模板性质的弹窗。样式是固定的,我们可以决定在这些模板里输入什么样的内容。常见的有,警告弹窗, 列表选择弹窗, 选择器弹窗,对话框,操作菜单。 下图是本文中要讲到的基类固定样式弹窗,其中选择器弹窗没有包含在内,…

痉挛性斜颈相关内容说明

一、颈部姿态的异常偏移​ 痉挛性斜颈会打破颈部原本自然笔直的状态&#xff0c;让颈部像被无形的力量牵引&#xff0c;出现不自主的歪斜、扭转。它就像打乱了颈部原本和谐的 “平衡游戏”&#xff0c;使得颈部姿态偏离正常&#xff0c;影响日常的体态与活动。​ 二、容易察觉…

【25-cv-05917】HSP律所代理Le Petit Prince 小王子商标维权案

Le Petit Prince 小王子 案件号&#xff1a;25-cv-05917 立案时间&#xff1a;2025年5月28日 原告&#xff1a;SOCIETE POUR LOEUVRE ET LA MEMOIRE DANTOINE DE SAINT EXUPERY - SUCCESSION DE SAINT EXUPERY-DAGAY 代理律所&#xff1a;HSP 原告介绍 《小王子》&#x…

vue3 ElMessage提示语换行渲染

在 ElMessage.error 中使用换行符 \n 并不会实现换行&#xff0c;因为 ElMessage 默认会将字符串中的换行符忽略。要实现换行显示&#xff0c;可以使用 HTML 标签 <br> 并结合 ElMessage 的 dangerouslyUseHTMLString 选项。以下是实现换行提示的代码示例&#xff1a; i…

Java 微服务架构设计:服务拆分与服务发现的策略

Java 微服务架构设计&#xff1a;服务拆分与服务发现的策略 微服务架构作为一种热门的软件架构风格&#xff0c;在 Java 领域有着广泛的应用。它通过将系统拆分为一组小型服务来实现更灵活、可扩展的系统设计。在微服务架构中&#xff0c;服务拆分和服务发现是两个关键环节。本…

华为OD机试真题——二叉树中序遍历(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《二叉树中序遍历》: 目录 …

VSCode + GD32F407 构建烧录

前言 最近调试一块 GD32F407VET6&#xff08;168Mhz&#xff0c;8Mhz晶振&#xff09; 板子时&#xff0c;踩了一些“启动失败”的坑。本以为是时钟配置有误&#xff0c;最后发现是链接脚本&#xff08;.ld 文件&#xff09;没有配置好&#xff0c;导致程序根本没能正常执行 ma…

Linux研学-入门命令

一 目录介绍 1 介绍 Linux与Windows在目录结构组织上差异显著&#xff1a;Linux采用树型目录结构&#xff0c;以单一根目录/为起点&#xff0c;所有文件和子目录由此向下延伸形成层级体系&#xff0c;功能明确的目录各司其职&#xff0c;使文件系统层次清晰、逻辑连贯&#xf…

设计模式之结构型:桥接模式

桥接模式(Bridge Pattern) 定义 桥接模式是一种​​结构型设计模式​​&#xff0c;通过​​将抽象部分与实现部分分离​​&#xff0c;使它们可以独立变化。它通过组合代替继承&#xff0c;解决多层继承导致的类爆炸问题&#xff0c;适用于​​多维度变化​​的场景(如形状与颜…

监控 Oracle Cloud 负载均衡器:使用 Applications Manager 释放最佳性能

设想你正在运营一个受欢迎的在线学习平台&#xff0c;在考试前的高峰期&#xff0c;平台流量激增。全球的学生同时登录&#xff0c;观看视频、提交作业和参加测试。如果 Oracle Cloud 负载均衡器不能高效地分配流量&#xff0c;或者后端服务器难以应对负载&#xff0c;学生可能…

早发现=早安心!超导心磁图如何捕捉早期病变信号?

随着生活节奏的加快&#xff0c;心血管疾病已成为威胁人们健康的“隐形杀手”。据国家心血管病中心发布的《中国心血管健康与疾病报告2022》显示&#xff0c;我国心血管病现患者人数已高达3.3亿&#xff0c;每5例死亡中就有2例死于心血管病。这一数据触目惊心&#xff0c;提醒我…

使用Vditor将Markdown文档渲染成网页(Vite+JS+Vditor)

1. 引言 编写Markdown文档现在可以说是程序员的必备技能了&#xff0c;因为Markdown很好地实现了内容与排版分离&#xff0c;可以让程序员更专注于内容的创作。现在很多技术文档&#xff0c;博客发布甚至AI文字输出的内容都是以Markdown格式的形式输出的。那么&#xff0c;Mar…

OPC Client第6讲(wxwidgets):Logger.h日志记录文件(单例模式);登录后的主界面

接上一讲三、2、2>4》&#xff0c;创建logger.h和helper_t.h里的gettime函数 即解决下图的报红 同时&#xff0c;接上一讲二、3、点击“确认”按钮后&#xff0c;进入MainFrame.h对应的下述界面&#xff0c;此讲下图进行实现 一、创建Logger.h&#xff1a;日志记录文件&…

单细胞注释前沿:CASSIA——无参考、可解释、自动化细胞注释的大语言模型

细胞类型注释是单细胞RNA-seq分析的重要步骤&#xff0c;目前有许多注释方法。大多数注释方法都需要计算和特定领域专业知识的结合&#xff0c;而且经常产生不一致的结果&#xff0c;难以解释。大语言模型有可能在减少人工输入和提高准确性的同时扩大可访问性&#xff0c;但现有…

历年武汉大学计算机保研上机真题

2025武汉大学计算机保研上机真题 2024武汉大学计算机保研上机真题 2023武汉大学计算机保研上机真题 在线测评链接&#xff1a;https://pgcode.cn/school 分段函数计算 题目描述 写程序计算如下分段函数&#xff1a; 当 x > 0 x > 0 x>0 时&#xff0c; f ( x ) …

AR-HUD 光波导方案优化难题待解?OAS 光学软件来破局

波导-HUD系统案例分析 简介 光波导技术凭借其平板超薄结构和强大的二维扩展能力&#xff0c;在解决AR-HUD问题方面展现出显著优势。一方面&#xff0c;其独特的结构特性能够大幅减小对光机体积的需求&#xff0c;成为 HUD 未来发展的重要技术方向&#xff1b;另一方面&#xf…

火狐安装自动录制表单教程——仙盟自动化运营大衍灵机——仙盟创梦IDE

打开火狐插件页面 安装完成 使用 功能 录制浏览器操作 录入地址 开始操作 录制完成 在当今快速发展的软件开发生态中&#xff0c;自动化测试已从一种新兴技术手段&#xff0c;转变为保障软件质量与开发效率不可或缺的关键环节。其重要性体现在多个维度&#xff0c;同时&#x…

线程池的详细知识(含有工厂模式)

前言 下午学习了线程池的知识。重点探究了ThreadPoolExecutor里面的各种参数的含义。我详细了解了这部分的知识。其中有一个参数涉及工厂模式&#xff0c;我将这一部分知识分享给大家~ 线程池的详细介绍(含工厂模式) 结语 分享到此结束啦。byebye~

木愚科技闪亮第63届高博会 全栈式智能教育解决方案助力教学升级

5月23日&#xff0c;第63届高等教育博览会在长春东北亚国际博览中心开幕&#xff0c;木愚科技积极筹备&#xff0c;奔赴展会现场。彼时&#xff0c;木愚科技企业领导及相关职能部门负责人亲临展位指导工作&#xff0c;通过特装展位、资料发放及现场交流等方式&#xff0c;全方位…