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

iOS设备Bonjour互联后InputStream未触发hasBytesAvailable问题

排查iOS Bonjour流通信中InputStream未触发hasBytesAvailable的问题

看起来你在iOS多设备Bonjour通信的流处理上踩了个常见的坑,我之前做类似的多设备连接管理功能时也遇到过几乎一样的问题,给你几个针对性的排查和解决方向:


1. 检查流的RunLoop调度与代理设置

InputStream的hasBytesAvailable事件完全依赖RunLoop的调度,如果流没有被正确加到RunLoop或者代理未设置,事件根本不会触发。在你的ASPeer类中,初始化流时必须做这几步:

class ASPeer {
    private let inputStream: InputStream
    private let outputStream: OutputStream
    
    init(inputStream: InputStream, outputStream: OutputStream) {
        self.inputStream = inputStream
        self.outputStream = outputStream
    }
    
    func setupStreams() {
        // 设置代理,确保ASPeer持有强引用,代理不失效
        inputStream.delegate = self
        outputStream.delegate = self
        
        // 把流加到当前线程的RunLoop,使用common模式保证在UI交互时也能触发事件
        inputStream.schedule(in: .current, forMode: .common)
        outputStream.schedule(in: .current, forMode: .common)
        
        // 打开流
        inputStream.open()
        outputStream.open()
    }
}

// 扩展实现StreamDelegate
extension ASPeer: StreamDelegate {
    func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
        // 打印所有事件,方便排查
        print("Peer stream event: \(eventCode.rawValue)")
        
        switch eventCode {
        case .hasBytesAvailable:
            guard let inputStream = aStream as? InputStream else { return }
            var buffer = [UInt8](repeating: 0, count: 1024)
            let bytesRead = inputStream.read(&buffer, maxLength: buffer.count)
            if bytesRead > 0 {
                let receivedData = Data(bytes: buffer, count: bytesRead)
                print("Received data from peer: \(receivedData)")
            }
        case .errorOccurred:
            print("Peer stream error: \(aStream.streamError?.localizedDescription ?? "Unknown error")")
        case .openCompleted:
            print("Peer stream opened successfully")
        default:
            break
        }
    }
}

⚠️ 注意:如果你的ASPeer是在后台线程初始化的,要确保那个线程的RunLoop是处于运行状态的,否则流事件不会被处理。尽量优先用主线程的RunLoop,除非你有明确的后台处理需求。


2. 确保OutputStream写入后触发刷新

很多时候数据已经写入OutputStream,但还停留在系统缓冲区里,没有真正发送到网络。写完数据后一定要调用flush():

// 在ASPeer中添加发送方法
func sendData(_ data: Data) {
    guard outputStream.streamStatus == .open else {
        print("Cannot send data: output stream is not open")
        return
    }
    
    let bytesWritten = data.withUnsafeBytes {
        outputStream.write($0.baseAddress!.assumingMemoryBound(to: UInt8.self), maxLength: data.count)
    }
    
    if bytesWritten > 0 {
        // 强制刷新缓冲区,确保数据发送出去
        outputStream.flush()
        print("Sent \(bytesWritten) bytes to peer")
    } else {
        print("Failed to write data: \(outputStream.streamError?.localizedDescription ?? "Unknown error")")
    }
}

3. 检查ASPeer的生命周期管理

如果你的ASPeer实例被意外释放(比如用了弱引用数组存储),那么流的代理会变成nil,自然收不到任何事件。确保存储连接的数组是强引用类型:

// 正确的存储方式:强引用数组
var connectedPeers: [ASPeer] = []

// 在Bonjour的连接回调中添加peer
func netService(_ sender: NetService, didAcceptConnectionWith inputStream: InputStream, outputStream: OutputStream) {
    let newPeer = ASPeer(inputStream: inputStream, outputStream: outputStream)
    newPeer.setupStreams()
    connectedPeers.append(newPeer)
    print("New peer connected, total peers: \(connectedPeers.count)")
}

另外,当流关闭或者出错时,记得从数组中移除对应的ASPeer,避免内存泄漏。


4. 排查Bonjour连接建立的时机

在NetServiceDelegate的netService(_:didAcceptConnectionWith:inputStream:outputStream:)回调中,必须立刻配置并打开流,不要延迟处理。如果回调返回的流没有被及时保留和初始化,可能会被系统回收,导致后续通信异常。

建议你在所有流事件的打印中,除了hasBytesAvailable,也要打印.errorOccurred、.openCompleted等事件,这样能快速定位到是流没打开,还是有错误发生,或者根本没收到数据。

内容的提问来源于stack exchange,提问作者Mike Zhao

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 08:59:01