Що краще? Багато невеликих пакетів TCP чи один довгий? [зачинено]


16

Я надсилаю зовсім небагато даних на сервер і з нього, для гри, яку я роблю.

Зараз я надсилаю такі дані про місцезнаходження:

sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));

Очевидно, він посилає відповідні значення X, Y і Z.

Чи було б більш ефективно надсилати такі дані?

sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));

2
За моїм обмеженим досвідом втрата пакетів зазвичай становить менше 5%.
mucaho

5
Насправді sendToClient надсилає пакет? Якщо так, то як ви це зробили?
користувач253751

1
@mucaho Я ніколи не вимірював це сам чи що-небудь, але я здивований, що TCP така груба по краях. Я би сподівався на щось більше, як 0,5% або менше.
Panzercrisis

1
@Panzercrisis Я повинен з вами погодитися. Я особисто вважаю, що втрата на 5% була б неприйнятною. Якщо ви думаєте про щось, як я посилаю, скажімо, новий корабель породив у грі, навіть 1% шансу того, що пакет не буде отриманий, було б згубним, бо я отримаю невидимі кораблі.
joehot200

2
не бідайте, хлопці, я мав на увазі 5% як верхню межу :) насправді це набагато краще, як зазначають інші коментарі.
mucaho

Відповіді:


28

Сегмент TCP має досить багато накладних витрат. Коли ви надсилаєте 10-байтне повідомлення з одним пакетом TCP, ви фактично надсилаєте:

  • 16 байт заголовка IPv4 (збільшиться до 40 байт, коли IPv6 стане загальним)
  • 16 байт заголовка TCP
  • 10 байт корисного навантаження
  • додаткові накладні витрати для використовуваних протоколів передачі даних та фізичного рівня

в результаті 42 байти трафіку для транспортування 10 байт даних. Таким чином, ви використовуєте лише менше 25% наявної пропускної здатності. І це ще не враховує витрату, яку споживають протоколи нижчого рівня, такі як Ethernet або PPPoE (але їх важко оцінити, оскільки існує так багато альтернатив).

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

З цієї причини вам слід спробувати надсилати всі наявні дані відразу в один сегмент TCP.

Щодо обробки втрат пакету : Коли ви використовуєте TCP, вам не потрібно про це турбуватися. Сам протокол гарантує, що будь-які втрачені пакети будуть повторно обрані, а пакети обробляються в порядку, тому ви можете припустити, що всі надіслані вами пакети надійдуть в іншу сторону, і вони надійдуть у тому порядку, коли ви їх надіслали. Ціна цього полягає в тому, що при втраті пакету ваш плеєр зазнає значного відставання, оскільки один скинутий пакет зупинить весь потік даних, поки його повторно не запитають і не отримають.

Коли це проблема, ви завжди можете використовувати UDP. Але тоді ви повинні знайти власне рішення для втрачених і зіпсованих повідомлень (це по крайней мере , гарантує , що повідомлення , які дійсно прибувають, прибуває повне і неушкоджений).


1
Чи варіюється накладні витрати, пов'язані з тим, як TCP відновлює втрати пакетів, залежно від розміру пакетів?
Panzercrisis

@Panzercrisis Тільки в тому випадку, якщо є більший пакет, який потрібно обурити.
Філіп

8
Я зауважу, що ОС майже напевно застосує алгоритм Nagles en.wikipedia.org/wiki/Nagle%27s_algorithm до вихідних даних, це означає, що це не має значення, якщо в додатку ви робите окремий запис або комбінуєте їх, він поєднає їх перш ніж фактично передавати їх через TCP.
Vality

1
@Vality Більшість API сокетів, які я використовував, дозволяють активувати або деактивувати nagle для кожного сокета. Для більшості ігор я б рекомендував деактивувати її, оскільки низька затримка зазвичай важливіша, ніж збереження пропускної здатності.
Філіп

4
Алгоритм Nagle - це одна, але це не єдина причина, по якій дані можуть бути розміщені на стороні відправки. Там немає ніякого способу , щоб надійно дані сили посилає. Крім того, буферизація / фрагментація може відбуватися в будь-якому місці після надсилання даних, або через NAT, маршрутизатори, проксі-сервери, або навіть приймальну сторону. TCP не дає жодних гарантій щодо розміру та термінів, в які ви отримуєте дані, лише те, що вони надійдуть в порядку та надійно. Якщо вам потрібні гарантії розміру, використовуйте UDP. Те, що TCP здається простішим для розуміння, не робить його найкращим інструментом для всіх проблем!
Піжама Panda

10

Один великий (в межах розуму) краще.

Як ви вже говорили, втрата пакетів є основною причиною. Пакети, як правило, надсилаються у кадрах фіксованого розміру, тому краще взяти один кадр з великим повідомленням, ніж 10 кадрів з 10 малих.

Однак зі стандартним TCP це насправді не проблема, якщо ви не відключите його. (Це називається алгоритмом Nagle , і для ігор слід вимкнути його.) TCP буде чекати фіксованого тайм-ауту або поки пакет не буде "заповнений". Де "повним" є деяке трохи магічне число, яке визначається частково розміром кадру.


Я чув про алгоритм Nagle, але чи справді гарна ідея його відключити? Я щойно прийшов із відповіді StackOverflow, де хтось сказав, що це більш ефективно (і з очевидних причин я хочу ефективності).
joehot200

6
@ joehot200 Єдина правильна відповідь на це - "це залежить". Так, це більш ефективно для надсилання великої кількості даних, але не для потокової передачі в реальному часі, якої потрібні ігри.
D-side

4
@ joehot200: Алгоритм Nagle погано взаємодіє з алгоритмом уповільненого підтвердження, який іноді використовують деякі реалізації TCP. Деякі реалізації TCP затримуватимуть надсилання ACK після того, як вони отримають деякі дані, якщо вони очікують, що незабаром з'явиться більше даних (оскільки підтвердження більш пізнього пакету також негайно підтверджує попередній). Алгоритм Nagle говорить, що пристрій не повинен надсилати частковий пакет, якщо він надіслав деякі дані, але не почув підтвердження. Іноді два підходи погано взаємодіють, і кожна сторона чекає, коли інша щось зробить, поки ...
supercat

2
... запускається "таймер розумності" і вирішує ситуацію (на порядок секунди).
supercat

2
На жаль, відключення алгоритму Nagle нічого не призведе до запобігання буферизації з іншого боку хоста. Якщо вимкнути алгоритм Nagle, ви не отримаєте один recv()дзвінок за кожен send()дзвінок, саме це шукає більшість людей. Використання протоколу, який гарантує це, як і UDP. "Коли у вас є TCP, все виглядає як потік"
піжама Panda

6

Усі попередні відповіді невірні. На практиці не має значення, довго випускаєтеsend() або кілька невеликих send()дзвінків.

Як зазначає Філліп, сегмент TCP має деякий накладні витрати, але як програміст програми, ви не маєте контролю над тим, як генеруються сегменти. Простими словами:

Один send()виклик не обов'язково перекладається на один сегмент TCP.

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

Це має декілька наслідків, але найважливішим є те, що:

Один send()виклик або один сегмент TCP не обов'язково перекладається на один успішний recv()виклик на іншому кінці

Причина цього полягає в тому, що TCP є протоколом потоку . TCP розглядає ваші дані як довгий потік байтів і абсолютно не має поняття "пакети". Зsend() ви додаєте байти до цього потоку, а recv()ви отримуєте байти з іншої сторони. TCP буде агресивно буферувати і розділяти ваші дані там, де вважає за потрібне, щоб переконатися, що ваші дані потрапляють на іншу сторону якомога швидше.

Якщо ви хочете відправляти та отримувати "пакети" за допомогою TCP, вам доведеться реалізувати маркери запуску пакетів, маркери довжини тощо. Як щодо використання протоколу, орієнтованого на повідомлення, наприклад UDP? UDP гарантує, що один send()дзвінок перекладається на одну відправлену дейтаграму та на один recv()дзвінок!

Коли у вас є TCP, все виглядає як потік


1
Префіксація кожного повідомлення довжиною повідомлення не така складна.
ysdx

У вас є один перемикач для перевертання, коли мова заходить про агрегацію пакетів, незалежно від того, чи діє алгоритм Nagle чи ні. Не рідкість, що це вимкнено в ігрових мережах, щоб забезпечити швидку доставку недостатньо заповнених пакетів.
Ларс Віклунд

Це повністю ОС або навіть бібліотека. Також у вас є великий контроль - якщо ви хочете. Це правда, що ви не маєте тотального контролю, TCP завжди дозволено поєднувати два повідомлення або розділяти одне, якщо воно не відповідає MTU, але ви все одно можете натякнути це в правильному напрямку. Встановлення різних налаштувань конфігурації, вручну надсилаючи повідомлення на відстань 1 секунду або буферизуючи дані та надсилаючи їх одним знімком.
Дорус

@ysdx: ні, не на стороні, що надсилає, але так - на стороні, що приймає. Оскільки у вас немає гарантій того, де саме ви отримаєте дані recv(), вам потрібно зробити власну буферизацію, щоб компенсувати це. Я б класифікував це в тій же складності, що і впровадження надійності над UDP.
Піжама Панда

Піжама @Pagnda: Наївна реалізація приймаючої сторони: while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }(опускання керування помилками та часткове читання для стислості, але це не сильно змінюється). Це selectне суттєво ускладнює ситуацію, і якщо ви використовуєте TCP, вам, мабуть, доведеться все-таки забуферувати часткові повідомлення. Реалізація надійної надійності над UDP набагато складніше, ніж це.
ysdx

3

Багато невеликих пакетів добре. Насправді, якщо ви турбуєтесь про накладні витрати TCP, просто вставітьbufferstream який збирає до 1500 символів (або як би там не було ваших MTU MTP, краще запитати це динамічно), і вирішуйте проблему в одному місці. Таким чином ви заощаджуєте накладні витрати в розмірі ~ 40 байт за кожен додатковий пакет, який ви інакше створили б.

Однак, все ж краще надсилати менше даних, і там створюють більші об'єкти. Звичайно, надіслати менше, "UID:10|1|2|3ніж надіслати UID:10;x:1UID:10;y:2UID:10;z:3. Насправді, також на цьому етапі ви не повинні винаходити колесо, використовуйте бібліотеку на зразок протобуфа, яка може зменшити такі дані до рядка 10 байт або менше.

Єдине, про що ви не повинні забувати - це вставити Flush команди у свій потік у відповідних місцях, тому що як тільки ви припините додавати дані у свій потік, він може нескінченно чекати, перш ніж він щось надсилатиме. Дійсно проблематично, коли ваш клієнт чекає цих даних, і ваш сервер не буде надсилати нічого нового, поки клієнт не відправить наступну команду.

Втрата пакунків - це те, на що ви можете вплинути тут незначно. Кожен відправлений байт потенційно може бути пошкоджений, і TCP автоматично подасть запит на повторну передачу. Менші пакети означають менший шанс пошкодження кожного пакету, але оскільки вони складаються накладні витрати, ви надсилаєте ще більше байтів, збільшуючи шанси втраченого пакету ще більше. Коли пакет втрачено, TCP буде буферувати всі наступні дані, поки відсутній пакет не буде повторно відправлений та отриманий. Це призводить до великої затримки (пінг). Хоча загальна втрата пропускної здатності через втрату пакету може бути незначною, то вищий пінг буде небажаним для ігор.

Підсумок: надсилайте якомога менше даних, надсилайте великі пакунки та не пишіть власні методи низького рівня для цього, але покладайтеся на добре відомі бібліотеки та методи, як-от bufferstreamі протобуф для обробки важкого підйому.


Насправді для простих речей, таких як це, легко прокатати своє. Набагато простіше, ніж переглядати документацію на 50 сторінок, щоб користуватися бібліотекою іншої людини, а потім після цього вам все-таки доведеться мати справу з їх помилками та дітками.
Pacerier

Правда, писати своє bufferstream- це банально, тому я назвав це методом. Ви все ще хочете обробляти це в одному місці, а не інтегрувати свою буферну логіку з кодом повідомлення. Що стосується об’єктної серіалізації, я дуже сумніваюся, що ви отримаєте щось краще, ніж тисячі людино-годин, які інші вкладають туди, навіть якщо ви намагаєтесь, я настійно пропоную вам порівняти своє рішення з відомими реалізаціями.
Дору

2

Хоча я був неофітом у мережевому програмуванні, я хотів би поділитися своїм обмеженим досвідом, додавши кілька пунктів:

  • TCP має на увазі накладні витрати - ви повинні виміряти відповідну статистику
  • UDP - це фактичне рішення для мережевих ігрових сценаріїв, але всі реалізації, які покладаються на нього, мають додатковий алгоритм на стороні процесора для обліку втрачених або відправлених пакетів

Щодо вимірювань, показники, які слід враховувати:

  • середня і моментальна пропускна здатність
  • середня , максимальна і мінімальна затримка в кінці для таких показників, існуючі інструменти можуть забезпечити швидке рішення. Наприклад: iperf ( https://iperf.fr/ ), D-ITG ( http://traffic.comics.unina.it/software/ITG/ ). Датовий, але все ж корисний документ про налаштування TCP можна знайти на веб- сайті http://dst.lbl.gov/publications/usenix-login.pdf .

Як згадувалося, якщо ви дізнаєтесь, що ви певним чином не обмежені і можете використовувати UDP, ідіть до цього. Існує кілька реалізацій на основі UDP, тому вам не доведеться винаходити колесо або працювати проти багаторічного досвіду та перевіреного досвіду. Такі реалізації, які варто згадати:

  • надійний UDP http://sourceforge.net/projects/rudp/
  • ENET http://enet.bespin.org/ (що навіть може замінити обробку повідомлень TCP, якщо виявиться, що вона працює краще)
  • UDT (протокол передачі даних на основі UDP http://udt.sourceforge.net/ ), який, здається, став нормою в обчислювальних сценаріях HP

Висновок: оскільки реалізація UDP може перевершити (в 3 рази) TCP, має сенс врахувати це, як тільки ви визначите, що ваш сценарій є зручним для UDP. Будьте попереджені! Реалізація повного стеку TCP поверх UDP - це завжди погана ідея.


Я використовував UDP. Я лише перейшов на TCP. Втрата пакетів UDP була просто неприйнятною для важливих даних, необхідних клієнту. Я можу надсилати дані про рух через UDP.
joehot200

Отож, найкраще, що ви можете зробити: дійсно, використовуйте TCP лише для вирішальних операцій АБО використовуйте впровадження програмного протоколу на основі UDP (при цьому Enet є простим, а UDT - добре перевіреним). Але спочатку виміряйте втрати і вирішіть, чи принесе вам УДТ перевагу.
теодрон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.