9.10. TCP – надійний потік байтів

TCP, Протокол керування передачею, є другим сервісом транспортного рівня поверх IP. Якщо UDP найкраще описати як «відправ пакет і сподівайся», то TCP – це «відкрий з’єднання між двома кінцевими точками і трактуй його як двосторонній канал байтів, які інша сторона гарантовано отримає, у правильному порядку та рівно один раз». Більшість інтернет-трафіку використовує саме його, і більшість мережевих операцій камери теж.

9.10.1. Що TCP додає до IP

TCP робить значно більше, ніж UDP. Він додає:

  • З’єднання. До передачі будь-яких даних обидві кінцеві точки обмінюються коротким рукостисканням, домовляючись про початок спілкування. З’єднання має стан – «відкрите», «напівзакрите», «закрите» – який відстежують обидві сторони.

  • Надійна доставка. Кожен байт, надісланий відправником, підтверджується отримувачем. Усе, що не підтверджено протягом тайм-ауту, надсилається знову. Застосунок ніколи не бачить втраченого байту – він бачить лише затримку, поки протокол повторно надсилає дані.

  • Доставка в правильному порядку. Байти надходять у тому ж порядку, у якому були надіслані. Навіть якщо пакети прибувають не по черзі, TCP впорядковує їх до того, як застосунок зчитає дані.

  • Контроль потоку. Якщо отримувач повільний, відправнику дається команда сповільнитися; з’єднання адаптується до темпу слабшої сторони.

  • Контроль перевантаження. Якщо мережа починає скидати пакети, відправник знижує темп до відновлення. Це захищає будь-яке окреме з’єднання від перевантаження каналу.

Усе це відбувається автоматично. Python API, яким користується застосунок, – це просто send(bytes) та recv(n); TCP бере на себе все інше.

9.10.2. Рукостискання

TCP-з’єднання відкривається тристороннім обміном, перш ніж дозволяється передача даних:

A diagram with two columns labelled "client" and "server". An arrow from client to server labelled "SYN", then an arrow from server to client labelled "SYN-ACK", then an arrow from client to server labelled "ACK". Below that a thick arrow labelled "data flowing both ways".

Тристороннє рукостискання. Після того, як обидві сторони підтвердили з’єднання, воно відкрите і може розпочатися передача даних.

Клієнт надсилає пакет SYN (синхронізація) з проханням відкрити з’єднання. Сервер відповідає SYN-ACK (синхронізація + підтвердження), погоджуючись. Клієнт надсилає фінальний ACK для підтвердження. Після цього обміну обидві сторони погоджуються, що з’єднання відкрите, і синхронізують свої лічильники для відстеження надісланих і отриманих байтів.

Рукостискання коштує одного часу обходу затримки до того, як перший корисний байт пройде. У локальних мережах це мілісекунда; для міжконтинентальних з’єднань – приблизно 100 мс. Це основна ціна TCP і причина, чому для коротких одноразових повідомлень іноді краще використовувати UDP.

9.10.2.1. Рукостискання завершення

TCP-з’єднання також закривається з обміном (пакет FIN від кожної сторони). Будь-яка сторона може закрити свою половину з’єднання незалежно; стан напівзакрите, коли одна сторона завершила надсилання, а інша ще передає, є допустимим, хоча й рідкісним. Застосунок зазвичай просто викликає close() і дозволяє протоколу обробити послідовність завершення.

9.10.3. Що TCP не гарантує

Декілька речей, які TCP іноді помилково вважають такими, що він гарантує:

  • Межі повідомлень. Застосунок надсилає потік байтів, а не потік повідомлень. Два виклики send(b"hello") можуть надійти як один recv() рядка b"hellohello", або як два recv()-и різного розміру. Якщо застосунку потрібне розмежування повідомлень, він має додати його самостійно (символ нового рядка, префікс довжини тощо). Наприклад, при передачі JSON-документів через TCP кожен документ потрібно відокремлювати символом нового рядка або іншим маркером.

  • Шифрування. TCP передає байти, надані застосунком, у відкритому вигляді від початку до кінця. Якщо вміст має бути конфіденційним, застосунок повинен загорнути з’єднання в TLS (див. Зашифровані сокети та TLS).

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

  • Виявлення активності тихого з’єднання. Якщо жодна зі сторін не надсилає дані тривалий час, з’єднання технічно ще відкрите, але не може виявити, що інша сторона впала або зникла. Для вирішення цієї проблеми можна увімкнути на сокеті keepalive-зонди.

9.10.4. Коли використовувати TCP

TCP – правильний вибір майже для будь-якої взаємодії, що відповідає шаблону «клієнт відкриває з’єднання до сервера, вони обмінюються байтами, після завершення з’єднання закривається». HTTP та HTTPS-запити, SSH-сесії, запити до баз даних, передача файлів, завантаження зображень – все через TCP.

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

З портами, UDP та TCP – вся розповідь про транспортний рівень завершена. Python API, що надає доступ до обох протоколів, описано на наступній сторінці: Об’єкти сокетів.