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

Пример 1 — Две записи, пришедшие как одно чтение

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

клиент пишет: "LOGIN alice", затем "STATUS"
провод, один пакет: LOGIN aliceSTATUS

сервер:
    chunk = socket.read()
    if chunk == "LOGIN alice": login(chunk)
    elif chunk == "STATUS": status()

Решение

LOGIN aliceSTATUS не совпадает ни с одной ветвью: поток байтов TCP
не имеет границы сообщений, и те же две записи могут разделиться
как "LOGIN ali" плюс "ceSTATUS".

обрамление: отправлять длину перед полезной нагрузкой
клиент пишет: 12 + "LOGIN alice" + 6 + "STATUS"
сервер:
    n = read_exactly(4)        # префикс длины
    msg = read_exactly(n)      # ровно столько байтов

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

Пример 2 — Куда ушли миллисекунды

Страница делает четыре вызова API, и для каждого открывается новое соединение. Один вызов занимает 340 мс на ответ в 2 кБ: 90 мс на разрешение, 60 мс на соединение, 90 мс на рукопожатие безопасности и 100 мс до первого байта. Скажите, что платится за соединение и чего стоила бы страница с одним общим соединением.

4 вызова, каждое соединение новое:
  разрешение   90 мс   только первый вызов; дальше попадает в кэш резолвера
  соединение   60 мс   на соединение
  рукопожатие  90 мс   на соединение
  первый байт 100 мс   на запрос

один вызов измерен: 340 мс на тело в 2 кБ

Решение

на соединение: соединение 60 + рукопожатие 90 = 150 мс
новые:   90 + 4*60 + 4*90 + 4*100 = 1090 мс круговых обходов
общие:   90 + 60 + 90 + 4*100     =  640 мс

Установка и рукопожатие платятся за соединение, поэтому четыре соединения платят за них четырежды, а тело в 2 кБ достаточно мало, чтобы его передача была шумом; разрешение же кэшируется после первого поиска, так что платит его только первый вызов, а переиспользование одного соединения убирает 450 мс. Ловушка в том, чтобы винить пропускную способность: тело занимает 2 кБ, и более широкий канал ничего не меняет, ведь стоимость считается круговыми обходами, а не байтами.