Как стать автором
Обновить
0
DataArt
Технологический консалтинг и разработка ПО

Страх и ненависть в Multipeer Connectivity

Время на прочтение7 мин
Количество просмотров8.3K

Автор: Роман Ивченко, iOS developer DataArt.

Введение

Наверняка каждый, кто хоть раз занимался поиском готового решения для обмена сообщениями, файлами, стримами между iOS-устройствами без использования серверной части, слышал о фреймворке Multipeer Connectivity, выпущенном в iOS 7.

Вцелом это один из самых инновационных фреймворков, выпущенных в 7-й версии системы. Он должен был заменить слегка устаревший CoreBluetooth.

Чтобы познать всю мощь и силу Multipeer Connectivity, мы попробовали обкатать его в нашем R&D-проекте, задача которого весьма проста — шаринг презентаций и синхронизация переключения слайдов между устройствами слушателей и устройством докладчика на конференциях, в учебных аудиториях и т. д.

Краткий обзор

Для реализации нашей задачи фреймворк, на первый взгляд, очень хорошо вписывался в архитектуру приложения. Условно у нас есть всего два типа пользователей — докладчик и слушатель. Multipeer Connectivity как раз предоставляет необходимые классы для имплементации функционала каждого типа пользователя.

Статья не претендует на полное освещение всех тонкостей фреймворка, а больше рассказывает о его проблемах и надежности. Все технические детали можно узнать в документации Apple.

Докладчик aka Advertiser



Механизм прост. Есть сессия, которая инициализируется параметрами безопасности и шифрования, а также обьектом класса MCPeerID, cущность которого довольно минималистична, т. к. этот класс имеет лишь одно свойство — displayName. Его, по сути, можно считать и названием ID-сессии. Чтобы нашу сессию могли видеть другие пользователи, фреймворк предоставляет нам класс MCAdvertiserAssistant, маяк, сообщающий всем об этой сессии, и хранящий информацию о ней.

Как только слушатель захочет подключится к сессии, MCAdvertiserAssistant автоматически покажет уведомление об этом, с опциями «разрешить/отклонить подключение». Как только пользователь адвертайзер разрешит подключится слушателю, тот попадает в сессию и получает возможность общаться с адвертайзером.

Слушатель aka Browser

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



Для браузер-девайса предоставляется класс MCNearbyServiceBrowser, что-то вроде радара, который ищет адвертайзеров в рамках определенного имени сервиса.

Общение между MCNearbyServiceBrowser и классом контроллера, реализуется через протокол MCNearbyServiceBrowserDelegate, где логика реализованных делегатных методов подразумевает отображение текущих активных сессий адвертайзеров, изменение их состояний.

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

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

First touch

В теории и даже, на первый взгляд, на практике, все смотрится очень удобно с точки зрения реализации архитектуры — разработчик получает доступ только к самому необходимому, избавляя себя от сложной логики работы с сетями wifi/bluetooth.

Как обычно, перед тем как начинать интегрировать фреймворк в проект, все хотят посмотреть, как он работает в официальном демопроекте от Apple. MultipeerGroupChat в принципе показывает, что к чему, и работает довольно стабильно при starter pack-наборе — симулятор и iPhone/iPod/iPad. Разработчики, которые имеют возможность посмотреть на демоприложение, имея больше двух девайсов, сразу могут ощутить: что что-то в этом фреймворке неладно.

Ghost sessions

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

Представим ситуацию. Алиса — докладчик (адвертайзер), Боб — слушатель (браузер). Алиса начинает сессию презентации, и Боб подключается к ней, они обмениваются парой сообщений, все нормально. Алиса заканчивает презентацию, и заканчивается ее сессия. Первое — вероятность, что Боб получит нотификацию, что адвертайзера больше нет, — чуть больше вероятности, что не получит.

Для браузер-девайса несуществующая сессия адвертайзера может существовать неопределенное время. При попытке подключения к этой сессии может просто ничего не происходить, или через некоторое время попытка подключения уходит в состояние failed. Проблема обретает серьезные масштабы, если адвертайзер создал свои сессии несколько раз. В таком случае у браузера в нативном контроллере MCBrowserViewController может отображаться несколько одинаковых адвертайзеров iPhone Simulator, когда ты работаешь только с одним девайсом и одним симулятором, и понятия не имеешь, какой адветайзер из списка активен. Пример бага в демоприложении от Apple:



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

Workaround:

Когда у нас имеется только один адвертайзер, каждая новая сессия этого адвертайзера должна иметь discoveryInfo. Это параметр в формате Dictionary<String,String>, который задается при старте сессии адвертайзера и содержит дополнительную информацию о сессии. Добавив время создания сессии как уникальный идентификатор каждой сессии, на стороне браузера можно отслеживать, какие сессии показывать, а какие нет.



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



Проблема максимального количества девайсов в сессии

Странно, но изначально фреймворк имеет ограничение в семь подключаемых устройств в одной сессиии. Цели, к примеру, нашей задачи, явно идут вразрез с этим ограничением от инженеров Apple. При использовании нашего приложения может участовать 30 — 40 девайсов одновременно, и очень жаль, что у фреймворка изначально нет решения для такого случая.

Workaround:

Несмотря на ограничение фреймворка по количеству девайсов в сессии, у него нет ограничения на количество сессий. Чтобы, к примеру, иметь один эдвертайзер имел возможность обмениваться данными с 40 браузерами, нужно реализовать решение, которое сможет поддерживать работу с шестью сессиями. Но тут, опять же, проблема: браузер-девайс будет видеть несколько сессий от одного и того же адвертайзера, и нужно сделать так, чтобы браузер видел самую последнюю сессию со свободными местами для подключения. Как вариант, на помощь приходит все тот же параметр discoveryInfo, в котором можно содержать, допустим индекс сессии.

Механизм менеджмента дополнительных сессий на адвертайзере:




На стороне браузера надо фильтровать все сессии по индексу и отображать сессию с самым максимальным значением индекса. Если в предыдущих сессиях вдруг появилось свободное место, мы не сможем никак сообщить об этом браузеру, который хочет подключиться к адвертайзеру, т. к. свойство discoveryInfo класса MCAdvertiserAssistant — readonly.

Reconnection

Эту головную боль фреймворка я считаю самой затратной по залатыванию костылями. Считаю очень странным со стороны Apple выпустить фреймворк без готового механизма переподключения. Обычный кейс — браузер девайс ушел в слип мод, на протяжении 15-20 секунд он все еще может получать сообщения от адвертайзера, но потом фреймворк сообщает нам о том что connection lost…

Workaround:

Казалось бы, чтобы опять подключится к сессии адвертайзера, просто нужно всегда хранить указатель на обьект этой сессии, и в случае чего повторно выслать приглашение адвертайзеру, используя эту сессию. На практике этот очевидный подход не работает. В нашем проекте помогал только hard reset браузер-сессии и всего, что было с ней связано, и эмулирование процесса, будто пользователь руками обновил список доступных адвертайзеров и подключился к тому, от которого отключился после ухода в бекграунд.



Решение очень грубое, и я уверен, что для некоторых проектов и задач — абсолютно неподходящее, но по-другому это сделать, видимо, невозможно. Пруф, Problem 4.

Общая нестабильность работы, внезапные connection lost


Если предыдущие баги и ограничения еще как-то можно было решить, тут все уже зависит от инженеров Apple. По моим наблюдениям, из 10 попыток тестирования самых простых кейсов для нашего приложения, примерно 7 — 8 заканчивались более-менее успешно. Простые кейсы — примерно 10 — 15 % максимально возможной нагруженности приложения (адвертайзер и 20 — 30 браузеров). В остальных фейловых случаях, происходило следующее:

  • Браузер-девайс внезапно перестает получать ообщения от адвертайзера, причем никаких уведомлений от фреймворка, никаких алертов, вызовов делегатных методов — просто тишина.
  • Браузер-девайс внезапно перестает получать сообщения от адвертайзера, кроме сообщений connection lost приходит, причины не уточняются.
  • Браузер-девайс или их группа получают сообщения с серьезной задержкой (на одном девайсе слайд переключился, на другом — только спустя 5 секунд).
  • При попытке реконнекта какого-либо из браузер-девайсов к адвертайзеру начинают отваливаться другие браузеры, или скорость получения ими сообщений от адвертайзера резко падает.


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




Увеличение количества подключенных устройств пропорционально уменьшает стабильность всей системы

Эта проблема — главная в статье. Если в случае двух-трех устройств стабильность работы более или менее нормальна, при семи-девяти, не говоря уже о большем количестве, — стабильность начинает стремиться к нулю. Фреймворк просто не работает. Оговорюсь сразу: речь идет о соединении типа «адвертайзер и множество браузеров». Возможно, существуют какие-то другие более стабильные конфигурации, но эта — самая простая в реализации, и при использовании фреймворка хочется, чтобы он работал для всех конфигураций одинаково стабильно.

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

Страх, ненависть и выводы

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

Многие сообщения — двухлетней давности, и можно сначала подумать, что все это — детские болезни фреймворка, и в 7 iOS он был внедрен в альфа-версии, но сейчас уже 2016 год, самая последняя версия iOS — 9.2, а проблемы остались все те же.

MultipeerConnectivity крайне не рекомендуется для использования с более чем тремя устройствами. Для двух-трех устройств прогноз благоприятный, но проблем все равно будет много: чтобы добиться того, что, по идее, должно быть «из коробки», нужно потратить вдвое больше времени.
Статью хорошо закончат пару комментариев к теме на devforums.apple.com/message/956192#956192 и ссылка на наше приложение на GitHub github.com/DataArt/SmartSlides.





Подтверждения проблемы:
www.ymc.ch/en/multipeer-connectivity-a-bag-of-hurt
stackoverflow.com/questions/28418965/multipeer-connectivity-vs-real-time-matches
devforums.apple.com/message/956192#956192
devforums.apple.com/message/982861#982861
Теги:
Хабы:
+7
Комментарии6

Публикации

Информация

Сайт
www.dataart.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек

Истории