为什么我的MCSession同行会随机断开连接?

41

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

两个问题:为什么?以及如何保持MCSession不断开连接?


我也遇到了同样的问题,但是在发送一些数据后就会断开连接。你解决了吗? - Moonkid
我注意到的一个问题是,在调试器中暂停会中断MCSession。我最终编写了一个机制,以便在会话被中断时重新建立它。 - tillerstarr
我有同样的问题。我注意到如果一个设备被放在后台并且消息被发送到它,那么就会发生断开连接的情况。 - jjxtra
@tillerstarr,你是如何重新连接会话的呢? - jjxtra
我不会自动重新连接。相反,我会让用户知道,然后他们可以重新连接。我会停止广告商和浏览器,并将会话设置为空,然后重新创建它们。 - tillerstarr
你解决了这个问题吗?我遇到了一些问题。 - Pablo Martinez
5个回答

28

这是一个错误,我刚刚向苹果公司报告了此问题。文档声称didReceiveCertificate回调是可选的,但实际上不是。请将以下方法添加到您的MCSessionDelegate中:

- (void) session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     certificateHandler(YES);
 }
随机断开连接应该停止。

9
即使实施了这个方法,仍然会出现断开连接的问题。 - jjxtra
1
在 Swift 中对我也没有解决问题。 - berliner
1
如果您已经收到了MCSessionStateConnected值的didChangeState消息,并且可以成功发送数据,那么您已经建立了连接,这不是您的问题。其他答案可能会提供更多信息。但是,如果您(像我一样)感到困惑,为什么当您尝试接受连接时,您会看到MCSessionStateConnecting,然后几秒钟后就会变为MCSessionStateNotConnected且没有其他解释,那么很可能这里提供的答案恰好是您的问题。 - GrandOpener

19

更新:在向苹果的支持票证中询问后,他们确认如果频繁调用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
}

由于苹果的bug,您还需要将此处理程序添加到您的MCSession代理中:

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

当应用程序进入后台时,您能否在将其从后台恢复时保持连接? - Tim
只要您的后台时间没有耗尽,您就可以继续操作。如果时间用完了,您只能强制结束会话或者崩溃。 - jjxtra
@PsychoDad,你从与苹果开发技术支持的交流中学到了什么关于过于频繁调用“sendData”方法的问题吗? - Niels Castle
是的,他们建议不要过于频繁地调用sendData函数(我最多将其限制在每秒约30次)。此外,如果您进行调试或暂停线程,对等方将会断开连接。 - jjxtra

13

这个问题有很多原因,前两个答案在我的经验中都是正确的,另一个在其他类似的问题中也会出现:只有一个设备可以接受另一个设备的邀请。

因此,需要澄清的是,如果您设置了一个应用程序,其中所有设备都是广告商和浏览器,任何设备都可以自由地邀请找到的任何其他设备加入会话。然而,在任何给定的两个设备之间,只有一个设备可以实际上接受邀请并连接到另一个设备。如果两个设备都接受彼此的邀请,则它们将在一分钟内断开连接。

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

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

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

  • 简单地compare: peerID的显示名称。但是无法保证这些名称不相等。
  • 存储多等实例控制器初始化的日期,并将其用于比较。
  • 为每个对等体分配一个UUID并发送此UUID以进行比较(我的技术,其中每个设备 - 实际上是在设备上使用该应用程序的每个用户 - 都具有其所采用的持久UUID)。
  • 等等-任何支持NSCoding和compare:的对象都可以胜任。

我本以为这是我的问题。所以我设计了一个复杂的延迟邀请,但它们都没有连接成功。 - daihovey
1
这实际上是个问题:请注意:模拟器不支持蓝牙网络。 - daihovey

3
我一直遇到类似的问题。看起来,如果我在一个iOS设备上运行我的应用程序,然后连接到另一个设备,然后退出并重新启动(比如从Xcode重新运行),那么我会遇到这样的情况:稍后会出现“已连接”和“未连接”消息。这让我感到困惑。但是仔细观察后,我发现“未连接”消息实际上是针对已连接的不同peerId而言的。
我认为问题在于,我看到的大多数示例只关心peerID的displayName,而忽略了同一设备/ 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;

...

这仍然是相关的,在Swift3中已经过了2年! - timothykc

1
我接受了连接请求后立即断开连接。观察状态,我发现它从MCSessionStateConnected变为MCSessionStateNotConnected。
我使用以下代码创建我的会话:
[[MCSession alloc] initWithPeer:peerID]

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

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

根据安德鲁的提示,我添加了委托方法

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

断开连接停止了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接