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

Rei*_*eid 3 time objective-c ios multipeer-connectivity

我已经在这个问题上工作了几天,我的解决方案都没有充足.我认为,我缺乏理论知识来实现​​这一目标,并且会喜欢一些建议(不一定是iOS特定的 - 我可以将C,伪代码,无论如何转化为我需要的东西).

基本上,我有两个iPhone.当用户按下按钮时,任何一个都可以触发重复动作.然后它需要通知其他iPhone(通过MultiPeer框架)触发相同的操作......但它们都需要在同一时刻启动并保持步调.我真的需要获得1/100秒的精度,我认为这个平台可以实现.

作为我的同步程度的半粗略衡量标准,我使用AudioServices在每个设备上播放"滴答声"...你可以很容易地通过耳朵告诉它们它们是如何同步的(理想情况下你不能识别多个声源).

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

发现系统时钟对我来说是完全不可靠的,我找到了一个N​​TP的iOS实现并且正在使用它.所以我有理由相信这两款手机在时间上有一个准确的共同参考(尽管我还没有找到一种测试这种假设的方法,而不是在两台设备上连续显示NTP时间,我这样做,而且看起来不错同步到我的眼睛).

我之前尝试的是用P2P消息发送"开始时间",然后(在接收方端)从1.5秒的常数中减去该延迟,并在该延迟之后执行动作.在发送方端,我只是等待该常量继续,然后执行操作.这根本不起作用.我离开了.

我的下一次尝试是在两端等待整整一秒被三整除,因为延迟似乎总是<1秒,我认为这会起作用.我使用"延迟"方法来简单地阻止线程.我知道这是一个棍棒,但我只是想在获得更优雅的解决方案之前获得时间工作期.所以,我的" 发送者 "(按下按钮的设备)执行此操作:

-(void)startActionAsSender
{
    [self notifyPeerToStartAction];
    [self delay];
    [self startAction];
}
Run Code Online (Sandbox Code Playgroud)

收件人这样做,以响应代表电话:

-(void)peerDidStartAction
{
    [self delay];
    [self startAction];
}
Run Code Online (Sandbox Code Playgroud)

我的" 延迟 "方法如下所示:

-(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);
}
Run Code Online (Sandbox Code Playgroud)

bra*_*enm 5

这是旧的,所以我希望你能够得到一些有用的东西.我遇到了一个非常类似的问题.在我的情况下,我发现不一致几乎完全是由于计时器合并,这导致计时器在iOS设备上错误高达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);
}
Run Code Online (Sandbox Code Playgroud)

  • 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 = 282s. (3认同)