Vision Pro应用中Ping DNS主机8.8.8.8检测可达性的代码问题
问题排查:UDP Ping 8.8.8.8时无错误返回且收不到响应
我尝试通过Ping DNS主机8.8.8.8(UDP 53端口)检测主机可达性,使用了以下NetworkMonitor类代码。但遇到问题:即使主机不可达,connection.send函数也不返回错误,且connection.receiveMessage始终无返回,请求帮忙排查代码问题。
class NetworkMonitor { private let networkMonitor = NWPathMonitor() private var connection: NWConnection @MainActor var isHostReachable = false init(host: NWEndpoint.Host = "8.8.8.8", port: NWEndpoint.Port = 53) { let endpoint = NWEndpoint.hostPort(host: host, port: port) connection = NWConnection(to: endpoint, using: .udp) startMonitoring() } private func startMonitoring() { networkMonitor.pathUpdateHandler = { [weak self] path in guard let self else { return } ping(callback: { isSuccess in print("***** ping status:", isSuccess) Task { @MainActor in self.isHostReachable = isSuccess } }) } let queue = DispatchQueue(label: QueueLabel.networkMonitor) networkMonitor.start(queue: queue) } func ping( host: NWEndpoint.Host = "8.8.8.8", port: NWEndpoint.Port = 53, callback: @escaping ((Bool) -> Void) ) { var didSendState = false connection.stateUpdateHandler = {[weak self] state in guard let self = self else { return } guard !didSendState else { if state != .cancelled { cancel(connection) } return } switch state { case .ready: // State is ready now send data let content = "Ping".data(using: .utf8) let startTime = Date() connection.send(content: content, completion: .contentProcessed {[weak self] error in guard let self = self else { return } if error != nil { callback(false) didSendState = true cancel(connection) } else { print("Ping sent, waiting for response...") connection.receiveMessage { [weak self] content, _, _, receiveError in guard let self = self else { return } if let receiveError { print("Error receiving ping: \(receiveError.localizedDescription)") callback(false) } else if let content = content, String(data: content, encoding: .utf8) == "Ping" { let roundTripTime = Date().timeIntervalSince(startTime) print("Ping received! Round-trip time: \(roundTripTime) seconds") callback(true) } else { print("Invalid response received") callback(true) } didSendState = true cancel(connection) } } }) case .failed( _), .waiting( _), .cancelled: didSendState = true callback(false) case .setup, .preparing: // No callback because the ping has not yet succeeded or failed break @unknown default: didSendState = true callback(false) // We don't know what this unknown default means, so cancel pings to be safe cancel(connection) } } connection.start(queue: .main) } func cancel(_ connection: NWConnection) { connection.cancel() } }
问题根源分析
1. UDP协议的无连接特性
UDP是无确认的协议,connection.send的contentProcessed回调返回成功,仅表示数据已被系统网络栈接收,不代表数据已送达目标主机。即使目标主机不可达,ICMP "不可达"报文可能延迟到达、被防火墙拦截,导致send回调不会触发错误。
2. 缺少超时机制
当前代码没有设置响应超时,receiveMessage会无限等待响应。如果主机无响应,这个回调永远不会执行,didSendState保持false,后续的回调和资源清理逻辑都无法触发。
3. 连接对象复用冲突
NetworkMonitor初始化时创建了单个connection实例,每次ping调用都会覆盖其stateUpdateHandler并重复调用start。这种复用会导致状态回调逻辑混乱,比如旧连接的状态变化可能干扰新的ping请求。
4. 无效的Ping数据格式
你发送的是普通字符串"Ping",但8.8.8.8:53是DNS服务器,仅处理符合DNS协议规范的请求,不会返回"Ping"字符串。因此即使收到响应,也会被判定为"无效响应",错误地返回true。
修复后的代码实现
class NetworkMonitor { private let networkMonitor = NWPathMonitor() @MainActor var isHostReachable = false private let queue = DispatchQueue(label: "NetworkMonitorQueue") init() { startMonitoring() } private func startMonitoring() { networkMonitor.pathUpdateHandler = { [weak self] path in guard let self else { return } self.ping(host: "8.8.8.8", port: 53) { isSuccess in print("***** ping status:", isSuccess) Task { @MainActor in self.isHostReachable = isSuccess } } } networkMonitor.start(queue: queue) } func ping( host: NWEndpoint.Host = "8.8.8.8", port: NWEndpoint.Port = 53, timeout: TimeInterval = 3, callback: @escaping ((Bool) -> Void) ) { // 每次ping创建新连接,避免复用冲突 let endpoint = NWEndpoint.hostPort(host: host, port: port) let connection = NWConnection(to: endpoint, using: .udp) var didComplete = false // 超时逻辑 let timeoutTask = queue.asyncAfter(deadline: .now() + timeout) { guard !didComplete else { return } didComplete = true connection.cancel() callback(false) print("Ping timed out") } connection.stateUpdateHandler = { state in guard !didComplete else { connection.cancel() return } switch state { case .ready: // 发送合法的DNS查询请求(查询google.com的A记录) let dnsQuery = self.buildDNSQuery() connection.send(content: dnsQuery, completion: .contentProcessed { error in if let error { print("Send error: \(error.localizedDescription)") didComplete = true connection.cancel() callback(false) return } print("DNS query sent, waiting for response...") connection.receiveMessage { content, _, _, receiveError in defer { didComplete = true connection.cancel() timeoutTask.cancel() } if let receiveError { print("Receive error: \(receiveError.localizedDescription)") callback(false) return } // 验证DNS响应的合法性(仅检查是否有响应,不解析具体内容) if let content, content.count >= 12 { let id = content.subdata(in: 0..<2) // 响应ID应与请求ID一致 if id == dnsQuery.subdata(in: 0..<2) { print("Ping succeeded") callback(true) } else { print("Invalid DNS response ID") callback(false) } } else { print("Empty or invalid response") callback(false) } } }) case .failed(let error), .waiting(let error): print("Connection state error: \(error.localizedDescription)") didComplete = true connection.cancel() callback(false) case .cancelled: break case .setup, .preparing: break @unknown default: didComplete = true connection.cancel() callback(false) } } connection.start(queue: queue) } /// 构建简单的DNS A记录查询请求 private func buildDNSQuery() -> Data { var data = Data() // 随机请求ID(2字节) data.append(UInt8.random(in: 0...255)) data.append(UInt8.random(in: 0...255)) // 标志位:标准查询 data.append(0x01) data.append(0x00) // 问题数:1 data.append(0x00) data.append(0x01) // 回答数、权威记录数、附加记录数:0 data.append(0x00) data.append(0x00) data.append(0x00) data.append(0x00) data.append(0x00) data.append(0x00) // 查询域名:google.com(拆分标签) let domainParts = "google.com".split(separator: ".") for part in domainParts { data.append(UInt8(part.count)) data.append(contentsOf: part.utf8) } // 域名结束符 data.append(0x00) // 查询类型:A记录 data.append(0x00) data.append(0x01) // 查询类:IN data.append(0x00) data.append(0x01) return data } }
关键修复点说明
- 每次Ping创建新连接:避免复用旧连接导致的状态混乱,确保每个请求独立。
- 添加超时机制:3秒后自动取消连接并返回失败,解决无限等待问题。
- 发送合法DNS请求:构建符合DNS协议的查询报文,确保目标服务器会返回响应,避免无效响应误判。
- 完善资源清理:在回调、超时、错误场景下都确保连接被取消,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Pooja Gupta
相关产品推荐
相关产品推荐

