41

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

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

4

5 回答 5

27

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

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

随机断开应该停止。

于 2013-10-30T23:59:03.323 回答
18

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

我在遇到断点和后台时断开了连接。由于断点不会在应用商店发生,因此您需要在应用即将进入后台时通过启动后台任务来处理后台情况。然后在您的应用返回前台时结束此任务。在 iOS 7 上,这为您提供了大约 3 分钟的背景时间,这总比没有好。

另一种策略是在后台时间到期之前使用 安排本地通知大约 15 秒[[UIApplication sharedApplication] backgroundTimeRemaining],这样您就可以在应用程序暂停之前将用户带回应用程序,并且必须关闭多对等框架。也许本地通知会警告他们他们的会话将在 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 错误,您还需要在 MCSession 委托中使用此处理程序:

- (void) session:(MCSession*)session didReceiveCertificate:(NSArray*)certificate fromPeer:(MCPeerID*)peerID certificateHandler:(void (^)(BOOL accept))certificateHandler
 {
     if (certificateHandler != nil) { certificateHandler(YES); }
 }
于 2013-10-21T15:26:21.223 回答
12

造成这种情况的原因有很多,到目前为止,根据我的经验,这两个答案都是正确的。您会在其他类似问题中找到的另一个问题是:只有一个同伴可以接受另一个同伴的邀请

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

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

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

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

  • 只是compare:peerID 的显示名称。但不能保证这些不会相等。
  • 存储您的多对等控制器的初始化日期并使用它进行比较
  • 给每个对等方一个 UUID 并将其发送以进行比较(我的技术,其中每个设备 - 实际上是设备上应用程序的每个用户 - 都有一个它使用的持久 UUID)。
  • 等 - 任何同时支持 NSCoding 并且compare:可以正常工作的对象。
于 2014-02-04T21:32:18.343 回答
3

我一直有类似的问题。似乎如果我在一台 iOS 设备上运行我的应用程序并连接到另一台设备,然后退出并重新启动(比如当我从 Xcode 重新运行时),那么我会收到一条已连接的消息,然后是未连接的消息稍后发消息。这让我失望了。但更仔细地看,我可以看到 Not Connected 消息实际上是针对与已连接的 peerId 不同的 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;

...
于 2014-12-24T09:59:46.583 回答
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);
     }

并且断开连接停止。

于 2014-09-04T19:35:10.673 回答