Почему мой одноранговый MCSession отключается случайно?

Я использую MCNearbyServiceBrowser и MCNearbyServiceAdvertiser, чтобы соединить двух пиров в MCSession. Я могу передавать данные между ними, используя метод sendData MCSession. Кажется, что все работает должным образом, пока я случайно (и не из-за какого-либо события, которым я управляю) получаю MCSessionStateNotConnected через обработчик сессии MCSessionDelegate didChangeState. Кроме того, у массива connectedPeers MCSession больше нет моих пиров.

Два вопроса: почему? и как я могу сохранить MCSession от отключения?

5 ответов

Это ошибка, о которой я только что сообщил в Apple. Документы требуют didReceiveCertificate обратный вызов не является обязательным, но это не так. Добавьте этот метод к вашему MCSessionDelegate:

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

Случайные отключения должны прекратиться.

ОБНОВЛЕНИЕ После использования заявки в службу поддержки 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
}

Вам также понадобится этот обработчик в вашем делегате MCSession из-за ошибки Apple:

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

Причин этому много, и оба ответа на мой взгляд верны. Другой вопрос, который вы найдете в других подобных вопросах, таков: только один участник может принять приглашение другого.

Итак, чтобы уточнить, если вы настроите приложение, в котором все устройства являются как рекламодателями, так и браузерами, любые устройства могут свободно приглашать любые другие найденные присоединиться к сеансу. Однако между любыми двумя данными устройствами только одно устройство может принять приглашение и подключиться к другому устройству. Если оба устройства принимают приглашения друг друга, они отключатся в течение минуты или меньше.

Обратите внимание, что это ограничение не препятствует желаемому поведению, потому что - в отличие от того, о чем говорила моя интуиция до того, как я построил свою многопользовательскую реализацию, - когда одно устройство принимает приглашение и подключается к другому устройству, они оба становятся подключенными и получают методы делегирования подключения и могут отправлять друг другу сообщения.,

Поэтому, если вы подключаете устройства, которые одновременно просматривают и рекламируют, отправляйте приглашения свободно, но принимайте только одно из пары.

Проблема принятия только одного из двух приглашений может быть решена множеством способов. Для начала поймите, что вы можете передать любой произвольный объект или словарь (заархивированный как данные) как context аргумент в приглашении. Следовательно, оба устройства имеют доступ к любой произвольной информации о другом (и, конечно, о себе). Итак, вы можете использовать по крайней мере эти стратегии:

  • просто compare: отображаемое имя peerID. Но нет гарантии, что они не будут равны.
  • сохраните дату инициализации вашего многопользовательского контроллера и используйте ее для сравнения
  • присвойте каждому пиру UUID и отправьте его для сравнения (моя методика, при которой каждое устройство - фактически каждый пользователь приложения на устройстве - имеет постоянный UUID, который он использует).
  • и т. д. - любой объект, который поддерживает как NSCoding, так и compare: все будет хорошо.

У меня были похожие проблемы. Кажется, однако, что если я запустил свое приложение на одном устройстве iOS и подключился к другому, затем вышел и перезапустил (скажем, когда я перезапущен из Xcode), то я нахожусь в ситуации, когда я получаю сообщение "Подключено", а затем "Не подключено" сообщение чуть позже. Это меня оттолкнуло. Но, посмотрев внимательнее, я вижу, что сообщение Not Connected на самом деле предназначено для другого peerId, чем тот, который подключился.

Я думаю, что проблема здесь в том, что большинство сэмплов, которые я видел, заботятся только о displayName peerID и игнорируют тот факт, что вы можете получить несколько peerID для одного устройства / displayName.

Сейчас я сначала проверяю 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;

...

Я отключился сразу после того, как принял запрос на подключение. Наблюдая за состоянием, я увидел, что оно изменилось с 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);
     }

и разъединения прекратились.

Другие вопросы по тегам