И наконец, в каждом TCP-соединении используется таймер, запускаемый в состоянии TIME WAIT при закрытии соединения. Он отсчитывает двойное время жизни пакета, чтобы гарантировать, что после закрытия соединения созданные им пакеты исчезли.
6.5.10. Контроль перегрузки в TCP
Напоследок мы оставили одну из ключевых функций TCP: контроль перегрузки. Когда в сеть (в том числе в интернет) поступает больше данных, чем она способна обработать, возникают перегрузки. Если сетевой уровень узнает, что на маршрутизаторах скопились длинные очереди, он пытается справиться с этой ситуацией (пусть даже простым удалением пакетов). Транспортный уровень получает обратную связь от сетевого уровня, что позволяет ему следить за перегрузкой и при необходимости снижать скорость отправки. В интернете протокол TCP так же незаменим при контроле перегрузки, как и при транспортировке данных. Именно это делает его особенным.
Общие вопросы контроля перегрузки мы обсуждали в разделе 6.3. Основная мысль заключается в следующем: транспортный протокол, использующий закон управления AIMD при получении двоичных сигналов сети о перегрузке, сходится к справедливому и эффективному распределению пропускной способности. Контроль перегрузки в TCP реализует этот подход с помощью окна, а в качестве сигнала используется потеря пакетов. TCP поддерживает окно перегрузки (congestion window), размер которого равен числу байтов, которое отправитель может передавать по сети в любой момент времени. Соответственно, скорость отправки равна размеру окна, деленному на RTT. Размер окна задается в соответствии с правилом AIMD.
Напомним, что окно перегрузки существует в дополнение к окну управления потоком, определяющему количество байтов, которое получатель может поместить в буфер. Они отслеживаются параллельно, и число байтов, которое отправитель может передать в сеть, равно размеру меньшего из этих окон. Таким образом, эффективное окно — наименьшее из подходящих отправителю и получателю. Здесь необходимо участие обеих сторон. TCP останавливает отправку данных, если одно из окон временно заполнено. Если получатель говорит: «Высылайте 64 Кбайт», но при этом источник знает, что отправка более 32 Кбайт засорит сеть, он все же передаст 32 Кбайт. Если же отправителю известно, что сеть способна пропустить и большее количество данных, например 128 Кбайт, он передаст столько, сколько просит получатель (то есть 64 Кбайт). Окно управления потоком было описано ранее, поэтому в дальнейшем мы будем говорить только об окне перегрузки.
Современная схема контроля перегрузки была реализована в TCP во многом благодаря стараниям Ван Джейкобсона (Van Jacobson, 1988). Это поистине захватывающая история. Начиная с 1986 года рост популярности интернета привел к возникновению ситуаций, которые позже стали называть отказом сети из-за перегрузки (congestion collapse), — длительных периодов, во время которых полезная пропускная способность резко падала (более чем в 100 раз) из-за перегрузки сети. Джейкобсон (и многие другие) решил разобраться в ситуации и придумать конструктивное решение.
В результате Джейкобсону удалось реализовать высокоуровневое решение, состоявшее в использовании метода AIMD для выбора окна перегрузки. Особенно интересно, что при всей сложности контроля перегрузки в TCP он смог добавить его в уже существующий протокол, не изменив ни одного формата сообщений. Благодаря этому новое решение можно было сразу применять на практике. Сначала Джейкобсон заметил, что потеря пакетов является надежным сигналом перегрузки, даже несмотря на то что эта информация приходит с небольшим опозданием (когда сеть уже перегружена). В конце концов, трудно представить себе маршрутизатор, который не удаляет пакеты при перегрузке, и в дальнейшем это вряд ли изменится. Даже когда буферная память будет исчисляться терабайтами, вероятно, мы будем использовать терабитные сети, которые будут ее заполнять.
Здесь есть одна тонкость. Дело в том, что использование потери пакетов в качестве сигнала перегрузки предполагает, что ошибки передачи происходят сравнительно редко. В случае беспроводных сетей (таких, как 802.11) это не так, поэтому в них используются собственные механизмы повторной передачи данных на канальном уровне. Из-за особенностей повторной передачи в таких сетях потеря пакетов на сетевом уровне, вызванная ошибками передачи, обычно не учитывается. Столь же редко это происходит в проводных и оптоволоконных сетях, поскольку их частота ошибок по битам обычно низкая.
Все алгоритмы TCP для интернета основаны на том предположении, что пакеты теряются из-за перегрузок. Поэтому они внимательно отслеживают тайм-ауты и пытаются обнаружить любые признаки проблемы подобно тому, как шахтеры следят за своими канарейками34. Чтобы узнавать о потере пакетов вовремя и с высокой точностью, необходим хороший таймер повторной передачи. Мы уже говорили о том, как такие таймеры в TCP учитывают среднее значение и отклонение RTT. Усовершенствование таймеров путем учета отклонений стало важным шагом в работе Джейкобсона. Если время ожидания повторной передачи выбрано правильно, TCP-отправитель может отследить количество исходящих байтов, нагружающих сеть, — достаточно сравнить порядковые номера переданных и подтвержденных пакетов.
Теперь наша задача выглядит просто. Все, что нам нужно, — это следить за размером окна перегрузки (с помощью порядковых номеров и номеров подтверждений) и менять его, следуя правилу AIMD. Но как вы уже догадались, на самом деле все гораздо сложнее. Во-первых, способ отправки пакетов в сеть (даже через короткие промежутки времени) должен соответствовать сетевому пути, иначе возникнет перегрузка. Допустим, хост с окном перегрузки 64 Кбайт подключен к коммутируемой сети Ethernet, работающей на скорости 1 Гбит/с. Если хост отправит целое окно за один раз, всплеск трафика может пройти через медленную ADSL-линию (1 Мбит/с), расположенную далее на пути. Всплеск, который длился всего половину миллисекунды на гигабитной линии, парализует медленную линию на целых полсекунды, полностью блокируя такие протоколы, как VoIP. В итоге мы получим отличный протокол для создания перегрузок, а не для борьбы с ними.
Однако отправка небольших порций пакетов может быть полезной. На илл. 6.43 показано, что произойдет, если хост-отправитель, подключенный к быстрой линии (1 Гбит/с), отправит небольшую порцию пакетов (4) получателю, находящемуся в медленной сети (1 Мбит/с), которая является узким местом пути или его самой медленной частью. Сначала эти четыре пакета перемещаются по сети с той скоростью, с которой они были отправлены. Затем маршрутизатор помещает их в очередь, так как они приходят по высокоскоростной линии быстрее, чем передаются по медленной. Это не слишком длинная очередь, поскольку число пакетов, отправленных за один раз, невелико. Обратите внимание, что на медленной линии одни и те же пакеты выглядят длиннее, чем на быстрой, так как их передача длится дольше.
Илл. 6.43. Порция пакетов, переданная отправителем, и скорость прихода подтверждений
Наконец, пакеты попадают к адресату, и он подтверждает их получение. Время отправки подтверждения зависит от времени прибытия пакета по медленному каналу. Поэтому на обратном пути расстояние между пакетами будет больше, чем в самом начале, когда исходные пакеты перемещались по быстрой линии. Оно не изменится на протяжении всего прохождения подтверждений через сеть и обратно.
Здесь особенно важно следующее: подтверждения приходят к отправителю примерно с той же скоростью, с которой пакеты могут передаваться по самому медленному каналу пути. Именно она и нужна отправителю. Если он будет передавать пакеты в сеть с такой скоростью, они будут перемещаться настолько быстро, насколько позволяет самая медленная линия, но зато не будут застревать в очередях на маршрутизаторах. Эта скорость называется скоростью прихода подтверждений (ack clock) и является неотъемлемой частью TCP. Данный параметр позволяет TCP выровнять трафик и избежать ненужных очередей на маршрутизаторах.