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 上的多点连接数据流问题的主要内容,如果未能解决你的问题,请参考以下文章