Пакеты, 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 кБ, и более широкий канал ничего не меняет, ведь стоимость считается круговыми обходами, а не байтами.