9.10. TCP – надёжный поток байтов¶
TCP, протокол управления передачей (Transmission Control Protocol), – это второй сервис транспортного уровня поверх IP. Если UDP лучше всего описывается фразой «отправь пакет и надейся», то TCP – это «открой соединение между двумя конечными точками и обращайся с ним как с двусторонней трубой байтов, которые другая сторона гарантированно получит, по порядку и ровно один раз». Большая часть интернет-трафика использует именно его, и большая часть того, что камера делает в сети, тоже работает поверх него.
9.10.1. Что TCP добавляет к IP¶
TCP делает гораздо больше, чем UDP. Он добавляет:
Соединение. Прежде чем начнётся передача каких-либо данных, две конечные точки обмениваются коротким рукопожатием, чтобы договориться о том, что они общаются. У соединения есть состояние – «открыто», «полузакрыто», «закрыто», – которое отслеживают обе стороны.
Надёжная доставка. Каждый байт, который вкладывает отправитель, подтверждается получателем. Всё, что не подтверждено в течение тайм-аута, отправляется заново. Приложение никогда не видит потерянного байта; оно видит лишь задержку, пока протокол выполняет повторную отправку.
Доставка по порядку. Байты приходят в том же порядке, в котором были отправлены. Даже если пакеты приходят к получателю не по порядку, TCP переупорядочивает их перед тем, как приложение их прочитает.
Управление потоком. Если получатель работает медленно, отправителю сообщают, что нужно замедлиться; соединение адаптируется к скорости более слабой стороны.
Управление перегрузкой. Если сеть посередине начинает отбрасывать пакеты, отправитель снижает темп, пока ситуация не восстановится. Это не даёт одному соединению обрушить перегруженный канал.
Всё это происходит автоматически. API на Python, которым пользуется приложение, – это просто send(bytes) и recv(n); всё остальное TCP берёт на себя под капотом.
9.10.2. Рукопожатие¶
TCP-соединение открывается трёхэтапным обменом, прежде чем будет разрешена передача каких-либо данных:
Трёхэтапное рукопожатие. Как только обе стороны подтвердили обмен, соединение открыто и данные могут передаваться.¶
Клиент отправляет пакет SYN (synchronise), запрашивая открытие. Сервер отвечает SYN-ACK (synchronise + acknowledge), принимая запрос. Клиент отправляет завершающий ACK для подтверждения. После этого кругового обмена обе стороны согласны, что соединение открыто, и синхронизировали свои счётчики для отслеживания того, какие байты были отправлены и получены.
Рукопожатие стоит одного времени обхода (round-trip-time) задержки, прежде чем пройдёт первый полезный байт. Для локальных сетей это миллисекунда; для межконтинентальных соединений – примерно 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 на столе история транспортного уровня завершена. API на Python, который предоставляет и то и другое, – на следующей странице: Объекты сокетов.