6.5.9. Управление таймерами в TCP
В TCP используется множество таймеров (по крайней мере, в теории). Наиболее важным из них является таймер повторной передачи (Retransmission TimeOut, RTO), который запускается при отправке сегмента. Если подтверждение получения сегмента придет раньше окончания заданного интервала, таймер останавливается. И наоборот, если период ожидания истечет раньше, чем прибудет подтверждение, сегмент передается еще раз (а таймер запускается снова). Соответственно, возникает вопрос: насколько долгим должен быть интервал времени ожидания?
На транспортном уровне эта проблема значительно сложнее, чем в протоколах канального уровня. Например, в 802.11 величина ожидаемой задержки измеряется в микросекундах, и ее довольно легко предсказать (у нее небольшой разброс), поэтому таймер можно установить на момент чуть позднее ожидаемого прибытия подтверждения (илл. 6.42 (а)). Поскольку перегрузок нет, подтверждения на канальном уровне задерживаются редко, поэтому их отсутствие в течение установленного временного интервала с большой вероятностью означает потерю фрейма или подтверждения.
TCP вынужден работать в совершенно иных условиях. Функция плотности вероятности для времени, необходимого для доставки подтверждения TCP, выглядит скорее как график на илл. 6.42 (б), чем на илл. 6.42 (а). Она более пологая и вариативная. Поэтому предсказать, сколько времени потребуется для прохождения данных от отправителя к получателю и обратно, весьма непросто. Даже если бы мы знали, каким должно быть это время, есть еще одна сложность — выбор подходящего интервала ожидания. Если он слишком короткий (например, T1 на илл. 6.42 (б)), возникнут излишние повторные передачи, заполняющие интернет бесполезными пакетами. Если же установить слишком большое значение (T2), то из-за увеличения времени ожидания в случае потери пакета пострадает производительность. При этом среднее значение и величина дисперсии времени прибытия подтверждений могут измениться за несколько секунд при возникновении и устранении перегрузки.
Илл. 6.42. Плотность вероятности времени прибытия подтверждения: (а) на канальном уровне; (б) для TCP
Решение состоит в использовании динамического алгоритма, который постоянно меняет период ожидания, основываясь на измерениях производительности сети. Алгоритм, широко применяемый в TCP, разработан Джейкобсоном (Jacobson) в 1988 году и работает следующим образом. Для каждого соединения в TCP предусмотрена переменная SRTT (Smoothed Round-Trip Time — усредненное время в пути туда-обратно), в которой хранится текущее наилучшее ожидаемое время получения подтверждения для данного соединения. При отправке сегмента запускается таймер, который измеряет время получения подтверждения и повторяет передачу, если оно не приходит в срок. Если подтверждение успевает вернуться до истечения периода ожидания, TCP-подсистема подсчитывает время, которое понадобилось для его получения (R). Затем значение переменной SRTT обновляется по следующей формуле:
где α — весовой коэффициент, определяющий, насколько быстро забываются старые значения. Обычно α = 7/8. Это формула вычисления взвешенного скользящего среднего (Exponentially Weighed Moving Average, EWMA), или фильтра низких частот, с помощью которого можно удалять шум.
Даже при известном значении SRTT выбор периода ожидания подтверждения — нетривиальная задача. В первых реализациях TCP это значение вычислялось как 2xRTT, но опыт показал, что постоянный множитель слишком негибкий и не реагирует на увеличение разброса. В частности, модели очередей случайного (то есть пуассоновского) трафика показывают, что когда нагрузка приближается к пропускной способности, задержка растет и становится крайне изменчивой. В результате может сработать таймер повторной передачи, после чего будет отправлена копия пакета, хотя оригинальный пакет все еще будет находиться в сети. Как правило, такие ситуации возникают именно при высокой нагрузке — и это не самое лучшее время для отправки в сеть лишних пакетов.
Чтобы решить эту проблему, Джейкобсон предложил сделать интервал времени ожидания чувствительным к отклонению RTT и к усредненному RTT. Для этого потребовалась еще одна сглаженная переменная — RTTVAR (RoundTrip Time Variation — изменение времени в пути туда-обратно), которая вычисляется по формуле:
Как и в предыдущем случае, это взвешенное скользящее среднее. Обычно β = 3/4. Значение интервала ожидания повторной передачи (RTO) устанавливается по формуле:
Множитель 4 выбран произвольно, однако умножение целого числа на 4 может быть выполнено одной командой сдвига, при этом менее 1 % всех пакетов придет с опозданием, превышающим четыре среднеквадратичных отклонения. Обратите внимание, что RTTVAR является не среднеквадратичным, а средним отклонением, но на практике это довольно близкие значения. В своей работе Джейкобсон приводит множество хитрых способов вычисления значений интервала ожидания с помощью только целочисленного сложения, вычитания и сдвига. Для современных хостов такая экономия не требуется, но она стала элементом культуры повсеместного применения TCP: он должен работать как на суперкомпьютерах, так и на небольших устройствах. Для RFID-чипов его пока еще не реализовали, но ведь все может быть.
Более подробные сведения о том, как вычислять этот интервал ожидания, а также начальные значения переменных, можно найти в RFC 2988. Для таймера повторной передачи минимальное значение также устанавливается равным 1 с, независимо от предварительной оценки. Это значение, выбранное с запасом (и в основном эмпирически), требуется, чтобы избежать выполнения лишних повторных передач на основании измерений (Оллман и Паксон; Allman and Paxson, 1999).
При сборе данных (R) для вычисления RTT возникает вопрос, что делать при повторной передаче сегмента. Когда для него приходит подтверждение, неясно, к какой передаче оно относится, первой или последней. Неверная догадка может серьезно нарушить работу RTO. Эта проблема была обнаружена радиолюбителем Филом Карном (Phil Karn). Его интересовал вопрос передачи TCP/IP-пакетов с помощью коротковолновой любительской радиосвязи, известной своей ненадежностью. Предложение Карна было очень простым: не обновлять оценки для сегментов, переданных повторно. Кроме того, при каждой повторной передаче время ожидания можно удваивать до тех пор, пока сегменты не пройдут с первой попытки. Это исправление получило название алгоритма Карна (Karn’s algorithm) (Карн и Партридж; Karn and Partridge, 1987) и применяется в большинстве реализаций TCP.
В протоколе TCP используется не только таймер повторной передачи, но и таймер настойчивости (persistence timer). Он предназначен для предотвращения следующей тупиковой ситуации. Получатель отправляет подтверждение, в котором указывает окно нулевого размера (это значит, что отправитель должен подождать). Через некоторое время получатель передает пакет с новым размером окна, но этот пакет теряется. Теперь обе стороны ожидают действий друг от друга. Когда срабатывает таймер настойчивости, отправитель высылает получателю пакет с вопросом, не изменилось ли текущее состояние. В ответ получатель сообщает текущий размер окна. Если он все еще равен нулю, таймер настойчивости запускается снова, и весь цикл повторяется. В случае увеличения окна отправитель может передавать данные.
В некоторых реализациях протокола используется третий таймер, называемый таймером проверки активности (keepalive timer). Он срабатывает, если соединение простаивает в течение долгого времени, заставляя одну сторону проверить, активна ли другая сторона. Если проверяющая сторона не получает ответа, соединение разрывается. Это свойство протокола довольно противоречиво, поскольку привносит дополнительные накладные расходы и может разорвать вполне жизнеспособное соединение из-за кратковременной потери связи.