Поради початківцям розробникам мережевих додатків

image
У даній статті буде дана серія рекомендацій початківцям мережевим розробникам. Можливо вона допоможе вам не наступати на деякі граблі а так само запропонує менш очевидні але більш правильні способи вирішення ваших завдань. Почасти стаття може є холиварной і суперечити вашому досвіду. В цьому випадку — ласкаво просимо в коментарі.

підказка 1
По можливості використовуйте TCP. В іншому випадку вам доведеться самому дбає про цілісність ваших даних, а це не так просто, як здається на перший погляд. Основне вузьке місце TCP, в яке ви упретеся почавши його використовувати в realtime завдання — затримка перед відправкою даних. Але це поправно — потрібно лише встановити сокету прапор TCP_NODELAY:

int flag = 1;
int result = setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));
if (result < 0)
... handle the error ...

Для деяких завдань використовувати TCP неможливо. Наприклад, при реалізації техніки NAT. У цьому випадку можна скористатися готовою бібліотекою, яка забезпечує гарантовану доставку поверх UDP, наприклад UDT library. Для інших завдань (наприклад, передача мультимедіа) іноді так само є сенс використовувати UDP — багато кодеки підтримують втрату пакетів. Але тут варто бути обережним — можливо кращим рішенням буде все ж не втрачати дані, а замість цього забезпечити прискорене відтворення фрагмента, отриманого з затримкою.

підказка 2
Правильно пакуйте і вычитывайте дані. В TCP ви маєте справу з потоком, а не пакетами, тому, наприклад при відправці двох повідомлень ви можете отримати одну з двума склеєними (і навпаки, при відправленні одного великого повідомлення ви можете спочатку отримати тільки першу частину вашого повідомлення, а через кілька мілісекунд другу). Один із стандартних патернів прийому-отримання даних виглядає так:
  • при відправленні — записати довжину пакета, потім сам пакет
  • при отриманні — прочитати всі дані і покласти їх в буфер (цей буфер повинен жити увесь той час, поки встановлено з'єднання)
  • до тих пір, поки в буфері достатньо даних:
  • десериализовать довжину повідомлення. Якщо в буфері дані більше, ніж довжина цього повідомлення розпарсити повідомлення і видалити його (і тільки його) з буфера
  • якщо даних не менше ніж довжина повідомлення — перервати цикл і чекати даних далі


Порада 3
Використовуйте механізми ассинхронной роботи з сокетами замість багатопоточності. Незважаючи на те, що в сучасних ОС потоки практично нічого не коштують у ассинхронного підходу є кілька переваг:
  • немає необхідності використовувати примітиви синхронізації
  • на кожне з'єднання витрачається мінімально необхідні ресурси (на відміну від потоків, для яких вам як мінімум потрібно деяку кількість пам'яті під стек для кожного з них)
Для цих цілей в linux існує epoll, під freebsd і macosx — kqueue а в windows — io completion port. Замість того, щоб вручну працювати з усім цим зоопарком є сенс подивитися в бік boost asio, libevent та інших подібних бібліотек.

Порада 4
Перш ніж писати свій велосипед серіалізації — подивіться на існуючі рішення. Для серіалізації в бінарному вигляді є google protobuff, MessagePack / CBOR та інші. В текстовому — json && xml і їх варіації.

Рада 5
Правильно вибирайте модель синхронізації. Основні моделі синхронізації наступні:
  • Передача повного стану. З точки зору реалізації тут все просто. Беремо об'єкт, сериализуем і відправляємо всім, кому він потрібен. Поки об'єкт має невеликий розмір або рідко запитується — все добре. Як тільки розмір стає відносно великий або ж потрібно часта синхронізація — починає рости споживання трафіку аж до неможливості забезпечити необхідну швидкість оновлення.
  • Передача змін (дифов). В цьому підході, замість відправки повного стану вважається різниця між старим станом і новим і відправляється лише різниця між ними. Різниця може вийдуть різними способами. Наприклад, її може явно запитувати приймаюча сторона (eg. прийшли мені всі дані з березня по червень, решта в мене вже є). Або ж сторона, яка відряджає, сама стежить за тим, які дані вже є у приймаючої, у найпростішому випадку — вважаючи що приймаюча успішно отримала всі старі дані (eg. ось тобі дані за березень; по тобі за квітень; ось тобі за травень ...).
  • Відтворення усіма однакового набору подій. У цьому підході сам стан взагалі не передається. Замість цього передаються події, приводяющие до зміни стану.
У випадку, якщо у вас серіалізовані події мають розмір сильно менший ніж стану, рекомендую використовувати саме третій спосіб. Однак він вимагає акуратного реалізаці. Іноді цей спосіб використовують у комбінації з першим (наприклад, в бд redis при master-slave синхронізації баз — підключився слейв спочатку завантажує повну копію бази а потім починає програвати всі команди синхронно з майстром). Так само цей спосіб дозволяє робити просте журналювання подій. Для цього достатньо просто зберігати всі команди. Так зроблено, наприклад, у грі StarCraft. Збережені команди используютеся в подальшому при перегляді реплеїв.

Рада 6
По можливості вибирайте централізовану архітектуру. Децентралізація виглядає привабливо, однак істотно ускладнює (і іноді сповільнює) реалізацію. При декількох рівноправних вузлах вам необхідно думати про те, хто ж у підсумку буде приймати рішення. Для вирішення цієї задачі розроблено різні схеми прийняття рішень. Найпопулярніші — paxos (повірте, ви не хочете програмувати) і raft. Але, навіть у випадку відсутності необхідності консенсусу вам доведеться використовувати різні хитрі методи вирішення тривіальних для централізованих систем завдань. Наприклад, dht, proof of work для захисту від атак, etc.

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

0 коментарів

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