You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 08:15:31