Не надо заново изобретать TCP
| | |
Как сказано в совете 7, UDP может быть намного производительнее TCP в простых приложениях, где есть один запрос и один ответ. Это наводит на мысль использовать в транзакционных задачах такого рода именно UDP. Однако протокол UDP не слишком надежен, поэтому эта обязанность лежит на приложении.
Как минимум, это означает, что приложение должно позаботиться о тех датаграммах, которые теряются или искажаются при передаче. Многие начинающие сетевые программисты полагают, что при работе в локальной сети такая возможность маловероятна, и потому полностью игнорируют ее. Но в совете 7 было показано, как легко можно потерять датаграммы даже тогда, когда клиент и север находятся на одной машине. Поэтому не следует забывать о защите от потери датаграмм.
Если же приложение будет работать в глобальной сети, то также возможен приход датаграмм не по порядку. Это происходит, когда между отправителем и получателем было несколько маршрутов.
В свете вышесказанного можно утверждать, что любое достаточно устойчивое UDP-приложение должно обеспечивать:
Первое требование можно удовлетворить, если при посылке каждого запроса взводить таймер, называемый таймером ретрансмиссии (retransmission timer), и RTO-таймером. Если таймер срабатывает до получения ответа, то запрос посылается повторно. В совете 20 будет рассмотрено несколько способов эффективного решения этой задачи. Второе требование легко реализуется, если в каждый запрос включить его порядковый номер и обеспечить возврат этого номера сервером вместе с ответом.
Если приложение будет работать в Internet, то фиксированное время срабатывания RTO-таймера не годится, поскольку период кругового обращения (RTT) между двумя хостами может существенно меняться даже за короткий промежуток. Поэтому хотелось бы корректировать значение RTO-таймера в зависимости от условий в сети. Кроме того, если RTO-таймер срабатывает, следует увеличить eгo продолжительность перед повторной передачей, поскольку она, скорее всего была слишком мала. Это требует некоторой экспоненциальной корректировки. (exponential backoff) RTO при повторных передачах.
Далее, если приложение реализует что- либо большее, чем простой протокол запрос- ответ, когда клиент посылает запрос и ждет ответа, не посылая дополнительных датаграмм, или ответ сервера состоит из нескольких датаграмм, то необходим какой-то механизм управления потоком. Например, если сервер - это приложение, работающее с базой данных о кадрах, а клиент просит послать имена и адреса всех сотрудников конструкторского отдела, то ответ будет состоять из нескольких записей, каждая из которых посылается в виде отдельной датаграммы. Если управление потоком отсутствует, то при этом может быть исчерпан пул буферов клиента. Обычный способ решения этой проблемы - скользящее окно типа того, что используется в TCP (только подсчитываются не байты, а датаграммы).
И, наконец, если приложение передает подряд несколько датаграмм, необходимо позаботиться об управлении перегрузкой. В противном случае такое приложение может легко стать причиной деградации пропускной способности, которая затронет всех пользователей сети.
Все перечисленные действия, которые должно предпринять основанное на протоколе UDP приложение для обеспечения надежности, - это, по сути, вариант TCP. Иногда на это приходится идти. Ведь существуют транзакционные приложения, в которых накладные расходы, связанные с установлением и разрывом TCP - соединения, близки или даже превышают затраты на передачу данных.
Примечание: Обычный пример - это система доменных имен (Domain Name System - DNS), которая используется для отображения доменного имени хоста на его IP-адрес. Когда вводится имя хоста www. rfс-editor.org в Web-браузере, реализованный внутри браузера клиент DNS посылает DNS-cepвepy UDP-датаграмму с запросом IP-адреса, ассоциированного с этим именем. Сервер в ответ посылает датаграмму, содержащую IP-адрес 128.9.160.27. Подробнее система DNS обсуждается в совете 29.
Тем менее необходимо тщательно изучить природу приложения, чтобы понять стоит ли заново реализовывать TCP. Если приложению требуется надежность TCP, то, быть может, правильное решение - это использование TCP.
Маловероятно, что функциональность TCP, продублированная на прикладном уровне, будет реализована столь же эффективно, как в настоящем TCP. Отчасти это связано с тем, что реализации TCP - это плод многочисленных экспериментов и научных исследований. С годами TCP эволюционировал по мере того, как публиковались наблюдения за его работой в различных условиях и сетях в том числе и Internet.
Кроме того, TCP почти всегда исполняется в контексте ядра. Чтобы понять, почему это может повлиять на производительность, представьте себе, что происходит при срабатывании RTO-таймера в вашем приложении. Сначала ядру нужно «пробудить» приложение, для чего необходимо контекстное переключение из режима ядра в режим пользователя. Затем приложение должно послать данные. Для этого требуется еще одно контекстное переключение (на этот раз в режим ядра) в ходе которого данные из датаграммы копируются в буферы ядра. Ядро выбирает маршрут следования датаграммы, передает ее подходящему сетевому интерфейсу и возвращает управление приложению - снова контекстное переключена Приложение должно заново взвести RTO-таймер, для чего приходится вновь переключаться.
А теперь обсудим, что происходит при срабатывании RTO-таймера внутри TCP. У ядра уже есть сохраненная копия данных, нет необходимости повторно копировать их из пространства пользователя. Не нужны и контекстные переключения. TCP заново посылает данные. Основная работа связана с передачей данных из буферов ядра сетевому интерфейсу. Повторные вычисления не требуются, так как TCP сохранил всю информацию в кэше.
Еще одна причина, по которой следует избегать дублирования функциональности TCP на прикладном уровне, - потеря ответа сервера. Поскольку клиент не I получил ответа, у него срабатывает таймер, и он посылает запрос повторно. При этом сервер должен дважды обработать один и тот же запрос, что может быть нежелательно. Представьте клиент, который «просит» сервер перевести деньги с одного банковского счета на другой. При использовании TCP логика повторных попыток реализована вне приложения, так что сервер вообще не определит, повторный ли это запрос.
Примечание: Здесь не рассматривается возможность сетевого сбоя или отказа одного из хостов. Подробнее это рассматривается в совете 9
Транзакционные приложения и некоторые проблемы, связанные с применением в них протоколов TCP и UDP, обсуждаются в RFC 955 [Braden 1985]. В этой работе автор отстаивает необходимость промежуточного протокола между ненадежным, но не требующим соединений UDP, и надежным, но зависящим от соединений TCP. Соображения, изложенные в этом RFC, легли в основу предложенной Брейденом протокола TCP Extensions for Transactions (T/TCP), который рассмотрен ниже.
Один из способов обеспечить надежность TCP без установления соединения воспользоваться протоколом Т/ТСР. Это расширение TCP, позволяющее достичь для транзакций производительности, сравнимой с UDP, за счет отказа (как правило) от процедуры трехстороннего квитирования в ходе установления обычного ТСР - соединения и сокращения фазы TIME-WAIT (совет 22) при разрыве соединения.
Обоснование необходимости Т/ТСР и идеи, лежащие в основе его реализации, описаны в RFC 1379 [Braden 1992a]. RFC 1644 [Braden 1994] содержит функциональную спецификацию Т/ТСР, а также обсуждение некоторых вопросов реализации. В работе [Stevens 1996] рассматривается протокол Т/ТСР, приводятся сравнение его производительности с UDP, изменения в API сокетов, необходимые для поддержки нового протокола, и его реализация в системе 4.4BSD.
К сожалению, протокол Т/ТСР не так широко распространен, хотя и реализован в FreeBSD, и существуют дополнения к ядру Linux 2.0.32 и SunOS 4.1.3.
Ричард Стивенс ведет страницу, посвященную Т/ТСР, на которой есть ссылки на различные посвященные этому протоколу ресурсы. Адрес Web-страницы – .