如何使用Apple Network Framework扫描网络中所有设备的IP地址?
基于Network框架实现网络设备扫描方案
首先明确:NWBrowser确实仅针对Bonjour服务发现,它没法直接扫描整个网段的所有设备——因为它依赖设备主动广播的Bonjour服务,而非主动探测IP存活。要实现类似App Store里IP Scanner的全设备扫描,得结合NWConnection主动探测,再用NWBrowser补充设备的Bonjour相关信息。
核心实现思路
- 先通过
getifaddrs获取本地设备的网络接口信息,计算出当前网段(比如192.168.1.0/24),这是确定扫描范围的基础。 - 遍历网段内的所有IP地址,用
NWConnection尝试建立TCP连接(比如扫常用端口80、443,ICMP在iOS/macOS上权限限制多,TCP更易实现),判断设备是否存活。 - 同时启动
NWBrowser监听Bonjour服务,收集设备的主机名、服务类型等额外信息,和扫描到的IP做关联,丰富设备详情。
示例代码片段
1. 获取本地网段(基于getifaddrs)
import Foundation import Network func getLocalSubnets() -> [String] { var subnets = [String]() var ifaddr: UnsafeMutablePointer<ifaddrs>? guard getifaddrs(&ifaddr) == 0 else { return subnets } defer { freeifaddrs(ifaddr) } var ptr = ifaddr while ptr != nil { let flags = Int32(ptr!.pointee.ifa_flags) let addrFamily = ptr!.pointee.ifa_addr.pointee.sa_family if (flags & (IFF_UP | IFF_RUNNING | IFF_LOOPBACK)) == (IFF_UP | IFF_RUNNING) && addrFamily == AF_INET { var addr = ptr!.pointee.ifa_addr.pointee var netmask = ptr!.pointee.ifa_netmask.pointee var ipAddr = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) var maskAddr = [CChar](repeating: 0, count: Int(INET_ADDRSTRLEN)) inet_ntop(AF_INET, &addr.sa_data.1, &ipAddr, socklen_t(INET_ADDRSTRLEN)) inet_ntop(AF_INET, &netmask.sa_data.1, &maskAddr, socklen_t(INET_ADDRSTRLEN)) let ipString = String(cString: ipAddr) let maskString = String(cString: maskAddr) if let subnet = calculateSubnet(ip: ipString, mask: maskString) { subnets.append(subnet) } } ptr = ptr!.pointee.ifa_next } return subnets } func calculateSubnet(ip: String, mask: String) -> String? { guard let ipParts = ip.split(separator: ".").compactMap({ Int($0) }), let maskParts = mask.split(separator: ".").compactMap({ Int($0) }), ipParts.count == 4, maskParts.count == 4 else { return nil } let subnetParts = zip(ipParts, maskParts).map { $0 & $1 } return subnetParts.map { String($0) }.joined(separator: ".") + "/24" // 简化处理,实际需根据掩码计算前缀 }
2. 用NWConnection主动探测IP存活
func scanIP(_ ip: String, port: UInt16 = 80, completion: @escaping (String, Bool) -> Void) { let host = NWEndpoint.Host(ip) let port = NWEndpoint.Port(rawValue: port)! let connection = NWConnection(host: host, port: port, using: .tcp) connection.stateUpdateHandler = { state in switch state { case .ready: completion(ip, true) connection.cancel() case .failed, .cancelled: completion(ip, false) default: break } } connection.start(queue: .global()) // 设置超时,避免等待过久 DispatchQueue.global().asyncAfter(deadline: .now() + 1) { if connection.state != .ready { connection.cancel() completion(ip, false) } } } // 遍历网段扫描 func scanSubnet(_ subnet: String) { guard let baseIP = subnet.split(separator: "/").first, let parts = baseIP.split(separator: ".").compactMap({ Int($0) }), parts.count == 3 else { return } for i in 1...254 { let ip = "\(parts[0]).\(parts[1]).\(parts[2]).\(i)" scanIP(ip) { ip, isAlive in if isAlive { print("发现存活设备: \(ip)") // 这里可以结合NWBrowser的结果补充信息 } } } }
3. 用NWBrowser发现Bonjour设备
func startBonjourBrowser() { let browser = NWBrowser(for: .bonjour(type: "_http._tcp", domain: nil), using: .tcp) browser.stateUpdateHandler = { state in if state == .ready { print("Bonjour浏览器已启动") } } browser.browseResultsChangedHandler = { results, changes in for result in results { guard let endpoint = result.endpoint as? NWEndpoint.HostPort, let ip = endpoint.host.debugDescription.split(separator: "/").last else { continue } let hostname = result.name print("Bonjour设备: \(hostname) - \(ip)") // 可以将此信息和之前扫描到的IP关联,补充设备名称 } } browser.start(queue: .global()) }
注意事项
- TCP扫描建议选择常用端口,避免扫过多端口导致性能问题或被防火墙拦截。
- macOS上如果需要ICMP扫描(ping),需要申请
com.apple.security.network.ping权限;iOS上无法直接使用ICMP,只能用TCP/UDP探测。 - NWBrowser只能发现开启了Bonjour服务的设备,必须和主动扫描结合才能覆盖所有设备,这也是IP Scanner类应用的常规做法。
内容的提问来源于stack exchange,提问作者zumzum
相关产品推荐
相关产品推荐

