通过 Multipeer Framework 临时同步两个客户端

Posted

技术标签:

【中文标题】通过 Multipeer Framework 临时同步两个客户端【英文标题】:Synchronize two clients temporally over Multipeer Framework 【发布时间】:2014-05-06 16:43:12 【问题描述】:

我已经解决这个问题几天了,但我的解决方案都不够用。我认为我缺乏实现这一点的理论知识,并且希望得到一些建议(不一定是特定于 ios 的——我可以将 C、伪代码等任何东西翻译成我需要的东西)。

基本上,我有两部 iPhone。当用户按下按钮时,任何一个都可以触发重复动作。然后它需要通知另一部 iPhone(通过 MultiPeer 框架)来触发相同的动作……但它们都需要在同一时刻开始并保持同步。我真的需要达到 1/100 秒的准确度,我认为在这个平台上是可以实现的。

作为对我的同步程度的半粗略衡量,我使用 AudioServices 在每台设备上播放“滴答”声……您可以很容易地通过耳朵判断它们的同步程度(理想情况下,您会无法辨别多个声源)。

当然,我必须以某种方式考虑 MultiPeer 延迟......而且它的变化很大,在我的测试中从 0.1 秒到 0.8 秒不等。

发现系统时钟对于我的目的来说完全不可靠,我找到了一个 NTP 的 iOS 实现并正在使用它。所以我有理由相信这两款手机有一个准确的共同时间参考(虽然我还没有想出一种方法来测试这个假设,除非我在两台设备上连续显示 NTP 时间,而且它看起来很好同步到我的眼睛)。

我之前尝试的是使用 P2P 消息发送“开始时间”,然后(在接收端)从 1.5 秒常数中减去该延迟,并在该延迟之后执行操作。在发件人端,我只需等待该常量过去,然后执行操作。这根本不起作用。我走得太远了。

我的下一个尝试是在两端等待整整一秒,可以被三整除,因为延迟似乎总是 sender”(按下按钮的设备)会这样做:

-(void)startActionAsSender

    [self notifyPeerToStartAction];
    [self delay];
    [self startAction];

接收者会这样做,以响应委托调用:

-(void)peerDidStartAction

    [self delay];
    [self startAction];

我的“delay”方法如下所示:

-(void)delay

    NSDate *NTPTimeNow = [[NetworkClock sharedInstance] networkTime];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSDateComponents *components = [calendar components:NSSecondCalendarUnit 
    fromDate:NTPTimeNow];
    NSInteger seconds = [components second];

    // If this method gets called on a second divisible by three, wait a second...
    if (seconds % 3 == 0)  
        sleep(1);
    

    // Spinlock
    while (![self secondsDivideByThree])  


-(BOOL)secondsDivideByThree

    NSDate *NTPTime = [[NetworkClock sharedInstance] networkTime];
    NSCalendar *calendar = [NSCalendar currentCalendar];
    NSInteger seconds = [[calendar components:NSSecondCalendarUnit fromDate:NTPTime] 
    second];

    return (seconds % 3 == 0);

【问题讨论】:

你好里德,你终于找到解决办法了吗?我有完全相同的问题,你的代码有时很好,但不是经常。你试过下面的sn-p,它有效吗? Ivan,我需要这个的项目被搁置了,所以我从来没有真正弄清楚。这似乎不像火箭科学,但我尝试过的任何事情都没有奏效。看来你最好的选择是选择下面的答案。 【参考方案1】:

这是旧的,所以我希望你能够得到一些工作。我遇到了一个非常相似的问题。就我而言,我发现这种不一致几乎完全是由于计时器合并导致的,这导致 iOS 设备上的计时器错误by up to 10% 以节省电池使用量。

作为参考,这是我在自己的应用中一直使用的解决方案。首先,我使用一个简单的自定义协议,该协议本质上是一个基本的 NTP,用于通过本地网络在两个设备之间同步一个单调递增的时钟。我在下面的代码中将此同步时间称为“DTime”。使用此代码,我可以告诉所有对等方“在时间 Y 执行操作 X”,并且同步发生。

+ (DTimeVal)getCurrentDTime

    DTimeVal baseTime = mach_absolute_time();
    // Convert from ticks to nanoseconds:
    static mach_timebase_info_data_t s_timebase_info;
    if (s_timebase_info.denom == 0) 
        mach_timebase_info(&s_timebase_info);
    
    DTimeVal timeNanoSeconds = (baseTime * s_timebase_info.numer) / s_timebase_info.denom;
    return timeNanoSeconds + localDTimeOffset;


+ (void)atExactDTime:(DTimeVal)val runBlock:(dispatch_block_t)block

    // Use the most accurate timing possible to trigger an event at the specified DTime.
    // This is much more accurate than dispatch_after(...), which has a 10% "leeway" by default.
    // However, this method will use battery faster as it avoids most timer coalescing.
    // Use as little as necessary.
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, DISPATCH_TIMER_STRICT, dispatch_get_main_queue());
    dispatch_source_set_event_handler(timer, ^
        dispatch_source_cancel(timer); // one shot timer
        while (val - [self getCurrentDTime] > 1000) 
            // It is at least 1 microsecond too early...
            [NSThread sleepForTimeInterval:0.000001]; // Change this to zero for even better accuracy
        
        block();
    );
    // Now, we employ a dirty trick:
    // Since even with DISPATCH_TIMER_STRICT there can be about 1ms of inaccuracy, we set the timer to
    // fire 1.3ms too early, then we use an until(time)  sleep();  loop to delay until the exact time
    // that we wanted. This takes us from an accuracy of ~1ms to an accuracy of ~0.01ms, i.e. two orders
    // of magnitude improvement. However, of course the downside is that this will block the main thread
    // for 1.3ms.
    dispatch_time_t at_time = dispatch_time(DISPATCH_TIME_NOW, val - [self getCurrentDTime] - 1300000);
    dispatch_source_set_timer(timer, at_time, DISPATCH_TIME_FOREVER /*one shot*/, 0 /* minimal leeway */);
    dispatch_resume(timer);

【讨论】:

非常感谢您花时间回答!实际上,当我被转移到其他项目时,我从未设计出可行的解决方案。一定要收藏它以供将来参考! @bradenm 你能解释一下你是如何计算 localDTimeOffset 的吗? 刚刚花了 3 个小时尝试使用这个 sn-p。你能提供一个使用例子吗?谢谢。 Ivan,我使用的算法是专有的。对于基本版本,假设您有两个设备,A 和 B。A 告诉 B“我的 DTime_A 是 514”,然后 B 立即告诉 A,“我在 DTime_B = 240 收到您的消息”。当 A 得到 B 的回复时,DTime_A 现在将是 530 左右。所以 A 估计 B 的回复是在 DTime_A = (530+514)/2 = 522 发送的,偏移量是 522 - 240 = 282 s。跨度> 谢谢。我会尝试实现它。

以上是关于通过 Multipeer Framework 临时同步两个客户端的主要内容,如果未能解决你的问题,请参考以下文章

iOS Multipeer Connectivity Framework 的实际形而上学半径是多少?

在 Swift 中通过 Multipeer Connectivity 临时同步两个 iOS 设备

Multipeer Connectivity Framework (iOS7) 如何在附近没有 Wi-Fi 路由器且蓝牙接口关闭的情况下使用?

无法通过 Multipeer Connectivity 连接蓝牙

通过 Multipeer Connectivity 发送 MPMediaItem

我可以通过 iOS Multipeer Connectivity 发送多大的消息?