Эффективное программирование TCP-IP

       

Различайте протоколы, требующие и не требующие установления логического соединения


| | |

Один из фундаментальных вопросов сетевого программирования - это различие между протоколами, требующими установления логического соединения (connection-oriented protocols), и протоколами, не требующими этого (connectionless protocols). Хотя ничего сложного в таком делении нет, но начинающие их часто путают. Частично проблема кроется в выборе слов. Очевидно, что два компьютера должны быть как-то «соединены», если необходимо наладить обмен данными между ними. Тогда что означает «отсутствие логического соединения»?

О наличии и отсутствии логического соединения говорят применительно к протоколам. Иными словами, речь идет о способе передачи данных по физическому носителю, а не о самом физическом носителе. Протоколы, требующие и не требующие логического соединения, могут одновременно разделять общий физический носитель; на практике обычно так и бывает.

Но если это деление не имеет ничего общего с физическим носителем, по которому передаются данные, то что же лежит в его основе? Главное различие в том, что в протоколах, не требующих соединения, каждый пакет передается независимо от остальных. Тогда как протоколы, устанавливающие соединение, поддержи­вают информацию о состоянии, которая позволяет следить за последовательностью пакетов.

При работе с протоколом, не требующим соединения, каждый пакет, именуемый оатаграммой, адресуется и посылается приложением индивидуально (совет 30).С точки зрения протокола каждая датаграмма - это независимая единица, не имеющая ничего общего с другими датаграммами, которыми обмениваются приложения.

Примечание: Это не означает, что датаграммы независимы с точки зрения приложения. Если приложение реализует нечто более сложное, чем простой протокол запрос- ответ (клиент посылает серверу одиночный запрос и ожидает одиночного ответа на него), то, скорее всего, придется отслеживать состояние. Но суть в том, что приложение, а не протокол, отвечает за поддержание информации о состоянии. Пример сервера, который не требует установления соединения, но следит за последовательностью датаграмм, приведен в листинге 3.6.


Обычно это означает, что клиент и сервер не ведут сложного диалога, - клиент посылает запрос, а сервер отвечает на него. Если позже клиент посылает новый запрос, то с точки зрения протокола это новая транзакция, не связанна с предыдущей.
Кроме того, протокол не обязательно надежен, то есть сеть предпримет все возможное для доставки каждой датаграммы, но нет гарантий, что ни одна не будет потеряна, задержана или доставлена не в том порядке.
С другой стороны, протоколы, требующие установления соединения, самостоятельно отслеживают состояние пакетов, поэтому они используются в приложениях, ведущих развитый диалог. Сохраняемая информация о состоянии позволяет протоколу обеспечить надежную доставку. Например, отправитель запоминает, когда и какие данные послал, но они еще не подтверждены. Если подтверждение не приходит в течение определенного времени, отправитель повторяет передачу. Получатель запоминает, какие данные уже принял, и отбрасывает пакеты-дубликаты. Если пакет поступает не в порядке очередности, то получатель может «придержать» его, пока не придут логически предшествующие пакеты.
У типичного протокола, требующего наличия соединения, есть три фазы. Сначала устанавливается соединение между двумя приложениями. Затем происходит обмен данными. И, наконец, когда оба приложения завершили обмен данными, соединение разрывается.
Обычно такой протокол сравнивают с телефонным разговором, а протокол, не требующий соединения, - с отправкой письма. Каждое письмо запечатывается в отдельный конверт, на котором пишется адрес. При этом все письма оказываются самостоятельными сущностями. Каждое письмо обрабатывается на почте независимо от других посланий двух данных корреспондентов. Почта не отслеживает историю переписки, то есть состояние последовательности писем. Кроме того, не гарантируется, что письма не затеряются, не задержатся и будут доставлены в правильном порядке. Это соответствует отправке датаграммы протоколом, не требующим установления соединения.
Примечание: Хаверлок [Haverlock 2000] отмечает, что более правильная аналогия - не письмо, а почтовая открытка, так как письмо с неправильным адресом возвращается отправителю, а почтовая открытка - никогда (как и в типичном протоколе, не требующем наличия соединения).




А теперь посмотрим, что происходит, когда вы не посылаете письмо другу, а звоните по телефону. Для начала набираете его номер. Друг отвечает. Некоторое время вы разговариваете, потом прощаетесь и вешаете трубки. Так же обстоит дел и в протоколе, требующем соединения. В ходе процедуры установления соединения одна из сторон связывается с другой, стороны обмениваются «приветствиями» (на этом этапе они «договариваются» о тех параметрах и соглашениях, кот рым будут следовать далее), и соединение вступает в фазу обмена данными.
Во время телефонного разговора звонящий знает своего собеседника. И перед каждой фразой не нужно снова набирать номер телефона - соединение установлено. Алогично в фазе передачи данных протокола, требующего наличия соединения, надо передавать свой адрес или адрес другой стороны. Эти адреса - часть информации о состоянии, хранящейся вместе с логическим соединением. Остается только посылать данные, не заботясь ни об адресации, ни о других деталях, связанных с протоколом.
Как и в разговоре по телефону, каждая сторона, заканчивая передачу данных, формирует об этом собеседника. Когда обе стороны договорились о завершении, они выполняют строго определенную процедуру разрыва соединения.
Примечание: Хотя указанная аналогия полезна, но она все же не точна. В телефонной сети устанавливается физическое соединение. А приводимое «соединение» целиком умозрительно, оно состоит лишь из хранящейся на обоих концах информации о состоянии. Что - бы должным образом понять это, подумайте, что произойдет, если хост на одном конце соединения аварийно остановится и начнет перезагружаться. Соединение все еще есть? По отношению к перезагрузившемуся хосту — конечно, нет. Все соединения установлены в его «прошлой жизни». Но для его бывшего «собеседника» соединение по-прежнему существует, так коку него все еще хранится информация о состоянии, и не произошло ничего такого, что сделало бы ее недействительной.
В связи с многочисленными недостатками протоколов, не требующих соединения, возникает закономерный вопрос: зачем вообще нужен такой вид протоколов? Позже вы узнаете, что часто встречаются ситуации, когда для создания приложения использование именно такого протокола оправдано. Например, протокол без соединения может легко поддерживать связь одного хоста со многими и наоборот. Между тем протоколы, устанавливающие соединение, должны обычно организовать по одному соединению между каждой парой хостов. Важно, что протоколы, не требующие наличия соединения, - это фундамент, на котором строятся более сложные протоколы. Рассмотрим набор протоколов TCP/IP. В совете 14 говорится, что TCP/IP - это четырехуровневый стек протоколов (рис. 2.1).



Рис.2. 1 Упрощенное представление стека протоколов TCP/IP
Внизу стека находится интерфейсный уровень, который связан непосредственно с аппаратурой. Наверху располагаются такие приложения, как telnet, ftp и другие стандартные и пользовательские программы. Как видно из рис. 2.1, TCP и UDP построены поверх IP. Следовательно, IP - это фундамент, на котором возведено все здание TCP/IP. Но IP представляет лишь ненадежный сервис, не требующий установления соединения. Этот протокол принимает пакеты с выше расположенных приложенных уровней, обертывает их в IP-пакет и направляет подходящему аппаратному интерфейсу для отправки в сеть. Послав пакет, IP, как и все протоколы, не устанавливающие соединения, не сохраняет информацию о нем.
В этой простоте и заключается главное достоинство протокола IP. Поскольку IP не делает никаких предположений о физической среде передачи данных, он может работать с любым носителем, способным передавать пакеты. Так, IP работает на простых последовательных линиях связи, в локальных сетях на базе технологий Ethernet и Token Ring, в глобальных сетях на основе протоколов Х.25 и ATM (Asynchronous Transfer Mode - асинхронный режим передачи), в беспроводных сетях CDPD (Cellular Digital Packet Data - сотовая система передачи пакетов цифровых данных) и во многих других средах. Хотя эти технологии принципиально различны, с точки зрения IP они не отличаются друг от друга, поскольку способны передавать пакеты. Отсюда следует важнейший вывод: раз IP может работать в любой сети с коммутацией пакетов, то это относится и ко всему набору протоколов TCP/IP.
А теперь посмотрим, как протокол TCP пользуется этим простым сервисом, чтобы организовать надежный сервис с поддержкой логических соединений. Поскольку TCP-пакеты (они называются сегментами) посылаются в составе 1Р-датаграмм, у TCP нет информации, дойдут ли они до адреса, не говоря о возможности искажения данных или о доставке в правильном порядке. Чтобы обеспечить надежность, TCP добавляет к базовому IP-сервису три параметра. Во-первых, в ТСР-сегмент включена контрольная сумма содержащихся в нем данных. Это позволяет в пункте назначения убедиться, что переданные данные не повреждены сетью во время транспортировки. Во-вторых, TCP присваивает каждому байту порядковый номер, так что даже если данные прибывают в пункт назначения не в том порядке, в котором были отправлены, то получатель сможет собрать из них исходное сооб­щение.


Примечание: Разумеется, TCP не передает порядковый номер вместе с каждым байтом. Просто в заголовке каждого TCP-сегмента хранится порядковый номер первого байта. Тогда порядковые номера остальных байтов можно вычислить.
В-третьих, в TCP имеется механизм подтверждения и повторной передачи. который гарантирует, что каждый сегмент когда-то будет доставлен.
Из трех упомянутых выше добавлений механизм подтверждения/повторной передачи самый сложный, поэтому рассмотрим подробнее его работу.
Примечание: Здесь опускаются некоторые детали. Это обсуждение поверхностно затрагивает многие тонкости протокола TCP и их применение для обеспечения надежного и отказоустойчивого транспортного механизма. Более доступное и подробное изложение вы можете найти в RFC 793 [Pastel 1981b] и RFC 1122 [Braden1989], в книге [Stevens 1994]. В RFC 813 [Clark 1982] обсуждается механизм окон и подтверждений TCP.
На каждом конце TCP-соединения поддерживается окно приема, представляющее собой диапазон порядковых номеров байтов, который получатель готов принят отправителя. Наименьшее значение, соответствующее левому краю окна, - это порядковый номер следующего ожидаемого байта. Наибольшее значение, соответствующее правому краю окна, - это порядковый номер последнего байта, для косого у TCP есть место в буфере. Использование окна приема (вместо посылки только номера следующего ожидаемого байта) повышает надежность протокола счет предоставления средств управления потоком. Механизм управления потоком предотвращает переполнение буфера TCP.
Когда прибывает TCP-сегмент, все байты, порядковые номера которых оказываются вне окна приема, отбрасываются. Это касается как ранее принятых данных (с порядковым номерами левее окна приема), так и данных, для которых нет места в буфере (с порядковым номерами правее окна приема). Если первый допустимый байт в сегменте не является следующим ожидаемым, значит, сегмент прибыл не по порядку. В большинстве реализаций TCP такой сегмент помещается в очередь и находится в ней, пока не придут пропущенные данные. Если же номер первого допустимого байта совпадает со следующим ожидаемым, то данные становятся доступными для приложения, а порядковый номер следующего ожидаемого байта увеличивается на число байтов в сегменте. В этом случае считается, что окно сдви­гается вправо на число принятых байтов. Наконец, TCP посылает отправителю подтверждение (сегмент АСК), содержащее порядковый номер следующего ожидаемого байта.


Например, на рис. 2. 2а окно приема обведено пунктиром. Вы видите, что порядковый номер следующего ожидаемого байта равен 4, и TCP готов принять 9 байт (с 4 по 12). На рис. 2.26 показано окно приема после поступления байтов с номерами 4-7. Окно сдвинулось вправо на четыре номера, а в сегменте АСК, который пошлет TCP, номер следующего ожидаемого байта будет равен 8.

Рис. 2.2. Окно приема TCP
Теперь рассмотрим эту же ситуацию с точки зрения протокола TCP на посылающем конце. Помимо окна приема, TCP поддерживает окно передачи, разделенное на две части. В одной из них расположены байты, которые уже отосланы, но еще не подтверждены, а в другой – байты, которые еще не отправлены. Предполагается, что на байты 1-3 уже пришло подтверждение, поэтому на рис. 2.3а изображено окно передачи, соответствующее окну приема на рис. 2.2а. на рис. 2.3б вы видите окно передачи после пересылки байтов 4-7, но до прихода подтверждения. TCP еще может послать байты 8-12, не дожидаясь подтверждения от получателя. После отправки байтов 4-7 TCP начинает отсчет тайм – аута ретрансмиссии (retransmission timeout - RTO). Если до срабатывания таймера не пришло подтверждение на все четыре байта, TCP считает, что они потерялись, и посылает их повторно.
Примечание: Поскольку в многих реализациях не происходит отслеживания того, какие байты были посланы в конкретном сегменте, может случиться, что повторно переданный сегмент содержит больше байтов, чем первоначальный. Например, если байты 8 и 9 были посланы до срабатывания RTO-таймера, то такие реализации повторно передадут байты с 4 по 9.
Обратите внимание, что срабатывание RTO-таймера не означает, что исходные данные не дошли до получателя. Например, может потеряться АСК - сегмент с подтверждением или исходный сегмент задержаться в сети на время, большее чем тайм-аут ретрансмиссии. Но ничего страшного в этом нет, так как если первоначально отправленные данные все-таки прибудут, то повторно переданные окажутся вне окна приема TCP и будут отброшены.


После получения подтверждения на байты 4-7 передающий TCP «забывает» про них и сдвигает окно передачи вправо, как показано на рис. 2.3в

Рис. 2.3. Окно передачи TCP
TCP обеспечивает прикладного программиста надежным протоколом, требующим установления логических соединений. О таком протоколе рассказываете в совете 9.
С другой стороны, UDP предоставляет программисту ненадежный сервис, не требующий соединения. Фактически UDP добавляет лишь два параметра к протоколу IP, поверх которого он построен. Во-первых, необязательную контрольную сумму для обнаружения искаженных данных. Хотя у самого протокола IP тоже есть контрольная сумма, но вычисляется она только для заголовка IP-пакета, поэтому TCP и UDP также включают контрольные суммы для защиты собственных заголовков и данных. Во-вторых, UDP добавляет к IP понятие порта. Для отправки IP-датаграммы конкретному хосту используются IP-адреса, то есть адреса, которые обычно приводятся в стандартной десятичной нотации Internet (совет 2). Но по прибытии на хост назначения датаграмму еще необходимо доставить нужному приложению. Например, один UDP-пакет может быть предназначен для сервиса эхо - контроля, а другой - для сервиса «время дня». Порты как раз и дают способ направления данных нужному приложению (этот процесс называют демультиплексированием). С каждым TCP и UDP-сокетом ассоциирован номер порта. Приложение может явно указать этот номер путем обращения к системному вызову bind или поручить операционной системе выбор порта. Когда пакет прибывает, ядро «ищет» в списке сокетов тот, который ассоциирован с протоколом, парой адресов и парой номеров портов, указанных в пакете. Если сокет найден, то данные обрабатываются соответствующим протоколом (в примерах TCP или UDP) и передаются тем приложениям, которые этот сокет открыли.
Примечание: Если сокет открыт несколькими процессами или потоками (thread), то данные может считывать только один из них, и остальным они будут недоступны.
Возвращаясь к аналогии с телефонными переговорами и письмами, можно сказать, что сетевой адрес в TCP-соединении подобен номеру телефона офисной АТС, а номер порта - это добавочный номер конкретного телефона в офисе. Точно так же UDP-адрес можно представить как адрес многоквартирного дома, а номер порта - как отдельный почтовый ящик в его подъезде.

Содержание раздела