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


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

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

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

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

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

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

Доповідач aka Advertiser


Механізм простий. Є сесія, яка ініціалізується параметрами безпеки та шифрування, а також об'єктом класу MCPeerID, сутність якого досить мінімалістична, оскільки цей клас має лише одну властивість — 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:



До речі, ця проблема не вирішується ні перезапуском програми, ні його перевстановлення. Старі сесії все одно можуть переслідувати вас. Допомагає тільки включення і вимикання авиарежима.

Обхідний шлях:

Коли у нас є тільки один адвертайзер, кожна нова сесія цього адвертайзера повинна мати discoveryInfo. Це параметр у форматі Dictionary<String,String>, який задається при старті сесії адвертайзера і містить додаткову інформацію про сесії. Додавши час створення сесії як унікальний ідентифікатор кожної сесії, на стороні браузера можна відстежувати, які сесії показувати, а які ні.



Якщо зробити все правильно, в списку активних сесій для кожного адвертайзер девайса буде найсвіжіша сесія.



Проблема максимальної кількості девайсів в сесії
Дивно, але спочатку фреймворк має обмеження у сім пристроїв в одній сессиии. Цілі, наприклад, нашої задачі, явно йдуть врозріз з цим обмеженням від інженерів Apple. При використанні нашого додатка може участовать 30 — 40 девайсів одночасно, і дуже шкода, що у фреймворку спочатку немає рішення для такого випадку.

Обхідний шлях:

Незважаючи на обмеження фреймворку за кількістю девайсів в сесії, у нього немає обмеження на кількість сесій. Щоб, приміром, мати один эдвертайзер мав можливість обмінюватися даними з 40 браузерами, потрібно реалізувати рішення, яке зможе підтримувати роботу з шістьма сесіями. Але тут, знову ж таки, проблема: браузер-девайс буде бачити кілька сесій від одного і того ж адвертайзера, і потрібно зробити так, щоб браузер бачив останню сесію з вільними місцями для підключення. Як варіант, на допомогу приходить все той же параметр discoveryInfo, в якому можна тримати, допустимо індекс сесії.

Механізм менеджменту додаткових сесій на адвертайзере:




На стороні браузера треба фільтрувати всі сесії за індексом і відображати сесію з максимальним значенням індексу. Якщо в попередніх сесіях раптом з'явилося вільне місце, ми не зможемо ніяк повідомити про це браузеру, який хоче підключитися до адвертайзеру, т. к. властивість discoveryInfo класу MCAdvertiserAssistant — readonly.

Reconnection
Цю головний біль фреймворку я вважаю найбільш витратною на латання милицями. Вважаю дуже дивним з боку Apple випустити фреймворк без готового механізму перепідключення. Звичайний кейс — браузер девайс пішов у сліп мод, протягом 15-20 секунд він все ще може отримувати повідомлення від адвертайзера, але потім фреймворк повідомляє нам про те що connection lost…

Обхідний шлях:

Здавалося б, аби знову підключиться до сесії адвертайзера, просто потрібно завжди зберігати покажчик на об'єкт цій сесії, і в разі чого повторно вислати запрошення адвертайзеру, використовуючи цю сесію. На практиці цей очевидний підхід не працює. У нашому проекті допомагав тільки 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

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.