为什么我的MCSession对等端会随机断开连接?

时间:2013-09-21 17:07:52

标签: objective-c ios7 multipeer-connectivity

我使用MCNearbyServiceBrowser和MCNearbyServiceAdvertiser将两个对等方连接到MCSession。我可以使用MCSession的sendData方法在它们之间发送数据。所有似乎都按预期工作,直到我随机(并且不是由于我控制的任何事件)通过会话的MCSessionDelegate didChangeState处理程序接收MCSessionStateNotConnected。此外,MCSession的connectedPeers数组不再拥有我的同伴。

两个问题:为什么?以及如何防止MCSession断开连接?

5 个答案:

答案 0 :(得分:25)

这是一个错误,我刚刚向Apple报告。文档声称didReceiveCertificate回调是可选的,但事实并非如此。将此方法添加到MCSessionDelegate

- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     certificateHandler(YES);
 }

应该停止随机断开连接。

答案 1 :(得分:17)

UPDATE 使用Apple支持票后,他们确认过于频繁地调用sendData并且数据太多会导致断开连接。

我在击中断点和背景时已断开连接。由于断点不会发生在应用商店中,因此您需要在应用即将进入后台时开始后台任务来处理后台处理案例。然后当您的应用程序返回到前台时结束此任务。在iOS 7上,这为您提供了大约3个背景分钟,这比什么都好。

另一种策略是使用[[UIApplication sharedApplication] backgroundTimeRemaining]在您的后台时间到期之前的15秒内安排本地通知,这样您就可以在用户暂停之前将用户带回应用程序,并且多对等框架具有被关闭。也许本地通知会警告他们他们的会话将在10秒内到期......

如果后台任务到期并且应用程序仍处于后台,则必须拆除与多对等连接相关的所有内容,否则将导致崩溃。

- (void) createExpireNotification
{
    [self killExpireNotification];

    if (self.connectedPeerCount != 0) // if peers connected, setup kill switch
    {
        NSTimeInterval gracePeriod = 20.0f;

        // create notification that will get the user back into the app when the background process time is about to expire
        NSTimeInterval msgTime = UIApplication.sharedApplication.backgroundTimeRemaining - gracePeriod;
        UILocalNotification* n = [[UILocalNotification alloc] init];
        self.expireNotification = n;
        self.expireNotification.fireDate = [NSDate dateWithTimeIntervalSinceNow:msgTime];
        self.expireNotification.alertBody = TR(@"Text_MultiPeerIsAboutToExpire");
        self.expireNotification.soundName = UILocalNotificationDefaultSoundName;
        self.expireNotification.applicationIconBadgeNumber = 1;

        [UIApplication.sharedApplication scheduleLocalNotification:self.expireNotification];
    }
}

- (void) killExpireNotification
{
    if (self.expireNotification != nil)
    {
        [UIApplication.sharedApplication cancelLocalNotification:self.expireNotification];
        self.expireNotification = nil;
    }
}

- (void) applicationWillEnterBackground
{
    self.taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
    {
        [self shutdownMultiPeerStuff];
        [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
        self.taskId = UIBackgroundTaskInvalid;
    }];
    [self createExpireNotification];
}

- (void) applicationWillEnterForeground
{
    [self killExpireNotification];
    if (self.taskId != UIBackgroundTaskInvalid)
    {
        [[UIApplication sharedApplication] endBackgroundTask:self.taskId];
        self.taskId = UIBackgroundTaskInvalid;
    }
}

- (void) applicationWillTerminate
{
    [self killExpireNotification];
    [self stop]; // shutdown multi-peer
}

由于Apple bug,您还需要在MCSession委托中使用此处理程序:

- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     if (certificateHandler != nil) { certificateHandler(YES); }
 }

答案 2 :(得分:11)

这有很多原因,到目前为止,这两个答案在我的经历中都是正确的。您在其他类似问题中找到的另一个问题是:只有一个同行可以接受另一个人的邀请

因此,澄清一下,如果您设置的应用程序所有设备都是广告客户和浏览器,任何设备都可以自由邀请任何其他设备加入会话。但是,在任何两个给定设备之间,只有一个设备可以实际接受邀请并连接到另一个设备。如果两个设备都互相接受了#39;他们将在一分钟或更短的时间内断开邀请。

请注意,此限制并不能阻止所需的行为,因为 - 与我在构建多重同步实现之前所说的直觉不同 - 当一个设备接受邀请并连接到另一个设备时,它们连接并且接收连接委托方法,并可以相互发送消息。

因此,如果您要连接既浏览又要做广告的设备,请自由发送邀请但只接受其中一对

只接受两个邀请之一的问题可以通过多种方式解决。首先,要了解您可以将任意对象或字典(存档为数据)作为邀请中的context参数传递。因此,两个设备都可以访问关于另一个(当然还有其自身)的任意信息。所以,你至少可以使用这些策略:

  • 只是compare: peerID的显示名称。但是,并不能保证这些不会是平等的。
  • 存储多重控制器初始化的日期并将其用于比较
  • 给每个对等体一个UUID并发送给它进行比较(我的技术,其中每个设备 - 实际上是设备上应用程序的每个用户 - 都有一个持久的UUID)。
  • etc - 支持NSCoding和compare:的任何对象都可以。

答案 3 :(得分:3)

我一直有类似的问题。看来,如果我在一台iOS设备上运行我的应用程序,并连接到另一台设备,然后退出并重新启动(比如当我从Xcode重新运行时),那么我处于一种情况,我收到一条已连接的消息,然后是一台未连接的消息稍后。这让我失望了。但仔细观察,我可以看到Not Connected消息实际上是针对与已连接的消息不同的peerId。

我认为这里的问题是我见过的大多数样本只关心peerID的displayName,而忽略了你可以为同一个device / displayName获得多个peerID的事实。

我现在首先检查displayName,然后通过比较指针来验证peerID是否相同。

- (void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state {

    MyPlayer *player = _players[peerID.displayName];

    if ((state == MCSessionStateNotConnected) &&
        (peerID != player.peerID)) {
        NSLog(@"remnant connection drop");
        return; // note that I don't care if player is nil, since I don't want to
                // add a dictionary object for a Not Connecting peer.
    }
    if (player == nil) {
        player = [MyPlayer init];
        player.peerID = peerID;
        _players[peerID.displayName] = player;
    }
    player.state = state;

...

答案 4 :(得分:1)

我在接受连接请求后立即断开连接。观察状态,我看到它从MCSessionStateConnected变为MCSessionStateNotConnected。

我正在创建我的会话:

[[MCSession alloc] initWithPeer:peerID]

不是处理安全证书的实例化方法:

 - (instancetype)initWithPeer:(MCPeerID *)myPeerID securityIdentity:(NSArray *)identity encryptionPreference:(MCEncryptionPreference)encryptionPreference 

根据Andrew的上述提示,我添加了委托方法

   - (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler {
         certificateHandler(YES);
     }

并且断开连接。