Транспортный уровень

Рассмотрим теперь четвертый уровень стека TCP/IP --- уровень TCP. Предыдущий уровень --- сетевой --- дал нам теоретическую возможность доставить пакет до любого получателя. На четвертом решается вопрос реализации этой возможности, для чего необходимо обеспечить подтверждение получения данных получателем. Иными словами, мы должны решить следующий вопрос: доставлен ли IP-пакет?

Получение подтверждения, что переданные данные получены, крайне важно, как для отслеживания качества взаимодействия по сети, так и по ряду других причин. Механизм подтверждения, казалос бы, прост: когда абонент получает данные, он отправляет подтверждение, а когда подтверждение получено, можно отправлять следующий фрагмент данных. Однако такой подход неэффективен, поэтому в протоколе TCP для ускорения работы используются одно подтверждение на несколько принятых пакетов и посылка некоторого объема данных без ожидания подтверждения предыдущих.

Кроме подтверждения доставки, транспортный протокол должен предоставлять следующие возможности. Если исходное сообщение большое, требуется разбить его на пакеты так, чтобы абонент затем мог восстановить исходные данные. На стороне абонента при этом должен быть механизм проверки, все ли пакеты пришли, и механизм сборки сообщения из пакетов. Возможна такая ситуация: данные были отправлены, но абонент ничего не получил. Пусть передаваемая информация разделена, скажем, на четыре пакета, которые отправляются последовательно, один за другим. Допустим, что абонент получает 1-й, 2-й и 4-й пакеты (3-й потерялся "по пути"). Нужно разработать механизм объединения этих четырех пакетов в поток так, чтобы получатель понял, что он не получил именно 3-й пакет (то есть тот 4-й, который он получил, есть именно 4-й, а не 3-й). В противном случае при "перемешивании" пакетов (например, в результате причуд маршрутизации вначале придет 4-й пакет, а только потом - 3-й) будет невозможно восстановить исходный порядок пакетов, а следовательно, и передваваемую информацию. Итак, необходимо решить вопрос манипулирования потоками данных. В случае, когда мы передаем сразу два потока данных, нужно сделать так, чтобы эти потоки друг от друга отличались.

Обеспечение надежной передачи должно начинаться с решения вопроса о подключении. Иными словами, перед тем как данные передавать, следует убедиться в том, что их кто-то будет принимать. К примеру, мы хотим отправить данные абоненту с адресом 158.250.10.1. Однако "существует" ли он для нас --- априори неизвестно. Даже в случае его существования мы не можем гарантировать, что маршрут, по которому пойдет пакет, функционирует корректно. Прежде чем начать передачу данных данному абоненту, следует обменяться с ним вспомогательной информацией. Если абонент не отвечает, то "добраться" до него мы не сможем, так что передавать данные бессмысленно.

Как видно даже из краткого описания возможностей протокола TCP, он передает заметное количество служебной информации. Однако всегда ли необходимо решение всех перечисленных задач? К примеру, если вся информация, которую надо передать, помещается в один пакет (датаграмму), то можно пойти более простым путем. Чтобы была возможность выбора, на транспортном уровне поддерживается два протокола: надежный протокол TCP и ненадежный, протокол UDP.

Итак, можно выделить пять задач надежного транспортного протокола:

Эти пять задач обычно решаются именно на уровне протокола TCP. Можно считать, что это пять требований к решению вопроса надежной доставки.

Как уже было сказано, помимо протокола TCP существует еще и протокол UDP. Следует разобраться, когда какой протокол использовать. Разберем подробнее указанный пример. Допустим, мы хотим послать ровно один пакет. Нужны ли нам в этом случае организация из него потока данных, отслеживание качества канала, или даже установка соединения? Если вся информация, которую мы собираемся передавать, умещается в один акт передачи данных, то ничего из перечисленного организовывать не нужно. В крайнем случае, повтор пересылаемых данных можно переложить на "вышележащий" протокол. Поскольку проверка контрольной суммы осуществляется и при использовании протокола UDP, то единственная возможная проблема --- потеря пакета. Но в случае соединения по протоколу TCP дела обстоят не лучше: если первые пакеты при попытке установить соединение не дошли до абонента, то соединение просто не установится. Следовательно, в нашем случае разумно использовать UDP, а потом просто проверить: дошел ли наш пакет? К примеру, именно так устроен протокол DNS: на UDP-запрос должен прийти ответ, который, во-первых, подтверждает корректную доставку и, во-вторых, несет содержательную информацию.

Именно по этой причине на транспортном уровне всего два протокола передачи данных. Если на уровне IP-протокола их гораздо больше (к примеру, протоколы туннелирования, L2TP и прочие), то здесь их в точности два. Один из них пять перечисленных свойств поддерживает - это TCP. Другой же не поддерживает ни одного из них, потому что все заключено в одной посылке данных, - это UDP.

TCP

Рассмотрим, как устроено трехуровневое подключение по TCP.

  1. Все начинается с установления подключения. Подключение по TCP двустороннее, то есть данные передаются в обе стороны. Клиент - это тот, кто инициирует подключение ("программа, которая хочет"), а сервер - тот, кто на него отвечает ("программа, которая может"). Итак, инициатор - клиент - подключается к серверу. Данные в дальнейшем передаются как от клиента к серверу, так и от сервера к клиенту.
  2. TCP устроен по принципу подтверждения: на каждый TCP-пакет (а он может быть гораздо больше, чем IP-пакет!), после того как он принят сервером, генерируется подтверждение, если все принято (все хорошо), или сообщение об ошибке, в случае если пакет "побился" по дороге. Сообщение об ошибке отправляется также в том случае, когда приходит что-то из того же потока данных, но не соответствующее ожиданиям. Это возможно, к примеру, когда в некоторый момент происходит timeout и некоторого пакета (или группы пакетов) внутри потока данных не приходит вообще: "Ты что мне шлешь 12-й? Я хочу 3-й!" Процесс это симметричный, подобно игре в волейбол. Данные, связанные с управлением, и собственно передаваемые данные можно объединять. Разумеется, может прийти и просто подтверждение, если посылать ничего не требуется.
  3. Все TCP-пакеты перенумерованы: в каждом соединении есть два счетчика seqn (sequence number) - по одному на каждое направление передачи. Счетчик инициализируется произвольно взятым числом при подключении и в дальнейшем увеличивается на объем передаваемых данных при каждой отсылке пакета. Такая схема позволяет, с одной стороны, определять последовательность пакетов и, с другой стороны, выяснять, что пропало и не сдублировался ли пакет.
  4. В каждом пакете передается также так называемая контрольная сумма, которая позволяет осуществлять контроль за целостностью данных.
  5. Что касается отслеживания качества канала, то в TCP используется довольно хитрая технология. Не вдаваясь в ее описание, заметим, что главная используемая идея такова: вначале обмен идет маленькими пакетами, а далее этот обмен происходит чем успешнее, тем быстрее (чем больше данных готова принять принимающая сторона, тем больше данных отправляет отправитель).

    Возможно, стоит перенумеровать общие задачи или же пункты этого списка, чтобы они соответствовали друг другу. -- DmitryChistikov 2008-07-06 16:06:54

Пример с tcpdump

Откроем окно эмулятора терминала, перейдем в режим суперпользователя и запустим программу tcpdump со следующими параметрами:

# tcpdump host 89.188.104.91
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes

Она не завершит свою работу, а будет отслеживать наши соединения с машиной, имеющей указанный IP-адрес. Откроем другое окошко и попробуем подключиться к ней:

$ netcat 89.188.104.91 80
(UNKNOWN) [89.188.104.91] 80 (www) : Connection refused

Как видно, в подключении нам отказали, а tcpdump тем временем выведет следующий текст:

19:23:22.076151 IP vaio.local.50291 > msk-f41.host-telecom.com.http: S 3412365941:3412365941(0) win 5840 <mss 1460,nop,nop,sackOK,nop,wscale 5>
19:23:22.077316 IP msk-f41.host-telecom.com.http > vaio.local.50291: R 0:0(0) ack 3412365942 win 0

Попробуем иначе:

$ netcat 89.188.104.91 22
SSH-2.0-OpenSSH_4.3p2 Debian-9

На этот раз все получилось лучше: мы видим приглашение для входа сервиса SSH. Не будем продолжать "разговор" с 89.188.104.91 и завершим соединение, нажав Ctrl+C. Что же за это время выведет tcpdump?

19:24:22.410905 IP vaio.local.44670 > msk-f41.host-telecom.com.ssh: S 85901152:85901152(0) win 5840 <mss 1460,nop,nop,sackOK,nop,wscale 5>
19:24:22.412067 IP msk-f41.host-telecom.com.ssh > vaio.local.44670: S 4086271543:4086271543(0) ack 85901153 win 5840 <mss 1460,nop,nop,sackOK,nop,wscale 6>
19:24:22.412142 IP vaio.local.44670 > msk-f41.host-telecom.com.ssh: . ack 1 win 183
19:24:22.418239 IP msk-f41.host-telecom.com.ssh > vaio.local.44670: P 1:32(31) ack 1 win 92
19:24:22.418304 IP vaio.local.44670 > msk-f41.host-telecom.com.ssh: . ack 32 win 183
19:24:32.215622 IP vaio.local.44670 > msk-f41.host-telecom.com.ssh: P 1:6(5) ack 32 win 183
19:24:32.216906 IP msk-f41.host-telecom.com.ssh > vaio.local.44670: . ack 6 win 92
19:24:37.520176 IP vaio.local.44670 > msk-f41.host-telecom.com.ssh: P 6:11(5) ack 32 win 183

Что это означает? Для того чтобы выполнить задачу разделения данных, пары "адрес отправителя - адрес получателя" недостаточно. Введем новое понятие - порт. Оно участвует в TCP и, кроме того, оказывается полезным на уровне интерпретации данных.

Придумано оно по аналогии с портами ввода-вывода обычного компьютера. Когда происходит установление соединения, клиент подключается не просто к IP-адресу сервера, но к паре "IP сервера - некоторый порт". Именно это мы и проделали с помощью программы telnet. На самом деле эта программа не предназначалась для таких действий, однако тестирование с ее помощью проводить удобно. Вначале мы подключились по IP-адресу 89.188.104.91 на 80-й порт, а потом по тому же адресу, но на 22-й порт. Во втором случае, как видно, нас ожидал OpenSSH-сервер на Debian.

Поскольку установление подключения двустороннее, то когда происходит ответное установление подключение от сервера к клиенту, оно происходит также по определенному порту. Его номер сообщается клиентом в самом начале соединения. В данном случае порт получателя - 22, а клиент передает номер 44670. Как видно, эти номера портов сохраняются и в дальнейшем. Если теперь мы будем устанавливать следующее подключение к тому же самому серверу, в качестве порта отправителя будет использовано другое число. Именно это число и будет отличать соответствующие потоки данных: IP-адрес получателя, порт получателя и IP-адрес отправителя не поменялись, а вот порт отправителя у них разный. Иными словами, каждое TCP-соединение использует свой собственный порт для идентификации отправителя. Таким образом, эта четверка (адрес и порт отправителя, адрес и порт получателя) и является идентификатором на транспортном уровне.

Опишем теперь, как используется понятие порта на уровне приложений. Согласно некоторой договоренности, разные типы приложений традиционно принимают соединения на разных портах. Поэтому данные, приходящие на разные порты, естественно интерпретировать по-разному. При подключении по 80 порту то, что мы передаем, будет интерпретироваться как HTTP-запросы, а по порту 22 нас ждет Secure Shell (SSH). Несложно понять, зачем это соглашение понадобилось. Дело в том, что никакого другого способа указать клиенту, по какому порту подключаться, не существует (кроме, разумеется, словесного описания: "У меня есть сервер - подключайся, пожалуйста, по порту 9090").

Существует организация IANA, в которой можно зарегистрировать свое приложение, сказав: пусть теперь теперь такой-то порт исключительно вот для этого используется. Список зарегистрированных портов можно посмотреть в файле /etc/services:

$ head -35 /etc/services
# /etc/services:
#
# Network services, Internet style
#
# The latest IANA port assignments can be gotten from
#       http://www.iana.org/assignments/port-numbers
# (last updated 8 November 2004)
#
# The port numbers are divided into three ranges: the Well Known Ports,
# the Registered Ports, and the Dynamic and/or Private Ports.
#
# The Well Known Ports are those from 0 through 1023.
# The Registered Ports are those from 1024 through 49151.
# The Dynamic and/or Private Ports are those from 49152 through 65535.
#
# Note that it is presently the policy of IANA to assign a single well-known
# port number for both TCP and UDP; hence, most entries here have two entries
# even if the protocol doesn't support UDP operations.
#
# Not all ports are included, only the more common ones.
#
# Each line describes one service, and is of the form:
#
# service-name  port/protocol  [aliases ...]   [# comment]

tcpmux          1/tcp                           # TCP port service multiplexer
tcpmux          1/udp                           # TCP port service multiplexer
rje             5/tcp                           # Remote Job Entry
rje             5/udp                           # Remote Job Entry
echo            7/tcp                           # Echo
echo            7/udp                           # Echo
discard         9/tcp           sink null       # Discard
discard         9/udp           sink null       # Discard
systat          11/tcp          users           # Active Users
systat          11/udp          users           # Active Users
[demo@vaio ~]$ grep ^ssh /etc/services
ssh             22/tcp                          # SSH Remote Login Protocol
ssh             22/udp                          # SSH Remote Login Protocol

Можно заметить, что временный порт для подключения всякий раз выбирается достаточно большим. На самом деле он должен быть больше 32000 (в некоторых случаях это нужно).

UDP

Скажем несколько слов и о протоколе UDP. На самом деле в TCP-пакетах и UDP-датаграммах нет почти ничего общего. Конечно, есть IP-адреса и порты получателя и отправителя. И тем не менее, это так. Если на прикладном уровне не организовано специальной поддержки подтверждений, UDP-датаграмма может уйти "в никуда". И, разумеется, есть класс задач, где это единственно возможная организация передачи данных. Классический пример - широковещание. Никому не придет в голову от всех клиентов, которые смотрят потоковое видео, получать подтверждения и сообщения об ошибках и обрабатывать их. Другая область применения - случай, когда сам факт обмена данных заключается в посылке очень маленьких пакетов, а работа на прикладном уровне подразумевает, что ответ будет отправлен. Типичный пример - DNS. Если мы ждем ответа от DNS-сервера, то его можно ждать и на прикладном уровне - с таким же успехом, как и на уровне установления TCP-соединения. Незачем из одного пакета делать четыре - разумнее экономить трафик, причем чем выше уровень DNS-сервера, тем это выгодней.

Есть и еще одно соображение, которое стоит рассмотреть. Предположим, у нас есть очень медленный (по времени отклика) канал. Оказывается, по такому каналу удобнее "гонять" UDP. Это хорошо видно из следующей схемы:

nfs_tcp_udp.png

На самом деле надо оценивать еще и случай возникновения ошибки: если мы узнаем о ней слишком поздно, то будет послано много лишних пакетов. В современном мире среды передачи данных и компьютеры работают все быстрее, а уровень надежности не повышается. Поэтому и в таких случаях становится все выгоднее использовать протокол с установлением соединения. Классический пример - сетевые файловые системы (NFS), переходящие с UDP на TCP.

Примечания

Есть предложение использовать там, где оно уместно, вместо "уровень протокола IP/TCP" "сетевой/транспортный уровень" соответственно. Ибо там не только IP и TCP. -- eSyr 2008-07-07 14:09:21


Сведения о ресурсах

Готовность (%)

Продолжительность (ак. ч.)

Подготовка (календ. ч.)

Полный текст (раб. д.)

Предварительные знания

Level

Maintainer

Start date

53

1

1

1

1

PavelSutyrin, DmitryChistikov, VsevolodKrishchenko

03.07.2008