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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 04:23:11