Wifi 上的多点连接数据流问题

Posted

技术标签:

【中文标题】Wifi 上的多点连接数据流问题【英文标题】:Multipeer connectivity data stream issue over Wifi 【发布时间】:2020-09-11 20:10:48 【问题描述】:

我正在使用多点连接在通过 Wifi 连接的多个设备之间同步 3D SceneKit 环境中节点的移动。

节点移动在主设备上计算并发送到从设备,然后将节点设置到接收到的位置。移动通过主设备的 SceneView 的渲染器循环发送,并通过每个从设备打开并由 MultipeerConnectivity 框架管理的数据流发送。

下面的视频显示了结果,以及由于在通过流接收数据时每 0.6-0.7 秒定期暂停而导致从属设备(右)上的球抖动的问题。左上角的计数器显示没有丢包。接收到的数据的完整性也没有问题。

从属设备上这种非常有规律的暂停不会出现在模拟器中,但只有当它在真实设备上运行时才会出现,无论是什么设备(iPhone 或 Ipad,旧的或最近的)。

有没有办法找出导致从属设备出现这种定期暂停的原因?

让slave上的输入流在专用线程/runloop而不是主线程的runloop上执行是否有意义?

下面的实现

Master Multipeer Connectivity 初始化

    PeerID = MCPeerID(displayName: "Master (" + UIDevice.current.name + ")")
    MPCSession = MCSession(peer: PeerID, securityIdentity: nil, encryptionPreference: .none)
    MPCSession.delegate = self
    
    ServiceAdvertiser = MCNearbyServiceAdvertiser(peer: PeerID, discoveryInfo: nil, serviceType: "ARMvt")
    ServiceAdvertiser.delegate = self
    ServiceAdvertiser.startAdvertisingPeer()
    

Slave Multipeer Connectivity 初始化

    PeerID = MCPeerID(displayName: "Slave (" + UIDevice.current.name + ")")
    MPCSession = MCSession(peer: PeerID, securityIdentity: nil, encryptionPreference: .none)
    MPCSession.delegate = self
    
    ServiceBrowser = MCNearbyServiceBrowser(peer: PeerID,  serviceType: "ARMvt")
    ServiceBrowser.delegate = self
    ServiceBrowser.startBrowsingForPeers()

发送数据的函数,在渲染函数中调用

func MPCSendData(VCRef: GameViewController, DataToSend: Dictionary<String, Any>, ViaStream: Bool = false)

    var DataFilledToSend = DataToSend
    var DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: true)
    var TailleData: Int = 0
    var NewTailleData: Int = 0
    
            
    if ViaStream    // Through the stream
    
            // Filling data to have a constant size packet
            // kSizeDataPack is set to 2048. The bigger it is the worst is the jittering.
        VCRef.Compteur = VCRef.Compteur + 1
        VCRef.Message.SetText(Text: String(VCRef.Compteur))
        DataFilledToSend[eTypeData.Compteur.rawValue] = VCRef.Compteur
        DataFilledToSend[eTypeData.FillingData.rawValue] = "A"
        TailleData = DataConverted.count
        DataFilledToSend[eTypeData.FillingData.rawValue] = String(repeating: "A", count: kSizeDataPack - TailleData)
        DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: false)
        NewTailleData = DataConverted.count
        DataFilledToSend[eTypeData.FillingData.rawValue] = String(repeating: "A", count: kSizeDataPack - TailleData - (NewTailleData - kSizeDataPack))

        DataConverted = try! NSKeyedArchiver.archivedData(withRootObject: DataFilledToSend, requiringSecureCoding: false)
        
        if VCRef.OutStream!.hasSpaceAvailable
        
            let bytesWritten = DataConverted.withUnsafeBytes  VCRef.OutStream!.write($0, maxLength: DataConverted.count) 

            if bytesWritten == -1  print("Erreur send stream") 
         else  print("No space in stream") 
    
    else    // Not through the stream
    
        let Peer = VCRef.MPCSession.connectedPeers.first!
        try! VCRef.MPCSession.send(DataConverted, toPeers: [Peer], with: .reliable)
    

通过slave上的流接收数据时调用的函数

func stream(_ aStream: Stream, handle eventCode: Stream.Event)

    DispatchQueue.main.async
    
        switch(eventCode)
        
        case Stream.Event.hasBytesAvailable:
            let InputStream = aStream as! InputStream
            var Buffer = [UInt8](repeating: 0, count: kSizeDataPack)
            let NumberBytes = InputStream.read(&Buffer, maxLength: kSizeDataPack)
            let DataString = NSData(bytes: &Buffer, length: NumberBytes)
            if let _ = NSKeyedUnarchiver.unarchiveObject(with: DataString as Data) as? [String:Any] //deserializing the NSData
            
                ProcessMPCDataReceived(VCRef: self, RawData: DataString as Data)
            
            
        case Stream.Event.hasSpaceAvailable:
            break
            
        case Stream.Event.errorOccurred:
            print("ErrorOccurred: \(String(describing: aStream.streamError?.localizedDescription))")
            
        default:
            break
        
    

处理接收到的数据的函数

func ProcessMPCDataReceived(VCRef: GameViewController, RawData: Data)

    let DataReceived: Dictionary = (try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(RawData) as! [String : Any])

    switch DataReceived[eTypeData.EventType.rawValue] as! String
    
    case eTypeEvent.SetMovement.rawValue:
        VCRef.CurrentMovement = eTypeMovement(rawValue: DataReceived[eTypeData.Movement.rawValue] as! String)!
     
    case eTypeEvent.SetPosition.rawValue:
        VCRef.Ball.position = DataReceived[eTypeData.Position.rawValue] as! SCNVector3
            
    default:
        break
    

【问题讨论】:

【参考方案1】:

看起来您正在主线程上分派工作。虽然我不希望解包数据真的会导致这些定期暂停,但您可能会遇到数据处理瓶颈。换句话说,接收、解包和重新定位节点(这也会导致隐式 SceneKit 事务)所花费的时间可能足以让设备变慢。看起来您的代码足够紧凑,可以让您将整个事情分派到队列中。我建议您尝试一下,看看您是否有任何新行为。试试DispatchQueue.global(),或者更好的是,使用DispatchQueue(label:"StreamReceiver", qos: .userInteractive) 自己制作。我认为异步调度在这里非常好。

编辑:实际上,看更多,我认为这可能与 SceneKit 事务有关。看起来你并不是真的在“暂停”,而是在减速。我提到了隐式事务 - 当您定位节点时,显式启动、设置动画时间并结束 SCNTransaction。我一直在使用的一个片段是:

func sceneTransaction(_ duration: Int? = nil,
                      _ operation: () -> Void) 
    SCNTransaction.begin()
    SCNTransaction.animationDuration =
        duration.map CFTimeInterval($0) 
            ?? SCNTransaction.animationDuration
    operation()
    SCNTransaction.commit()

尝试使用您的重新定位代码调用它,或者只是将事务开始/动画时间/结束粘贴在您的块周围。祝你好运!

编辑 2:好的,还有一件事。如果这对您的用例有意义,请确保您已停止为同行浏览和广告。这是一种昂贵的网络连接,它可能会使整个子系统陷入困境。

【讨论】:

感谢您的 cmets/反馈。一些补充: 1)视频不清晰,但从设备上的球确实在暂停,而不仅仅是减速。如果你增加发送的数据包的大小,它会变得更糟 2) 浏览器和广告商确实停止了 3) 尝试了建议的调度和交易选项,但没有运气 4) 完整代码可在此处获得:github.com/JonahBegood/MultiPeerios

以上是关于Wifi 上的多点连接数据流问题的主要内容,如果未能解决你的问题,请参考以下文章

多点连接范围和非 ios 设备

updateChat上的ios 8多点连接错误

从多对等连接中禁用 WiFi

在移动游戏多点网络中,大约 0.5 秒的延迟是常见的吗?

在 Windows 上的 Java 中以编程方式创建 wifi 对等连接? [关闭]

使用 iOS 中的多点连接框架一次可以连接多少台设备?