Протокол TCP
Семёнов Ю.А. (ГНЦ ИТЭФ), book.itep.ru
Протокол TCP (transmission control protocol, RFC-793, -1323, -1644[T/TCP], -2018, -2581, -2582[RENO], -2861, -2873, -2883[SACK], -2923[MTU], -2988[RTO], -3293[GSMP], -3448[TFRC], -3465, -3481) в отличии от UDP осуществляет доставку дейтаграмм, называемых сегментами, в виде байтовых потоков с установлением соединения. Протокол TCP применяется в тех случаях, когда требуется гарантированная доставка сообщений. Он использует контрольные суммы пакетов для проверки их целостности и освобождает прикладные процессы от необходимости таймаутов и повторных передач для обеспечения надежности. Для отслеживания подтверждения доставки в TCP реализуется алгоритм "скользящего" окна. Наиболее типичными прикладными процессами, использующими TCP, являются FTP (file transfer protocol - протокол передачи файлов) и telnet. Кроме того, TCP используют системы SMTP, HTTP, X-window, RCP (remote copy), а также "r"-команды. Внутренняя структура модуля TCP гораздо сложнее структуры UDP. Подобно UDP прикладные процессы взаимодействуют с модулем TCP через порты (см. в предыдущей главе). Под байтовыми потоками здесь подразумевается то, что один примитив, например, read или write (см. ) может вызвать посылку адресату последовательности сегментов, которые образуют некоторый блок данных (сообщение). Использование портов открывает возможность осуществлять несколько соединений между двумя сетевыми объектами (работать с разными процессами).
Примером прикладного процесса, использующего TCP, может служить FTP, при этом будет работать стек протоколов ftp/tcp/ip/ethernet. Хотя протоколы UDP и TCP могли бы для сходных задач использовать разные номера портов, обычно этого не происходит. Модули TCP и UDP выполняют функции мультиплексоров/демультиплексоров между прикладными процессами и IP-модулем. При поступлении пакета в модуль IP он будет передан в TCP- или UDP-модуль согласно коду, записанному в поле протокола данного IP-пакета. Формат сегмента (пакета) TCP представлен ниже на рис. 4.4.3.1.
Если вы хотите глубже разобраться с особенностями работы этого протокола, рекомендуется воспользоваться услугами программы tcpdump, которая позволяет отслеживать содержимое отправляемых и приходящих пакетов в ходе реализации сессии.
Если IP-протокол работает с адресами, то TCP, также как и UDP, с портами. Именно с номеров портов отправителя и получателя начинается заголовок TCP-сегмента. 32-битовое поле код позиции в сообщении определяет порядковый номер первого октета в поле данных пользователя. В приложениях передатчика и приемника этому полю соответствуют 32-разрядные счетчики числа байт, которые при переполнении обнуляются. При значении флага syn=1 в этом поле лежит код ISN (Initial Sequence Number; смотри ниже описание процедуры установления связи), выбираемый для конкретного соединения. Первому байту, передаваемому через созданное соединение, присваивается номер ISN+1. Значение ISN может задаваться случайным образом. Но в UNIX 4.4BSD при загрузке ОС ISN делается равнм 1 (это нарушает требования RFC), а далее увеличивается на 640000 каждые полсекунды. Аналогичная инкрементация осуществляется при установлении нового соединения. В RFC рекомендуется увеличивать счетчик ISN на 1 каждые 4 микросекунды.
32-битовое поле номер октета, который должен прийти следующим содержит код, который на единицу больше номера номера последнего успешно доставленного (принятого) байта. Содержимое этого поля интерпретируется получателем сегмента, только если присутствует флаг ACK. В заголовках всех сегментов, передаваемых после установления соединения это поле заполняется, а флаг AСK=1.
В ТСР предусмотрен режим полнодуплексной передачи. При этом данные могут передаваться в обоих направлениях независимо. В ходе обмена каждая из сторон должна отслеживать позиционные номера передаваемых и принимаемых байт. Если получен сегмент с некоторым кодом поля номер октета, который должен прийти следующим, это означает, что все октеты с номерами меньше указанного в данном поле, доставлены благополучно.
Если благополучно доставлены байты с номерами 0-N, а затем получен сегмент с номерами байтов (N+k) - (N+k+m), такой сегмент будет буферизован, но подтверждения его получения не последует. Вместо этого посылается отклик, с кодом номер октета, который должен прийти следующим =(N+1). В случае получения сегмента с неверной контрольной суммой будет послан отклик, идентичный предыдущему. Дублированные отклики позволяют детектировать потерю пакета.
Поле HLEN - определяет длину заголовка сегмента, которая измеряется в 32-разрядных словах. Это поле нужно, так как в заголовке могут содержаться поля опций пееременной длины. Далее следует поле резерв, предназначенное для будущего использования, в настоящее время должно обнуляться. Поле размер окна сообщает, сколько октетов готов принять получатель (флаг ACK=1) вслед за байтом, указанным в поле номер октета, который должен прийти следующим. Окно имеет принципиальное значение, оно определяет число сегментов, которые могут быть посланы без получения подтверждения. Значение ширины окна может варьироваться во время сессии (смотри описание процедуры "медленного старта"). Значение этого поля равное нулю также допустимо и указывает, что байты вплоть до указанного в поле номер октета, который должен прийти следующим, получены, но адресат временно не может принимать данные. Разрешение на посылку новой информации может быть дано с помощью посылки сегмента с тем же значением поля номер октета, который должен прийти следующим, но ненулевым значением поля ширины окна. Поле контрольная сумма предназначено для обеспечения целостности сообщения. Контрольное суммирование производится по модулю 1. Перед контрольным суммированием к TCP-сегменту добавляется псевдозаголовок, как и в случае протокола , который включает в себя адреса отправителя и получателя, код протокола и длину сегмента, исключая псевдозаголовок. Поле указатель важной информации представляет собой указатель последнего байта, содержащий информацию, которая требует немедленного реагирования.
Поле имеет смысл лишь при флаге URG=1, отмечающем сегмент с первым байтом "важной информации". Значение разрядов в 6-битовом коде флаги описано в таблице 4.4.3.1. Если флаг ACK=0, значение поля номер октета, который должен прийти следующим, игнорируется. Флаг URG=1 устанавливается в случае нажатия пользователем клавиш Del или Ctrl-С.
Таблица 4.4.3.1 Значения бит поля флаги
Обозначение битов (слева на право) поля флаги |
Значение бита, если он равен 1 |
URG |
Флаг важной информации, поле Указатель важной информации имеет смысл, если urg=1. |
ACK |
Номер октета, который должен прийти следующим, правилен. |
PSH |
Этот сегмент требует выполнения операции push. Получатель должен передать эти данные прикладной программе как можно быстрее. |
RST |
Прерывание связи. |
SYN |
Флаг для синхронизации номеров сегментов, используется при установлении связи. |
FIN |
Отправитель закончил посылку байтов. |
Рис. 4.4.3.1 Формат TCP-сегмента
Поле
опции зарезервировано на будущее и в заголовке может отсутствовать, его размер переменен и дополняется до кратного 32-бит с помощью поля заполнитель. Формат поля
опции представлен на рис. 4.4.3.2. В настоящее время определены опции:
0 Конец списка опций.
1 Никаких операций. Используется для заполнения поля опции до числа октетов, кратного 4.
2 Максимальный размер сегмента (MSS).
В поле
вид записывается код опции, поле
LEN содержит число октетов в описании опции, включая поля вид и LEN. Определены также опции со значением вид=4,5,6,7. В предложении T/TCP (RFC-1644) описаны опции 11, 12 и 13. Поле
данные может иметь переменную длину, верхняя его граница задается значением
MSS (Maximum Segment Size). Значение MSS может быть задано при установлении соединения каждой из сторон независимо. Для Ethernet MSS=1452 байта.
Рис. 4.4.3.2. Формат опций для TCP-сегментов
Поле данные в TCP-сегменте может и отсутствовать, характер и формат передаваемой информации задается исключительно прикладной программой, теоретически максимальный размер этого поля составляет в отсутствии опций 65495 байт (на практике, помимо MSS, нужно помнить, например, о значении MTU для Ethernet, которое немногим больше 1500 байт).
TCP является протоколом, который ориентируется на согласованную работу ЭВМ и программного обеспечения партнеров, участвующих в обмене информацией. Установление связи клиент-сервер осуществляется в три этапа:
- Клиент посылает SYN-сегмент с указанием номера порта сервера, который предлагается использовать для организации канала связи (active open).
- Сервер откликается, посылая свой SYN-сегмент, содержащий идентификатор (ISN - Initial Sequence Number). Начальное значение ISN не равно нулю. Процедура называется passive open.
- Клиент отправляет подтверждение получения SYN-сегмента от сервера с идентификатором равным ISN (сервера)+1.
Стандартная процедура установления связи представлена на рисунке 4.4.3.3 (под словом “стандартная” подразумевается отсутствие каких-либо отклонений от штатного режима, например, одновременного открывание соединения со стороны сервера и клиента). Если же соединение одновременно инициируется клиентом и сервером, в конечном итоге будет создан только один виртуальный канал.
Рис. 4.4.3.3. Алгоритм установления связи. В рамках представлены состояния клиента и сервера; пунктиром отмечены изменения cостояния после посылки сообщения (см. также рис. 4.4.3.4)
Префикс
S на рисунке указывает на сервер, а
С - на клиента. Параметры в скобках обозначают относительные значения ISN. После установления соединения ISN(S) = s_seq_1, а ISN(C) = c_seq_1.
Каждое соединение должно иметь свой неповторимый код ISN. Для реализации режима соединения прикладная программа на одном конце канала устанавливается в режим пассивного доступа ("passive open"), а операционная система на другом конце ставится в режим активного доступа ("active open"). Протокол TCP предполагает реализацию 11 состояний (established, closed, listen, syn_sent, syn_received и т.д.; см. также RFC-793), переход между которыми строго регламентирован. Машина состояний для протокола TCP может быть описана диаграммой, представленной на рис. 4.4.3.4. Здесь состояние closed является начальной и конечной точкой последовательности переходов.
Каждое соединение стартует из состояния closed. Из диаграммы машины состояний видно, что ни одному из состояний не поставлен в соответствие какой-либо таймер. Это означает, что машина состояний TCP может оставаться в любом из состояний сколь угодно долго. Исключение составляет keep-alive таймер, но его работа является опционной, а время по умолчанию составляет 2 часа. Это означает, что машина состояния может оставаться 2 часа без движения. В случае, когда две ЭВМ (C и S) попытаются установить связь друг с другом одновременно, реализуется режим simultaneous connection (RFC-793). Обе ЭВМ посылают друг другу сигналы SYN. При поучении этого сигнала партнеры посылают отклики SYN+ACK. Обе ЭВМ должны обнаружить, что SYN и SYN+ACK относятся к одному и тому же соединению. Когда C и S обнаружат, что SYN+ACK соответствует посланному ранее SYN, они выключат таймер установления соединения и перейдут непосредственно в состояние syn_recvd (смотри рис. 4.4.3.4).
В состоянии established пакет будет принят сервером, если его ISN лежит в пределах s_ack, s_ack+s_wind (s_wind - ширина окна для сервера; см. рис. 4.4.3.5). Аналогичный диапазон ISN для клиента выглядит как: c_ack, c_ack+c_wind (c_wind - ширина окна для клиента). c_wind и s_wind могут быть не равны. Пакеты, для которых эти условия не выполняются, будут отброшены.
Рассмотрим пример установления соединения для случая FTP-запроса (См. также ). Пусть клиент
С запускает процесс установления FTP-соединения с сервером
s. Обычный порядок установления соединения показан ниже (см. рис. 4.4.3.3):
c -> s:syn(isnc)
s -> c:syn(isns), ack(isnc)
c -> s: ack(isns) (Связь установлена)
c -> s: данные
и/или
s -> c: данные
ISN - идентификатор пакета, посылаемого клиентом (С) или сервером (S). Клиент, послав SYN серверу S, переходит в состояние syn_sent. При этом запускается таймер установления соединения. Как при установлении соединения так и при его разрыве приходится сталкиваться с проблемой двух армий. Представим себе, что имеется две армии А и Б, причем Б больше по численности чем А.
Армия Б разделена на две части, размещенные по разные стороны от армии А. Если две части армии Б одновременно нападут на армию А, победа гарантирована. В то же время нападение на А одной из частей армии Б обрекает ее на поражение. Но как обеспечить одновременность? Здесь предполагается, что радио еще не изобретено и передача сообщений осуществляется вестовыми, которые в нашем случае могут быть перехвачены врагом. Как убедиться, что вестовой дошел? Первое, что приходит в голову, это послать другого вестового с подтверждением. Но он также с некоторой вероятностью может быть перехвачен. А отправитель не будет знать, дошел ли он. Ведь если сообщение перехвачено, отправитель первичного запроса не выдаст команды на начало, так как не уверен, дошло ли его первое сообщение. Возникает вопрос, существует ли алгоритм, который бы гарантировал надежность синхронизации решений путем обмена сообщениями при ненадежной доставке? Повысит ли достоверность увеличение числа обменов между партнерами? Ответом на этот вопрос будет - нет, не существует. В этом читатель, порассуждав логически, может убедиться самостоятельно. Не трудно видеть, что схожие проблемы возникают в любом протоколе, работающем через установление соединения. Чаще всего эта проблема решается путем таймаутов и повторных попыток (это, слава богу, не война и все обходится без людских жертв).
Сервер, получив
SYN, откликается посылкой другого SYN. Когда
С получает SYN от
S (но не получает ACK, например, из-за его потери или злого умысла), он предполагает, что имеет место случай одновременного открытия соединения. В результате он посылает
syn_ack, отключает таймер установления соединения и переходит в состояние syn_received. Сервер получает syn_ack от C, но не посылает отклика. Тогда С ожидает получения syn_ack в состоянии syn_received. Так как время пребывания в этом состоянии не контролируется таймером,
С может остаться в состоянии syn_received вечно. Из-за того, что переходы из состояния в состояние не всегда четко определены, протокол TCP допускает и другие виды атак (некоторые из них описаны в разделе ), там же рассмотрены алгоритмы задания и изменения ISN.
Хотя TCP- соединение является полнодуплексным, при рассмотрении процесса разрыва связи проще его рассматривать как два полудуплексных канала, каждый из которых каналов ликвидируется независимо. Сначала инициатор разрыва посылает сегмент с флагом
FIN, сообщая этим партнеру, что не намерен более что-либо передавать (FIN посылается, как правило в результате вызова приложением функции
close). Когда получение этого сегмента будет подтверждено (ACK), данное направление передачи считается ликвидированным (реализуется полузакрытие соединения). При этом передача информации в противоположном направлении может беспрепятственно продолжаться. Когда партнер закончит посылку данных, он также пошлет сегмент с флагом FIN. По получении отклика ACK виртуальный канал считается окончательно ликвидированным. Таким образом, для установление связи требуется обмен тремя сегментами, а для разрыва - четырьмя. Но протокол допускает совмещение первого ACK и второго FIN в одном сегменте, сокращая полное число закрывающих сегментов с четырех до трех. Партнер, пославший флаг FIN первым, производит активное закрытие соедиения, а противоположный партнер (получивший FIN) отвечает на него своим FIN, осуществляя пассивное закрытие соединения. Инициатором посылки первого FIN может любая из сторон, но чаще это делается клиентом (например, путем ввода команды quit). Полузакрытие используется, например при реализации команды
rsh (запуск операций в удаленном узле).
Машина состояний для протокола TCP не предусматривает изменения состояний при посылке или получении обычных пакетов, содержащих данные.
Всего в машине конечных состояний протокола TCP имеется 11 состояний (CLOSED, LISTEN, SYN_RCVD, SYN_SENT и т.д., введены в RFC-793). Состояние CLOSED является начальной и конечной точкой диаграммы. ESTABLISHED указывает на то, что система находится в состоянии с установленным соединением. Четыре состояния в левом углу помещены в границы зеленой зоны и соответствуют активному закрытию. Состояния CLOSE_WAIT и LAST_ACK относятся к пассивному закрытию.
Переход из состояния SYN_RCVD в LISTEN возможно, если переход в SYN_RCVD осуществлен из состояния LISTEN, а не из состояния SYN_SENT (одновременное открытие двух соединений, получение RST вместо финального ACK).
Рис. 4.4.3.4. Машина состояний для протокола tcp (W.R. Stivens, TCP/IP Illustrated. V1. Addison-Wesley publishing company. 1993. Имеется обновленная версия книги, переведенная на русский язык: У.Ричард Стивенс, "Протоколы TCP/IP. Практическое руководство", BHV, Санкт-Петербург, 2003)
Состояние TIME_WAIT часто называется ожиданием длительностью 2MSL (Maximum Segment Lifetime). Значение MSL задается конкретной реализацией и определяет предельную величину пребывания сегмента в сети. В RFC-793 рекомендуется задавать MSL равным 2 мин. Но нужно помнить, что ТСР-сегмент транспортируется в IP-дейтаграмме, содержащем поле TTL. Когда модуль выполнил активное закрытие и в ответ на FIN послал ACK, соединение должно оставаться в состоянии TIME_WAIT в течение времени, в два раза превышающем MSL. Сокет, используемый данным соединение не может быть задействован другим соединением в продолжении указанного времени. Все сегменты данного соединения, задержавшиеся в пути, во время TIMR_WAITотбрасываются. Это гарантирует то, что сегменты старого соединения не будут восприняты новым соедиением. Такая процедура препятствует перезапуску серверов в течение 1-4 минут, так как в течение данного времени не могут использоваться стандартные значения номеров портов.
Состояние FIN_WAIT_2 сопряжено со случаем, когда одна сторона послала сегмент FIN, а другая сторона подтвердила его получение. Если данное соединение не нужно, можно ждать, когда приложение другой стороны получит код конца файла и пришлет свой флаг FIN. Только после этого система перейдет из состояний FIN_WAIT_2 в состояние TIME_WAIT. Теоретически такое ожидание может быть бесконечным. Другая сторона при этом остается в состоянии CLOSE_WAIT, пока приложение не вызовет функцию
close. Для решения проблемы часто вводят дополнительный таймер.
В ТСР возможна ситуация, когда обе стороны запускают процедуру закрытия одновременно (посылают FIN), что в протоколе ТСР вполне допустимо. Каждая из сторон при этом переходит из состояния ESTABLISHED в состояние FIN_WAIT_1 (после вызова операции closed). По получении FIN стороны переходят из состояния FIN_WAIT_1 в состояние CLOSING и посылают ACK. После получения ACK происходит переход в состояние TIME_WAIT.
Когда оператор, работая в диалоговом режиме, нажимает командную клавишу, сегмент, в который помещается эта управляющая последовательность, помечается флагом PSH (push). Это говорит приемнику, что информация из этого сегмента должна быть передана прикладному процессу как можно скорее, не дожидаясь прихода еще какой-либо информации. Сходную функцию выполняет флаг URG. URG позволяет выделить целый массив данных, так как активизирует указатель последнего байта важной информации. Будет ли какая-либо реакция на эту "важную" информацию определяет прикладная программа получателя. urg-режим используется для прерываний при работе с FTP, telnet или rlogin. Если до завершения обработки "важной" информации придет еще один сегмент с флагом URG, значение старого указателя конца "важного" сообщения будет утеряно. Это обстоятельство должно учитываться прикладными процессами. Так telnet в командных последовательностях всегда помещает префиксный байт с кодом 255.
В режиме удаленного терминала (telnet/ssh) при нажатии любой клавиши формируется и поcылается 41-октетный сегмент (здесь не учитываются издержки Ethernet), который содержит всего один байт полезной информации. В локальной сети здесь проблем не возникает, но в буферах маршрутизаторов в среде Интернет могут возникнуть заторы. Эффективность работы может быть улучшена с помощью алгоритма
Нагля (Nagle, 1984; RFC-896). Нагль предложил при однобайтовом обмене посылать первый байт, а последующие буферизовать до прихода подтверждения получения посланного. После этого посылаются все буферизованные октеты, а запись в буфер вводимых кодов возобновляется.
Если оператор вводит символы быстро, а сеть работает медленно, этот алгоритм позволяет заметно понизить загрузку канала. Встречаются, впрочем, случаи, когда алгоритм Нагля желательно отключить, например, при работе в Интернет в режиме Х-терминала, где сигналы перемещения мышки должны пересылаться немедленно, чтобы не ввести в заблуждение пользователя относительно истинного положения маркера.
Существует еще одна проблема при пересылке данных по каналам TCP, которая называется синдром узкого окна (silly window syndrome; Clark, 1982). Такого рода проблема возникает в том случае, когда данные поступают отправителю крупными блоками, а интерактивное приложение адресата считывает информацию побайтно. Предположим, что в исходный момент времени буфер адресата полон и передающая сторона знает об этом (window=0). Интерактивное приложение считывает очередной октет из TCP-потока, при этом TCP-агент адресата поcылает уведомление отправителю, разрешающее ему послать один байт. Этот байт будет послан и снова заполнит до краев буфер получателя, что вызовет отправку ACK со значением window=0. Этот процесс может продолжаться сколь угодно долго, понижая коэффициент использования канала ниже паровозного уровня.
Кларк предложил не посылать уведомление о ненулевом значении ширины окна при считывании одного байта, а лишь после освобождения достаточно большого пространства в буфере. Например, когда адресат готов принять MSS байтов или когда буфер наполовину пуст.
Предполагается, что получатель пакета практически всегда посылает отправителю пакет-отклик. Отправитель может послать очередной пакет, не дожидаясь получения подтверждения для предшествующего. Таким образом, может быть послано k пакетов, прежде чем будет получен отклик на первый пакет (протокол "скользящего окна").
В протоколе TCP "скользящее окно" используется для регулировки трафика и препятствия переполнения буферов. Идея скользящего окна отображена на рис. 4.4.3.5. Здесь предполагается, что ширина окна равна 7 (k=7; это число может меняться в очень широких пределах).
Рис. 4.4.3.5. Схема использования скользящего окна
После прихода отклика на пакет окно смещается вправо на одну позицию. Теперь отправитель может послать и пакет . Если порядок прихода откликов нарушается, сдвиг окна может задержаться. Размер окна в сегментах определяется соотношением:
window > RTT*B/MSS,
где
B - полоса пропускания канала в бит/с, а
MSS - максимальный размер сегмента в битах, а
window - в сегментах.
Для протокола TCP механизм скользящего окна может работать на уровне октетов или сегментов. В первом случае нужно учитывать каждый раз размер поля данных переданного и подтвержденного сегмента. В TCP-протоколе используется три указателя (стрелки на рис. 4.4.3.3б):
Первый указатель определяет положение левого края окна, отделяя посланный сегмент, получивший подтверждение, от посланного сегмента, получение которого не подтверждено. Второй указатель отмечает правый край окна и указывает на сегмент, который может быть послан до получения очередного подтверждения. Третий указатель помечает границу внутри скользящего окна между уже посланными сегментами и теми, которые еще предстоит послать. Получатель организует аналогичные окна для обеспечения контроля потока данных. Если указатель 3 совпадет с указателем 2, отправитель должен прервать дальнейшее отправление пакетов до получения хотя бы одного подтверждения. Обычно получатель посылает одно подтверждение (ACK) на два полученных сегмента.
Регулирование трафика в TCP подразумевает существование двух независимых процессов: контроль доставки, управляемый получателем с помощью параметра
window, и контроль перегрузки, управляемый отправителем с помощью окна перегрузки
cwnd (congestion window) и
ssthreth (slow start threshold). Первый процесс отслеживает заполнение входного буфера получателя, второй - регистрирует перегрузку канала, а также связанные с этим потери и понижает уровень трафика. В исходный момент времени при установлении соединения cwnd делается равным одному MSS, а ssthreth=65535 байтам.
Программа, управляющая пересылкой, никогда не пошлет больше байт, чем это задано cwnd и объявленным получателем значением window. Когда получение очередного блока данных подтверждено, значение cwnd увеличивается. Характер этого увеличения зависит от того, осуществляется медленный старт или реализуется процедура подавления перегрузки. Если cwnd меньше или равно ssthreth, выполняется медленный старт, в противном случае осуществляется подавление перегрузки. В последнем случае cwndi+1 = cwndi + MSS/8 +(MSS*MSS)/cwnd. Если возникает состояние перегрузки канала значение cwnd снова делается равным одному MSS.
В качестве модуля приращения cwnd используется MSS. При получении подтверждения (ACK) окно перегрузки увеличивается на один сегмент ("медленный старт", CWNDi+1 = CWNDi + размер_сегмента, последнее слагаемое нужно, если размер окна задан в октетах, в противном случае вместо него следует использовать 1) и теперь отправитель может послать, не дожидаясь ACK, уже два сегмента и т.д.. Ширина окна, в конце концов, может стать настолько большой, что ошибка доставки в пределах окна станет заметной. Тогда будет запущена процедура “медленного старта” или другой алгоритм, который определит новое, уменьшенное значение окна. Окно перегрузки позволяет управлять информационным потоком со стороны отправителя, блокируя возможные перегрузки и потери данных в промежуточных узлах сети (о других методах подавления перегрузки канала смотри ). Если переполнения не происходит, CWND становится больше окна, объявленного получателем, и именно последнее будет ограничивать поток данных в канале. Размер окна, объявленный получателем, ограничивается произведением полосы пропускания канала (бит/с) на RTT (время распространения пакета туда и обратно). Максимально допустимый размер окна в TCP равен 65535 байт (задается размером поля). Конечной целью регулирования трафика является установление соответствия между темпом передачи и возможностями приема. Причиной перегрузки может быть не только ограниченность размера буфера, но и недостаточная пропускная способность какого-то участка канала.
С учетом этого обстоятельства каждый отправитель формирует два окна: окно получателя и окно перегрузки (ширина этого окна равна cwnd). Каждое из этих окон задает число байтов, которое может послать отправитель. Реальное число байтов, которое разрешено послать, равно минимальному из этих окон. При инициализации соединения окно перегрузки имеет ширину равную максимальному сегменту, который может быть использован в данном канале. Отправитель посылает такой сегмент. Если будет прислано подтверждение до истечения времени таймаута, размер окна перегрузки удваивается и посылается два сегмента максимальной длины. При получении подтверждения доставки каждого из сегментов окно перегрузки увеличивается на один сегмент максимальной длины. Когда ширина окна перегрузки становится равной B сегментов и все B посланных сегментов получают подтверждение, окно перегрузки возрастает на число байт, содержащихся в этих сегментах. Таким образом, ширина окна перегрузки последовательно удваивается, пока доставка всех сегментов подтверждается. Рост ширины окна перегрузки при этом имеет экспоненциальный характер. Это продолжается до тех пор, пока не наступит таймаут или окно перегрузки не сравняется с окном получателя. Именно эта процедура и называется
медленным стартом (Джекобсон, 1988).
Как было сказано выше, помимо окон перегрузки и получателя в TCP используется третий параметр - порог (иногда он называется порогом медленного старта ssthresh). При установлении соединения ssthresh=64 Kбайт. В случае возникновения таймаута значение
ssthresh делается равным
CWND/2, а само значение CWND приравнивается MSS (см. рис. 4.4.3.6). Далее запускается процедура медленного старта, чтобы выяснить возможности канала. При этом экспоненциальный рост
cwnd осуществляется вплоть до значения
ssthresh. Когда этот уровень
cwnd достигнут, дальнейший рост происходит линейно с приращением на каждом шагу равным
MSS (рис. 4.4.3.6).
Рис. 4.4.3.6. Эволюция ширины окна при медленном старте
Здесь предполагается, что MSS=1 Кбайт.
Началу диаграммы соответствует установка значения ssthresh=16 Kбайт. Данная схема позволяет более точно выбрать значение
cwnd. После таймаута, который на рисунке произошел при передаче c номером 12, значение порога понижается до 12 Кбайт (=cwnd/2). Ширина окна cwnd снова начинает расти от передачи к передаче, начиная с одного сегмента, вплоть до нового значения порога ssthresh=12 Кбайт. Стратегия с экспоненциальным и линейным участками изменения ширины окна переполнения позволяет несколько приблизить среднее его значение к оптимальному. Для локальных сетей, где значение RTT невелико, а вероятность потери пакета мала, оптимизация задания
cwnd не так существенна, как в случае протяженных внешних (например, спутниковых) каналов. Ситуация может поменяться, если в локальной сети имеется фрагмент, где вероятность потерь пакетов велика. Таким фрагментом может быть МАС-бридж (или переключатель), один из каналов которого подключен к сегменту Fast Ethernet, а другой к обычному Ethernet на 10 Мбит/c. Если такой мост не снабжен системой подавления перегрузки (до сих пор такие приборы не имели подобных систем), то каждый из пакетов будет потерян в среднем 9 раз, прежде чем будет передан (здесь предполагается, что передача идет из сегмента FE). При этом cwnd будет практически все время равно MSS, что крайне неэффективно при передаче по каналам Интернет. Такие потери вызовут определенные ошибки при вычислении среднего значения и дисперсии RTT, а как следствие и величин таймаутов. Применение в таких местах маршрутизаторов или других приборов, способных реагировать на перегрузку посредством
ICMP(4), решает эту проблему.
Для взаимного согласования операций в рамках TCP-протокола используется четыре таймера:
- Таймер повторных передач (retransmission; RTO) контролирует время прихода подтверждений (ACK). Таймер запускается в момент посылки сегмента. При получении отклика ACK до истечения времени таймера, он сбраcывается. Если же время таймера истекает до прихода ACK, сегмент посылается адресату повторно, а таймер перезапускается.
- Таймер запросов (persist timer), контролирующий размер окна даже в случае, когда приемное окно закрыто. При window=0 получатель при изменении ситуации посылает сегмент с ненулевым значением ширины окна, что позволит отправителю возобновить свою работу. Но если этот пакет будет потерян, возникнет тупик, тогда каждая из сторон ждет сигнала от партнера. Именно в этой ситуации и используется таймер запросов. По истечении времени этого таймера отправитель пошлет сегмент адресату. Отклик на этот сегмент будет содержать новое значение ширины окна. Таймер запускается каждый раз, когда получен сегмент с window=0.
- Таймер контроля работоспособности (keepalive), который регистрирует факты выхода из строя или перезагрузки ЭВМ-партнеров. Время по умолчанию равно 2 часам. Keepalive-таймер не является частью TCP-спецификации. Таймер полезен для выявления состояний сервера half-open при условии, что клиент отключился (например, пользователь выключил свою персональную ЭВМ, не выполнив LOGOUT). По истечении времени таймера клиенту посылается сегмент проверки состояния. Если в течение 75 секунд будет получен отклик, сервер повторяет запрос 10 раз с периодом 75 сек, после чего соединение разрывается. При получении любого сегмента от клиента таймер сбрасывается и запускается вновь.
- 2MSL-таймер (Maximum Segment Lifetime) контролирует время пребывания канала в состоянии TIME_WAIT. Выдержка таймера по умолчанию равно 2 мин (FIN_WAIT-таймер). См. рис. 4.4.3.4. и RFC-793. Таймер запускается при выполнении процедуры active close в момент посылки последнего ACK.
Важным параметром, определяющим рабочие параметры таймеров, является RTT (время путешествия пакета до адресата и обратно). TCP-агент самостоятельно измеряет RTT. Такие измерения производятся периодически и по их результатам корректируется среднее значение RTT:
RTTm = a*RTTm + (1-a)*RTTi,
где
RTTi - результат очередного измерения,
RTTm - величина, полученная в результате усреднения предыдущих измерений,
а - коэффициент сглаживания, обычно равный 0.9.
RFC- 793 рекомендует устанавливать время таймаута для ретрансмиссии (повторной передачи), значение
RTO - Retransmission TimeOut равно RTO=RTTm*b, где
b равно 2. От корректного выбора этих параметров зависит эффективная работа каналов. Так занижение времени ретрансмиссии приводит к неоправданным повторным посылкам сегментов, перегружая каналы связи. Для более точного выбора RTO необходимо знать дисперсию RTT. Несколько более корректную оценку RTO можно получить из следующих соотношений (предложено Джекобсоном в 1988 году, он же позднее предложил целочисленный алгоритм реализации этих вычислений):
RTTm = RTTm + g(RTTi-RTTm)
D = D + d(|RTTi - RTTm| - D)
RTO = RTTm + 4D,
где
D - среднее отклонение RTT от равновесного значения, а коэффициенты
g = 0,125,
D = 0,25. Чем больше
g, тем быстрее растет
RTO по отношению к RTT. Это хорошо работает до тех пор, пока не произойдет таймаут и ретрансмиссия. В этом случае, получив ACK, трудно решить, какому сегменту соответствует это подтверждение, первому или второму. На эту проблему впервые обратил внимание Фил Карн. Решением проблемы является приостановка коррекции
RTTm при таймауте и ретрансмиссиях. Значение RTO зависит от пропускной способности канала и от специфических задержек, например в случае спутниковых каналов. В основном RTO лежит в секундном диапазоне (5-15 сек). Наиболее вероятная причина потери пакетов - это перегрузка канала на участках между отправителем и приемником. Указанием на то, что пакет потерян, может служить таймаут или получение дубликата сегмента ACK. Если произошел таймаут, система переходит в режим "медленного старта" (ширина окна перегрузки делается равной 1 сегменту, а значение порога медленного старта -
ssthresh делается равным двум сегментам). При инициализации канала переменная
ssthresh обычно равна 65535. Дублирование ACK индицирует потерю пакета до наступления таймаута. В этом случае сначала меняется алгоритм приращения величины окна перегрузки
cwnd (замедляется темп его роста). После прихода очередного ACK новое значение cwnd вычисляется по формуле:
cwndi+1 = cwndi + (размер_сегмента*размер_сегмента)/cwndi + размер_сегмента/8
Если же в этот момент величина окна перегрузки меньше или равна некоторому порогу (
ssthresh - slow start threshold, обычно измеряется в байтах), осуществляется "медленный старт". Следует помнить, что TCP требует посылки немедленного подтверждения (дублированного ACK) при обнаружении прихода сегментов с нарушением порядка следования. Причиной нарушения порядка следования может быть флуктуация задержки в сети или потеря пакета. Если получено три или более задублированных ACK, это является убедительным указанием на потерю пакета и, не дожидаясь таймаута, осуществляется его повторная передача. Перехода в режим медленного старта в этом случае не производится, но понижаются значения
cwnd и
ssthresh (почти вдвое).
Когда TCP-канал закрывается и за время сессии переслано более 16 полых окон, а адресат достижим не через маршрут по умолчанию, то в таблицу маршрутизации заносится следующая информация: усредненное значение RTT, значение дисперсии
RTT и
ssthresh.
Если в ходе TCP-сессии получено сообщение
ICMP(4) (переполнение канала - quench), требующее снижения потока данных, то cwdn делается равным одному сегменту, а величина порога медленного старта ssthresh не изменяется. На ICMP-сообщения о недостижимости сети или ЭВМ программы TCP-уровня не реагируют вообще.
Нулевой размер окна блокирует посылку информации и этим система время от времени пользуется. Что произойдет, если получатель послал сегмент, объявляющий окно ненулевым, а подтверждение получения этого сегмента не прошло? TCP-протокол не предусматривает посылки ACK на само подтверждение. Адресат ждет в этом случае данных, так как он уже объявил о существовании ненулевого окна с помощью соответствующего ACK, а отправитель ждет этого недошедшего ACK, чтобы начать передачу данных. Для разрешения этой тупиковой ситуации используется таймер запросов, который периодически посылает зондирующие сегменты получателю. Цель этого зондирования - выяснение существования окна ненулевой ширины.
Таймер запросов запускается при получении информации об обнулении ширины окна приемником. Если за определенное время не поступает сегмента, сообщающего об изменении размера окна, таймер начинает посылать зондирующие сегменты. Таймер запросов использует базовую временную шкалу с периодом в 500 мсек, а период посылки зондирующих сегментов лежит в диапазоне 5-60 сек. Такой сегмент содержит только один байт данных. Таймер запросов не прерывает своей работы до тех пор, пока не будет подтверждено открытие окна или пока прикладная задача не завершит свою работу, выключив канал связи.
Будучи однажды создан, канал TCP может существовать "вечно". Если клиент и сервер пассивны, они не заметят того, например, что какой-то бульдозер оборвал кабель или спутник связи покоится на дне океана. Чтобы это обнаружить, либо клиент либо сервер должны попытаться послать какую-то информацию. Чтобы информировать систему об этих и подобных им жизненных неурядицах, предусмотрен таймер контроля работоспособности (keepalive). Многим читателям, возможно, приходилось легкомысленно выключать питание своего персонального компьютера, не позаботившись о корректном logout из процедуры telnet или FTP. Если бы не существовало этого таймера, включив ЭВМ, вы бы обнаружили, что "находитесь" в заморском депозитарии, где были вчера. Но таймер контроля работоспособности может и прервать сессию, если какой-то промежуточный маршрутизатор произвел перезагрузку или был вынужден поменять маршрут. Принцип работы таймера работоспособности предельно прост. Если канал пассивен, например, 2 часа, сервер посылает клиенту сегмент-зонд. При этом ЭВМ-клиент может быть в одном из четырех состояний.
- Работоспособен и достижим для сервера. Отклик от клиента сбросит таймер работоспособности в ноль (начало отсчета очередных двух часов).
- Вышел из строя, выключен или перезагружается. Сервер посылает 10 запросов с интервалом 75 сек. Если отклика нет, канал закрывается и со стороны сервера.
- Перезагрузился. Сервер получит отклик типа RESET и канал будет закрыт.
- Работоспособен, но не достижим для сервера. Случай тождественен, описанному во втором по порядку пункте.
Временная постоянная таймера keepalive является системной переменной единой для всех пользователей ЭВМ или даже локальной сети.
Расширение пропускной способности и надежности телекоммуникационных каналов делает актуальной совершенствование протоколов. Так как TCP является основным транспортным протоколом, попытки усовершенствовать его предпринимаются, начиная с 1992 года (RFC-1323, Якобсон, Браден и Борман). Целью этих усовершенствований служит повышение эффективности и пропускной способности канала, а также обеспечение безопасности. При этом рассматриваются следующие возможности:
- увеличение MTU (максимальный передаваемый блок данных);
- расширение окна за пределы 65535 байт;
- исключение "трех-сегментного" процесса установления связи и "четырехсегментного" ее прерывания (T/TCP, RFC-1644);
- совершенствование механизма измерения RTT.
- оптимизация отслеживания CWND.
Оптимальный выбор MTU позволяет минимизировать или исключить фрагментацию (и последующую сборку) сегментов. Верхняя граница на MTU налагается значением MSS (максимальный размер сегмента). Разумно находить и запоминать оптимальные значения MTU для каждого конкретного маршрута. Так как в современных системах используются динамические протоколы маршрутизации, поиск оптимального MTU рекомендуется повторять каждые 10 мин (RFC-1191).
Как уже отмечалось, размер TCP-окна определяется произведением полосы канала (в бит/с) на RTT в сек. Для Ethernet c полосой 10 Мбит/с и RTT=3 мсек это произведение равно 3750 байт, а для канала ИТЭФ-ДЕЗИ с пропускной способностью 1,5 Мбит/с и RTT=710 мсек (спутник) - 88750 байт, а это отнюдь не предел современной телекоммуникационной технологии. Но уже эти примеры говорят о том, что максимально возможный размер окна должен быть увеличен в раз 10-100 уже сегодня. Протокол же разрешает 65535 байт. Появление столь мощных каналов порождает и другие проблемы - потеря пакетов в них обходится слишком дорого, так как "медленный старт" и другие связанные с этим издержки сильно снижают пропускную способность.
В последнее время алгоритм медленного старта заменяется более эффективными алгоритмами.
Простое увеличение ширины окна до тех пор, пока не произойдет сбой, плохая стратегия при использовании традиционного медленного старта, так как заметную часть времени ширина окна будет неоптимальной - то слишком большой, то слишком малой. Оптимальная стратегия должна включать в себя прогнозирование оптимальной ширины окна. В новых версиях модулей TCP реализуются именно такие алгоритмы. В 1994 году Бракмо предложил вариант стратегии изменения параметров передачи, который на 40-70% повышает пропускную способность TCP-канала.
Существуют и другие, могущие показаться забавными проблемы. Каждый сегмент в TCP-протоколе снабжается 32-битным идентификатором. Время жизни IP-пакета (TTL) определяется по максимуму 255 шагами или 255 секундами в зависимости оттого, что раньше наступит. Трудно предсказуемая ситуация может произойти, когда канал ликвидирован, затем создан снова (для той же комбинации IP-адресов и портов), а какой-то пакет из предшествующей сессии, погуляв по Интернет, придет уже во время следующей. Есть ли гарантия, что он будет верно идентифицирован? Одной из мер, упомянутых ранее, можно считать использование ограничения по максимальному времени жизни сегмента (
MSL) или TTL, хотя снижение значения TTL не всегда возможно - ведь IP-пакетами пользуется не только TCP-протокол и нужна очень гибкая система задания его величины. Во многих приложениях MSL=30 сек (рекомендуемое значение 2 мин слишком велико). Технический прогресс ставит и некоторые новые проблемы. Высокопроизводительные каналы (1 Гбит/с) уже сегодня могут исчерпать разнообразие идентификационных кодов пакетов за один сеанс связи. Появление же двух пакетов с равными идентификаторами может породить неразрешимые трудности. Для передачи мегабайтного файла по гигабитному каналу требуется около 40 мсек (при этом предполагается, что задержка в канале составляет 32 мсек (RTT=64 мсек)). Собственно передача этой информации занимает 8 мсек.
Из этих цифр видно, что традиционные протоколы, размеры окон и пр. могут свести на нет преимущества скоростного (дорогостоящего) канала. Пропускная способность такого канала определяется уже не его полосой, а задержкой. Понятно также, что необходимо расширить поле размера окна с 16 до 32 бит. Чтобы не изменять формат TCP-сегментов, можно сделать код размера окна в программе 32-разрядным, сохранив соответствующее поле в сегменте неизменным. Размер окна в этом случае задается как бы в формате с плавающей запятой. При установлении канала определяется масштабный коэффициент n (порядок) лежащий в интервале 0-14. Передача этого коэффициента (один байт) осуществляется сегментом SYN в поле опций. В результате размер окна оказывается равным 65535*2n. Если один из партнеров послал ненулевой масштабный коэффициент, но не получил такого коэффициента от своего партнера по каналу, то
n считается равным нулю. Эта схема позволит сосуществовать старым и новым системам. Выбор
n возлагается на TCP-модуль системы.
Для того чтобы точнее отслеживать вариации RTT, предлагается помещать временные метки в каждый посылаемый сегмент. Так как в TCP используется одно подтверждение ACK на несколько сегментов, правильнее будет сказать, что RTT измеряется при посылке каждого ACK. Способность и готовность партнеров работать в таком режиме временных меток определяется на фазе установления канала. Более точное вычисление RTT позволяет не только корректно выбрать временные постоянные для таймеров, правильно вычислить задержку TIME_WAIT (TIME_WAIT=8*RTO), но и отфильтровать "старые" сегменты. Идеология временных меток используется и в алгоритме PAWS (Protection Against Wrapped Sequence Numbers) для защиты против перепутывания номеров сегментов.
Предлагаемое усовершенствование TCP - T/TCP модифицирует алгоритмы выполнения операций. T/TCP вводит новую 32-битную системную переменную - число соединений (CC). СС позволяет сократить число пересылаемых сегментов при установлении канала, а также отфильтровывать "старые" сегменты, не принадлежащие данной сессии (установленной связи).
Время отклика клиента в рамках указанных алгоритмов сокращается до суммы RTT и времени обработки запроса процессором. Данные пришедшие до SYN-сегмента должны буферизоваться для последующей обработки, а не отбрасываться.
Ethernet (10 Мбит/c) в идеальных условиях позволяет осуществить обмен в рамках протокола TCP (например, при FTP-сессии) со скоростью 1,18 Мбайт/с.
Как уже отмечалось, максимальная длина сегмента (MSS - Maximum Segment Size) в TCP-обменах величина переменная. Длина сегмента определяет длину кадра, в который он вложен. Для локальных Ethernet-сетей MSS=1460 октетов. Чем длиннее кадр, тем выше пропускная способность сети (меньше накладные расходы на заголовок кадра). С другой стороны, при передаче дейтаграмм по внешним каналам, где размер пакета не столь велик, большое значение MSS приведет к фрагментации пакетов, которая замедлит обмен, поэтому администратор сети должен взвешивать последствия, задавая значения размера сегментов. Если MSS явно не задан, устанавливается значение по умолчанию (536 байт), что соответствует 576-байтной IP-дейтаграмме. Для нелокальных адресов - это, как правило, разумный выбор.
Ликвидация связи требует посылки четырех сегментов. TCP-протокол допускает возможность, когда один из концов канала объявляет о прекращении посылки данных (посылает FIN-сегмент), продолжая их получать (режим частичного закрытия - half-close). Посылка сегмента FIN означает выполнение операции active close. Получатель FIN-сегмента должен послать подтверждение его получения. Когда противоположный конец, получивший FIN, закончит пересылку данных, он пошлет свой FIN-сегмент. Прием подтверждения на получение этого сегмента означает закрытие данного канала связи. Возможно прерывание связи и с помощью посылки RST-сегмента. В этом случае все буферы и очереди очищаются немедленно и часть информации будет потеряна.
Содержание раздела