试图了解 L2CAP 通道

Posted

技术标签:

【中文标题】试图了解 L2CAP 通道【英文标题】:Trying to understand L2CAP channel 【发布时间】:2020-01-05 04:11:57 【问题描述】:

现在使用CoreBlueTooth L2CAP channel 面临一些挑战。为了更好地理解事物是如何工作的。我从 GitHub 获取了 L2CapDemo (master) (https://github.com/paulw11/L2CapDemo) 并尝试使用它。这是我所做的,还有一个问题。

已经用这个替换了 sendTextTapped 函数:

@IBAction func sendTextTapped(_ sender: UIButton) 
    guard let ostream = self.channel?.outputStream else 
        return
    

    var lngStr = "1234567890"
    for _ in 1...10 lngStr = lngStr + lngStr
    let data = lngStr.data(using: .utf8)!

    let bytesWritten =  data.withUnsafeBytes  ostream.write($0, maxLength: data.count) 
    print("bytesWritten = \(bytesWritten)")
    print("WR = \(bytesWritten) / \(data.count)")

而执行结果是:

bytesWritten = 8192
WR = 8192 / 10240

这让我可以看到在 bytesWritten

现在问题来了。问题是我什么也没看到,剩下的字节似乎被忽略了。 如果我不想忽略这些字节,我想知道该怎么做。关心其余字节的方法是什么?在某些情况下,我们需要传输数万甚至数十万字节。

【问题讨论】:

【参考方案1】:

您只需要记下发送了多少字符,从data 实例中删除这些字符,然后当您收到指示输出流中有可用空间的委托回调时,再发送一些。

例如,您可以添加几个属性来保存排队的数据和一个串行调度队列以确保对该队列的线程安全访问:

private var queueQueue = DispatchQueue(label: "queue queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem, target: nil)

private var outputData = Data()

现在,在sendTextTapped 函数中,您只需将新数据添加到输出队列:

@IBAction func sendTextTapped(_ sender: UIButton)      
    var lngStr = "1234567890"
    for _ in 1...10 lngStr = lngStr + lngStr
    let data = lngStr.data(using: .utf8)!

    self.queue(data:data)  

queue(data:)函数以线程安全的方式将数据添加到outputData对象并调用send()

private func queue(data: Data) 
    queueQueue.sync  
        self.outputData.append(data)
    
    self.send()   

send() 确保流已连接,有数据要发送并且输出流中有可用空间。如果一切正常,那么它会发送尽可能多的字节。然后从输出data 中删除发送的字节(同样以线程安全的方式)。

private func send() 

    guard let ostream = self.channel?.outputStream, !self.outputData.isEmpty, ostream.hasSpaceAvailable  else
        return
    
    let bytesWritten =  outputData.withUnsafeBytes  ostream.write($0, maxLength: self.outputData.count) 
    print("bytesWritten = \(bytesWritten)")
    queueQueue.sync 
        if bytesWritten < outputData.count 
            outputData = outputData.advanced(by: bytesWritten)
         else 
            outputData.removeAll()
        
    

最后的改变是调用send()来响应.hasSpaceAvailable流事件:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) 
    switch eventCode 
    case Stream.Event.openCompleted:
        print("Stream is open")
    case Stream.Event.endEncountered:
        print("End Encountered")
    case Stream.Event.hasBytesAvailable:
        print("Bytes are available")
    case Stream.Event.hasSpaceAvailable:
        print("Space is available")
        self.send()
    case Stream.Event.errorOccurred:
        print("Stream error")
    default:
        print("Unknown stream event")
    

可以在示例的largedata分支中看到修改后的代码

【讨论】:

还不错。这时候就得手动处理了。它的算法部分相当简单,但我只是认为可能有一些我不知道的自动处理。谢谢你说清楚。此外,由于我最终对传输二进制数据(而不仅仅是字符串)感兴趣,我稍后将不得不看看如何从 Data 对象中删除字节(并将它们添加到接收端)(我还不确定要使用的正确语法)。 上面的代码会发送任何数据;二进制数据与字符串没有什么特别之处。你需要考虑的是框架 - 你怎么知道这是流中“文件”的开始,这是“结束” - 你可能需要发送某种标题, 好的。太好了,我会花一些时间研究一下。

以上是关于试图了解 L2CAP 通道的主要内容,如果未能解决你的问题,请参考以下文章

将 L2CAP 通道与 CoreBluetooth 一起使用

L2CAP 通道,已发送但未到达的字节

蓝牙的L2CAP协议

BLE主机之L2CAP

Keycloak 反向通道注销

通道单一连接(会话)到事件处理程序映射