TCP: handshake, TIME_WAIT, keepalive, Nagle

О лекции

До HTTP работает TCP: три рукопожатия, TIME_WAIT, keepalive и Nagle. TCP keepalive проверяет, жив ли сосед. HTTP keep-alive повторно использует соединение. Это разные вещи.

Транспорт и DNS http собеседование

HTTP едет внутри TCP. TCP — надёжная труба: байты доходят, в том же порядке, без дыр. Пока стороны не пожали руки, данных приложения ещё нет.

Соединение — не «просто номер сокета». Это договор двух ядер. Договор задают четыре числа: ваш IP и порт, чужой IP и порт. Это 4-tuple.

Короткий запрос без повторного использования трубы платит handshake каждый раз. Часто ещё и TLS. Длинный простой держит память ядра. Поэтому на бэкенде важно понимать TIME_WAIT, Nagle и два разных keepalive.

Handshake и TLS

Клиент шлёт SYN. Сервер отвечает SYN-ACK. Клиент шлёт ACK. После этого можно писать байты.

TLS добавляет свои круги поверх. В TLS 1.3 обычно один round-trip. В 1.2 больше. Таймаут connect на клиенте режет именно эту фазу, не чтение тела HTTP.

Пример. Браузер открыл https://api.example.com. Сначала TCP handshake, потом TLS, потом уже GET. Если connect timeout = 2s, а сервер не отвечает на SYN, клиент даже не начнёт HTTP.

// client                         server
//   SYN     ----------------->
//           <-----------------  SYN-ACK
//   ACK     ----------------->
//   (байты HTTP/TLS уже здесь)
//
// active close → TIME_WAIT ~ 2MSL
// зачем: запоздавший сегмент ≠ новое соединение на том же 4-tuple
// цена: исходящие порты кончаются при connect без reuse / keep-alive

Три сегмента handshake. TIME_WAIT держит 4-tuple у стороны, которая закрыла первой.

TIME_WAIT

Сторона, которая закрыла первой, не забывает соединение сразу. Ядро держит 4-tuple ещё порядка двух максимальных времён жизни сегмента (2MSL). Это TIME_WAIT.

Зачем: запоздавший пакет старого диалога не должен попасть в новое соединение с теми же четырьмя числами. Это защита протокола, не «сервер завис».

Пример. Скрипт в цикле: connect, GET /ping, close. Тысячи раз в секунду. Каждый раз исходящий порт зависает в TIME_WAIT. Через минуту: cannot assign requested address. Порты кончились. Лечение — пул соединений и HTTP keep-alive, а не «выключить TIME_WAIT».

Keepalive и Nagle

TCP keepalive — редкие зонды «жив ли сосед». По умолчанию это минуты, не миллисекунды. HTTP keep-alive — следующие запросы по уже открытому TCP, без нового handshake. Это разные слои.

Idle timeout прокси может закрыть трубу раньше, чем вы думаете. Три таймера: TCP keepalive, HTTP keep-alive, idle на балансировщике.

Алгоритм Nagle копит мелкие куски, пока не придёт ACK или не наберётся полный сегмент. Для мелкого RPC это лишняя пауза. Часто включают TCP_NODELAY. Delayed ACK вместе с Nagle даёт классическую задержку ~40–200 мс.

  • TCP keepalive — зонды живости соседа на уровне сокета, редкие.
  • HTTP keep-alive — повтор запросов на одном TCP-соединении.
  • Nagle копит мелкие сегменты; для низкой латентности часто TCP_NODELAY.
Типичная ошибка

Смешать TCP keepalive, HTTP keep-alive и idle timeout балансировщика в одну фразу «keepalive». И списать TIME_WAIT на «зависший сервер»: это состояние у закрывшего, обычно у клиента с короткими connect.

На собеседовании

Разведите три keepalive. TIME_WAIT — про запоздавший пакет и 4-tuple, не про HTTP/2. Для мелкого RPC упомяните Nagle и TCP_NODELAY.