Пакеты, TCP/IP и порты

Что это такое

Данные пересекают сеть пакетами — небольшими подписанными кусками, которые по пути могут потеряться, задублироваться или переставиться. Адрес каждой машине и службу доставки по мере возможностей даёт схема адресации IP, а над этой ненадёжной службой TCP выстраивает надёжный упорядоченный поток; UDP обходится теми же пакетами, но без механизмов надёжности. Числа, позволяющие одной машине мультиплексировать много разговоров на один адрес, называются портами.

Почему это важно

Этот слой объясняет, почему сетевые проблемы выглядят именно так, как выглядят. Сообщение, которое обязано дойти, в условиях потерь дойдёт медленно: TCP считает пропавший пакет перегрузкой и замедляется — так протокол работает по замыслу, а не баг в вашем клиенте. Отброшенный пакет порождает повтор, которого вы не увидите. Адрес и порт вместе определяют один эндпоинт, поэтому два сервиса могут делить машину, а правило файрвола, игнорирующее порт, блокирует оба. Словарь этого слоя вы встретите в каждом отчёте об инциденте, который прочтёте.

Ключевые идеи

IP доставляет пакеты по мере возможностей

Каждый пакет несёт адрес источника и назначения, а пересылают его узел за узлом маршрутизаторы, которые знают только следующий узел, но не весь путь. Никто не гарантирует, что пакет дойдёт, дойдёт ровно один раз или дойдёт по порядку, — и это намеренно: пересылка без состояния как раз и позволяет сети расти. Надёжность оставлена концам, и на этом принципе держится весь стек.

ваш хост -> маршрутизатор -> маршрутизатор -> маршрутизатор -> сервер
на каждом узле: посмотреть адрес назначения, переслать дальше

TCP превращает пакеты в поток

TCP добавляет порядковые номера, подтверждения и повторную передачу, поэтому приложение видит упорядоченный поток байтов, а не последовательность кусков. При потерях он ещё и замедляется, поэтому канал с потерями не просто теряет данные, но и снижает пропускную способность. Отсюда же и стоимость установки TCP-соединения: обе стороны договариваются о стартовом номере и подтверждают, что слышат друг друга, а это занимает круговой обход, прежде чем двинется полезная нагрузка.

client -> SYN         -> server
client <- SYN, ACK    <- server
client -> ACK         -> server      (соединение открыто, 1 круговой обход)

UDP сохраняет форму пакетов

UDP отправляет датаграмму без соединения, без порядка и без повторной передачи. Так стоит поступать, когда поздний пакет бесполезен — живой звук, обновление позиции в игре, замер метрики, — и когда приложение само хочет решать, что делать с потерями. Выбирая UDP, вы берёте порядок, дублирование и темп на себя.

Порты разделяют разговоры

Порт — это число в заголовке пакета, а адрес вместе с портом даёт эндпоинт. Сервер слушает общеизвестный порт, например 443, клиент выбирает эфемерный, и операционная система отслеживает пару эндпоинтов как соединение. Именно благодаря такой паре один серверный процесс обслуживает тысячи одновременных клиентов и не путает их данные.

клиент 203.0.113.9:51874  <->  сервер 198.51.100.7:443
клиент 203.0.113.9:51902  <->  сервер 198.51.100.7:443   (другой разговор)

Круговой обход — единица стоимости

Задержка измеряется круговыми обходами, а не байтами. Открытие соединения стоит один обход, поиск имени — один или больше, рукопожатие уровня приложения — ещё один. Полезная нагрузка, которой хватает, чтобы заполнить канал, обходится во время, пропорциональное её размеру, однако в небольших сообщениях преобладает число обходов. Поэтому первое исправление медленного запроса обычно состоит в том, чтобы убрать обход, а не уменьшить нагрузку.

Практика

Посчитайте круговые обходы

Выберите один эндпоинт, который вызывает ваше приложение. С помощью средства захвата пакетов или вывода времени клиента посчитайте круговые обходы между открытием соединения и получением первого байта ответа.

Готово, когда: вы можете перечислить каждый обход по порядку, с его целью, и назвать тот, который дешевле всего убрать.

Почувствуйте разницу от рукопожатия

Напишите клиента, который открывает новое соединение для каждого из десяти запросов, а затем версию, отправляющую все десять через одно соединение. Замерьте оба варианта.

Готово, когда: версия с общим соединением измеримо быстрее, и вы можете отнести выигрыш к установке соединения, а не к полезной нагрузке.

Частые ошибки

  • Отброшенный пакет как ошибка приложения — повторная передача невидима вашему коду, поэтому симптом — задержка, а не ошибка; исправить: измеряйте круговые обходы и потери, прежде чем подозревать логику.
  • Убеждение, что TCP хранит границы сообщений — он доставляет поток байтов, поэтому две записи могут прийти как одно чтение, а одна запись — как два; исправить: обрамляйте сообщения префиксом длины или разделителем.
  • Использование UDP с ожиданием порядка — датаграммы приходят не по порядку, а иногда и не приходят; исправить: добавьте порядковые номера и решите, что делать с пропажей, или возьмите TCP.
  • Путаница адреса с эндпоинтом — правило или лог без порта описывает сразу несколько сервисов; исправить: записывайте адрес и порт вместе.
  • Обвинение пропускной способности в медленном маленьком запросе — в нескольких килобайтах главное — задержка, а не пропускная способность; исправить: посчитайте круговые обходы, прежде чем менять канал.

Что читать дальше