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

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

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


person tillerstarr    schedule 21.09.2013    source источник
comment
У меня такая же проблема, но я отключаюсь после отправки некоторых данных. Вы решили это?   -  person Moonkid    schedule 09.10.2013
comment
Один момент, который я заметил, заключается в том, что пауза в отладчике прерывает MCSession. В итоге я написал механизм для восстановления сеанса в случае его разрыва.   -  person tillerstarr    schedule 09.10.2013
comment
У меня та же проблема. Я заметил, что если одно устройство находится в фоновом режиме и на него отправляются сообщения, то происходит отключение.   -  person jjxtra    schedule 21.10.2013
comment
@tillerstarr, как вы переподключили сеанс?   -  person jjxtra    schedule 21.10.2013
comment
я не подключаюсь автоматически. Вместо этого я сообщаю пользователю, и тогда он может снова подключиться. Я останавливаю рекламодателя и браузер и обнуляю сеанс, а затем заново создаю их все.   -  person tillerstarr    schedule 21.10.2013
comment
ты решил эту проблему? у меня проблемы   -  person Pablo Martinez    schedule 03.05.2016


Ответы (5)


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

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

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

person Andrew Cone    schedule 30.10.2013
comment
Все еще получаю разъединения даже после реализации этого - person jjxtra; 07.12.2013
comment
У меня тоже это не исправляет (быстро) - person berliner; 19.12.2014
comment
Если вы дошли до того, что получили сообщение didChangeState со значением MCSessionStateConnected и можете успешно отправить данные, значит, вы установили соединение, и это не ваша проблема. В одном из других ответов может быть больше информации. Однако, если вы (как и я) озадачены тем, почему при попытке принять соединение вы видите MCSessionStateConnecting, а затем через несколько секунд MCSessionStateNotConnected без каких-либо других объяснений, то вполне вероятно, что приведенный здесь ответ — это именно ваша проблема. - person GrandOpener; 28.06.2015

ОБНОВЛЕНИЕ После обращения в службу поддержки 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); }
 }
person jjxtra    schedule 21.10.2013
comment
При работе в фоновом режиме вы можете поддерживать соединение при возвращении приложения из фона? - person Tim; 24.01.2014
comment
Вы можете, пока ваше фоновое время не закончится. Если это так, у вас нет другого выбора, кроме как завершить сеанс или сбой. - person jjxtra; 25.01.2014
comment
@PsychoDad, вы узнали что-нибудь о том, что считалось слишком частым вызовом sendData из вашего знакомства с Apple DTS? - person Niels Castle; 24.11.2014
comment
Да, они рекомендовали не вызывать sendData слишком часто (я ограничиваю его максимум 30 разами в секунду). Кроме того, если вы отлаживаете или приостанавливаете потоки, одноранговые узлы отключаются. - person jjxtra; 24.11.2014

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

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

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

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

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

  • просто compare: отображаемое имя peerID. Но нет гарантии, что они не будут равными.
  • сохраните дату инициализации вашего многорангового контроллера и используйте ее для сравнения
  • дать каждому одноранговому узлу UUID и отправить его для сравнения (мой метод, в котором каждое устройство - действительно, каждый пользователь приложения на устройстве - имеет постоянный UUID, который он использует).
  • и т.д. - подойдет любой объект, который поддерживает как NSCoding, так и compare:.
person SG1    schedule 04.02.2014
comment
Я думал, что это моя проблема. Поэтому я даже придумал запутанный инвайт с задержкой, но ни один из них не подключается. - person daihovey; 28.10.2014
comment
Собственно в этом и была проблема: Please note: Bluetooth networking is not supported in Simulator. - person daihovey; 28.10.2014

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

Я думаю, проблема здесь в том, что большинство образцов, которые я видел, заботятся только об отображаемом имени peerID и игнорируют тот факт, что вы можете получить несколько идентификаторов 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;

...
person mahboudz    schedule 24.12.2014
comment
Это все еще актуально, 2 года спустя в Swift3! - person timothykc; 04.12.2016

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

и отключения прекратились.

person DannyJi    schedule 04.09.2014