Это передача займет больше отрезков времени, однако каждый отрезок короче, так как отправляются пакеты меньшего размера.
[(512 + 40 байт) x 8 бит/байт]/1544000 бит/сек = 2,9 миллисекунды на пересылку
Сейчас полное время составляет (18 x 2,9) = 52,2 миллисекунды. Каждый канал снова не занят в течение двух отрезков времени, что сейчас составляет 5,8 миллисекунды.
В этом примере мы игнорировали время, которое необходимо для того, чтобы вернулось подтверждение (ACK), также мы проигнорировали время, необходимое для установления и разрыва соединения, и не приняли во внимание то, что по каналам может двигаться и другой траффик. Тем не менее, расчеты в [Bellovin 1993] указывают, что отправка больших пакетов всегда эффективней. Однако, для различных сетей требуются более подробные исследования.
На рисунке 21.6 мы сразу видим три повторные передачи в моменты времени 10, 14 и 21. Во всех трех случаях только один сегмент передается повторно, потому что только одна точка оказалась ниже предыдущих.
Давайте рассмотрим первый из этих "скачков вниз" (в момент времени 10). Вывод команды tcpdump мы рассмотрим вместе с рисунком 21.7.
Основная проблема заключается в том, что маршрутизаторы это устройства, которые работают по принципу "сохранить и перенаправить". Они обычно получают входящий пакет целиком, проверяют на правильность IP заголовок, включая контрольную сумму IP, принимают решение о маршрутизации и затем начинают отправку исходящего пакета. На этом рисунке мы предположили идеальный случай, когда на операции, осуществляемые в маршрутизаторе, время не тратится, (горизонтальные пунктирные линии). Тем не менее, на отправку всех 8192 байт от R1 до R4 будет истрачено четыре отрезка времени. Время на каждую пересылку будет составлять
[(4096 + 40 байт) x 8 бит/байт]/1544000 бит/сек = 21,4 миллисекунды на пересылку
(IP и TCP заголовки составляют 40 байт.) Полное время, которое тратится на отправку данных, состоит из количества пакетов плюс количество пересылок минус один и составляет четыре отрезка времени или 85,6 миллисекунды. Каждый канал остается неиспользованным в течение двух отрезков времени или 42,8 миллисекунды.
На рисунке 24.4 показано что произойдет, если мы пошлем 16 пакетов размером 512 байт.
На рисунке 24.6 показано состояние обеих сетей через 30 миллисекунд. В обеих сетях первый бит данных достиг удаленного конца через 30 миллисекунд (латенсия), однако в случае сети T1 (емкость канала - 5790 байт), 994210 байт все еще находятся у отправителя, ожидая того, что они будут отправлены. Емкость гигабитной сети, составляет 3750000 байт, поэтому файл целиком занимает всего лишь около 25% канала. Последний бит файла достигает получателя через 8 миллисекунд после первого бита.
Полное время передачи файла по сети T1 составляет 5,211 секунды. Если мы увеличить ширину полосы пропускания, например, с использованием сети T3 (45000000 бит/сек), полное время уменьшится до 0,208 секунды. Увеличение ширины полосы в 29 раз уменьшает полное время в 25 раз.
В случае гигабитной сети полное время, необходимое на передачу файла, составляет 0,038 секунды: 30-миллисекундная латенсия плюс 8 миллисекунд на реальную передачу файла. Предположим, мы можем увеличить ширину полосы пропускания до 2 гигабит/сек, однако в этом случае мы уменьшим полное время передачи до всего лишь 0,034 секунды: та же самая 30-миллисекундная латенсия плюс 4 миллисекунды на передачу файла. Таким образом, удвоение полосы передачи, уменьшает полное время всего лишь на 10%. В случае гигабитных скоростей мы уже ограничены латенсией, а не шириной полосы.
Латенсия определяется скоростью света, и мы не можем ее уменьшить (если, конечно, Эйнштейн был прав). Влияние фиксированной латенсии становится еще более ощутимым (в отрицательную сторону), когда мы решаем установить или закрыть соединение. В случае гигабитных сетей на некоторые сетевые проблемы приходится взглянуть с другой точки зрения.
Если мы сравним это с той же самой командой, вводимой в Rlogin (рисунок 19.2), то увидим, что линейный режим Telnetа использует два сегмента (один с данными и один для подтверждения, что в целом составляет 86 байт, включая IP и TCP заголовки), тогда как Rlogin использует 15 сегментов (5 с введенными данными, 5 с отраженными эхом данными, 5 с подтверждениями, всего 611 байт). Нетрудно догадаться, в чью пользу это сравнение!
Что если на сервере мы запустим приложение, которое требует использовать режим единственного символа? (Например, редактор vi.) В этом случае будет происходить следующее.
Когда приложение стартует на сервере и изменяет режим своего псевдотерминала, Telnet сервер уведомляется о том, что требуется режим единственного символа. Сервер посылает WILL ECHO клиенту вместе с подопцией линейного режима, которая сообщает клиенту о том, что не нужно составлять полные строки, а вместо этого необходимо посылать один символ за один раз. Клиент отвечает DO ECHO и подтверждает подопцию линейного режима. Приложение запускается на сервере. Каждый символ, который мы печатаем, отправляется серверу сам по себе (естественно используется алгоритм Нагла), при этом сервер не требует отражения эхом. Когда приложение завершает работу и восстанавливает режим псевдотерминала, об этом уведомляется Telnet сервер. Сервер отправляет WONT ECHO клиенту вместе с подопцией линейного режима, которая сообщает клиенту о необходимости снова составлять полные строки. Клиент отвечает DONT ECHO и подтверждает подопцию линейного режима.
Отличие этого сценария от того, который реализуется при вводе пароля, показывает, что функция отражения эхом и символ за один раз, а также строка за один раз, это независимые характеристики. Когда мы вводим пароль, эхо должно быть отключено, при этом активизирован режим строка за один раз. Для полноэкранных приложений, таких как редакторы, эхо отключается и требуется режим символ за один раз.
На рисунке 26.15 показаны различные режимы, которые мы видели для Telnet и Rlogin.
Приложение | Клиент посылает | Эхо клиента? | Пример | |
символ за раз | строка за раз | |||
Rlogin | · | нет | ||
Telnet, символ за раз | · | нет | ||
Telnet, линейный режим | · | да | обычные команды | |
Telnet, линейный режим | · | нет | ввод пароля | |
Telnet, линейный режим | · | нет | редактор vi |
В строках 1, 2 и 3 отправляется строка "hello, world" от клиента к серверу и обратно. Первая проба "оставайся в живых" появляется через 2 часа (7200 секунд) в строке 4. Первое на что необходимо обратить внимание - это ARP запрос и ARP отклик перед отправкой TCP сегмента в строке 6. На пробу "оставайся в живых" в строке 6 приходит отклик с удаленного конца (строка 7). Тот же обмен пакетами происходит через 2 часа в строках 8-11.
Если бы мы могли видеть все поля в пробах "оставайся в живых" (строки 6 и 10), то обязательно обратили бы внимание на то, что поле номера последовательности на единицу меньше чем следующий отправляемый номер последовательности, который должен быть отправлен (в данном примере 13, тогда как должен быть 14). Так как в сегменте нет данных, tcpdump не печатает поле номера последовательности. (Программа tcpdump печатает номер последовательности для пустых сегментов, только в том случае если они содержат флаги SYN, FIN или RST.) Именно прием этих неверных номеров последовательности заставляет TCP модуль сервера отвечать подтверждениями на пробы "оставайся в живых". В отклике клиенту сообщается следующий номер последовательности, которую ожидает сервер (14).
Некоторые более старые реализации, основанные на 4.2BSD, не отвечают откликом на пробы "оставайся в живых", если сегмент не содержит данных. Некоторые системы могут быть сконфигурированы так, чтобы посылать в пробе один байт данных, чтобы получить на него отклик. Этот байт не принесет никакого вреда, потому что это не ожидаемый байт (байт, который получатель уже ранее получил и подтвердил), поэтому он отбрасывается получателем. Другие системы посылают сначала сегмент в стиле 4.3BSD (без данных) в качестве пробы, и если отклик не получен, переключаются на сегменты в стиле 4.2BSD.
Затем мы отсоединили кабель и ожидаем, что на следующую пробу (а именно через 2 часа) отклик не будет получен. Когда появляется следующая проба, мы никогда не увидим TCP сегменты в кабеле, потому что хост не отвечает на ARP запросы. Все же мы видим, что клиент отправляет 10 проб, с промежутком в 75 секунд, перед тем как прекратить попытки. Из нашего интерактивного скрипта мы видим, что код ошибки, возвращенный процессу клиента от TCP модуля, транслируется в сообщение "Connection timed out" (соединение закрыто по тайм-ауту).
Представим TCP соединение, использующее опцию масштабирования окна, с максимально возможным окном, 1 гигабайт (230). (Самое большое окно даже меньше чем это, 65535 x 214, а не 216 x 214, однако это не должно влиять на наши рассуждения.) Также представьте, что используется опция временной марки, и что значение временной марки, назначенное отправителем, увеличивается на единицу для каждого отправляемого окна. (Это достаточно устаревший способ. Обычно значение временной марки увеличивается значительно быстрее.) На рисунке 24.8 показан поток данных между двумя хостами, возникающий при передаче 6 гигабайт. Чтобы избежать большого количества десятизначных цифр, мы используем запись G, что означает умножение на 1.073.741.824. Мы также используем форму записи из tcpdump, где J:K означает байты от J до K-1, включая байт K-1.
Время | Отправленные байты | Отправленный номер последова-тельности | Отправлен-ная временная марка | Получение |
A | 0G:1G | 0G:1G | 1 | принято нормально |
B | 1G:2G | 1G:2G | 2 | принято нормально, но один сегмент потерян и передан повторно |
C | 2G:3G | 2G:3G | 3 | принято нормально |
D | 3G:4G | 3G:4G | 4 | принято нормально |
E | 4G:5G | 0G:1G | 5 | принято нормально |
F | 5G:6G | 1G:2G | 6 | принято нормально, но повторно переданный сегмент появился в сети повторно |
32-битный номер последовательности перешел через ноль между моментами времени D и E. Мы предположили, что один сегмент потерялся в момент времени B и был передан повторно. Также мы предположили, что потерянный сегмент повторно появился в сети в момент времени F.
Разница во времени между моментами, когда сегмент был потерян и появился повторно, меньше чем MSL; иначе сегмент должен быть отброшен каким-либо маршрутизатором по истечению его TTL. Как мы упоминали ранее, такая проблема возникает только на высокоскоростных соединениях, где старые сегменты могут повторно появиться и содержать номер последовательности, который в настоящее время передается.
Также мы можем видеть из рисунка 24.8, что использование временной марки решает эту проблему. Получатель рассматривает временную марку как 32-битное расширение к номеру последовательности. Так как потерянный сегмент, повторно появившийся в момент времени F, имел временную марку равную 2, что меньше чем самая последняя приемлемая временная марка (5 или 6), он отбрасывается алгоритмом PAWS.
Алгоритм PAWS не требует какой-либо формы синхронизации времени между отправителем и получателем. Все что необходимо получателю это то, чтобы значение временной марки монотонно увеличивалось и увеличивалось по крайней мере на единицу для каждого нового окна.
Давайте убедимся в том, что при передаче текстовых файлов по умолчанию используется формат NVT ASCII. В этот раз мы не будем использовать флаг -d, поэтому мы не увидим команды клиента, однако клиент все еще печатает отклики от сервера:
sun % ftp bsdi
Connected to bsdi.
220 bsdi FTP server (Version 5.60) ready.
Name (bsdi:rstevens): вводим RETURN
331 Password required for rstevens.
Password: вводим пароль
230 User rstevens logged in.
ftp> get hello.c получаем файл
200 PORT command successful.
150 Opening ASCII mode data connection for hello.c (38 bytes).
226 Transfer complete. сервер сообщает, что файл содержит 38 байт
local: hello.c remote: hello.c вывод от клиента
42 bytes received in 0.0037 seconds (11 Kbytes/s) 42 байта пришло по соединению данных
ftp> quit
221 Goodbye.
sun % ls -l hello.c
-rw-rw-r-- 1 rstevens 38 Jul 18 08:48 hello.c однако файл содержит 38 байт
sun % wc -l hello.c подсчет строк в файле
4 hello.c
Сорок два байта было передано по соединению данных, потому что файл содержит четыре строки. Каждый Unix символ новой строки (\n) конвертируется в 2-байтную последовательность NVT ASCII конец строки (\r\n) сервером для передачи, а затем конвертируется обратно клиентом при записи на диск.
Более новые клиенты стараются определить, используется ли подобная система на сервере, и если да, передают файлы в двоичном виде (image тип файла) вместо ASCII. Это помогает в двух случаях. Отправитель и получатель не должны просматривать каждый байт (это большая экономия времени и ресурсов). Передается меньше байт, если операционная система хоста использует меньше байтов в качестве символа конца строки, нежели 2-байтовая последовательность NVT ASCII (это меньшая часть экономии).
Мы можем увидеть подобную оптимизацию с использованием BSD/386 клиента и сервера. Включен отладочный режим, что позволяет увидеть команды FTP клиента:
bsdi % ftp -d slip указываем опцию -d, чтобы видеть команды клиента
Connected to slip.
220 slip FTP server (Version 5.60) ready.
Name (slip:rstevens): вводим RETURN
---> USER rstevens
331 Password required for rstevens.
Password: вводим пароль
---> PASS XXXX
230 User rstevens logged in.
---> SYST это клиент посылает автоматически
215 UNIX Type: L8 Version: BSD-199103 отклик сервера
Remote system type is UNIX. вывод информации клиентом
Using binary mode to transfer files. вывод информации клиентом
ftp> get hello.c получить файл
---> TYPE I это клиент посылает автоматически
200 Type set to I.
---> PORT 140,252,13,66,4,84 номер порта = 4 х 256 + 84 = 1108
200 PORT command successful.
---> RETR hello.c
150 Opening BINARY mode data connection for hello.c (38 bytes).
226 Transfer complete.
38 bytes received in 0.035 seconds (1.1 Kbytes/s) в этот раз только 38 байт
ftp> quit
---> QUIT
221 Goodbye.
После того как мы правильно ввели имя и пароль серверу, FTP клиент автоматически посылает команду SYST, в ответ на которую сервер сообщает свой тип системы. Если отклик начинается со строки "215 UNIX Type: L8", и если клиент работает под управлением Unix системы с 8-битными байтами, для передачи всех файлов используется двоичный (image) режим, если его не сменит пользователь.
Когда мы забираем файл hello.c, клиент автоматически посылает команду TYPE I, чтобы установить тип файла в двоичный. На этот раз по соединению данных было передано 38 байт.
Требования к хостам Host Requirements RFC говорят, что FTP сервер должен поддерживать команду SYST (это было необязательным условием в RFC 959). Из систем описанных в тексте (см. внутреннюю сторону обложки) эту команду поддерживают BSD/386 и AIX 3.2.2. SunOS 4.1.3 и Solaris 2.x выдают на эту команду отклик 500 (команда неизвестна). SVR4 ведет себя совсем по-дикому: отвечает 500 и закрывает управляющее соединение!
Мы будем использовать этот формат при описании всех переменных MIB в этой главе. Колонка, помеченная как "R/W", пуста, если переменная предназначена только для чтения или содержит точку (·), если переменную можно читать и записывать. Мы всегда будем включать эту колонку, даже если все переменные в группе только для чтения (как мы видели в группе udp), чтобы напомнить, что ни одна из переменных не может быть установлена менеджером. В случае если тип данных - INTEGER (целый) с ограничением, мы будем указывать верхний и нижний пределы, как сделано для номера порта UDP на следующем рисунке.
На рисунке 25.9 описываются две переменные в udpTable.
Таблица слушающего процесса (listener) UDP, index = <udpLocalAddress>.<udpLocalPort> | |||
Имя | Тип данных | R/W | Описание |
udpLocalAddress | IpAddress | Локальный IP адрес слушающего процесса. 0.0.0.0 указывает, что слушающий процесс воспринимает датаграммы с любого интерфейса. | |
udpLocalPort | [0..65535] | Локальный номер порта слушающего процесса. |
Мы можем запросить хост sun на предмет некоторых из этих переменных для всех его интерфейсов. Мы ожидаем увидеть три интерфейса (см. главу 3, раздел "Команда ifconfig"), если активизирован SLIP интерфейс:
sun % snmpi -a sun
snmpi> next ifTable во-первых, мы видим, чему равен индекс первого интерфейса
ifIndex.1=1
snmpi> get ifDescr.1 ifType.1 ifMtu.1 ifSpeed.1 ifPhysAddress.1
ifDescr.1="le0"
ifType.1=ethernet-csmacd (6)
ifMtu.1=1500
ifSpeed.1=10000000
ifPhysAddress.1=0x08:00:20:03:f6:42
snmpi> next ifDescr.1 ifType.1 ifMtu.1 ifSpeed.1 ifPhysAddress.1
ifDescr.2="sl0"
ifType.2=propPointToPointSerial (22)
ifMtu.2=552
ifSpeed.2=0
ifPhysAddress.2=0x00:00:00:00:00:00
snmpi> next ifDescr.2 ifType.2 ifMtu.2 ifSpeed.2 ifPhysAddress.2
ifDescr.3="lo0"
ifType.3=softwareLoopback (24)
ifMtu.3=1536
ifSpeed.3=0
ifPhysAddress.3=0x00:00:00:00:00:00
Во-первых, мы получили пять переменных для первого интерфейса, используя оператор get, а затем получили те же пять переменных для второго интерфейса, используя оператор get-next. Последняя команда получает эти же пять переменных для третьего интерфейса и опять с использованием команды get-next.
Тип интерфейса для SLIP канала сообщается как последовательное соединение точка-точка, а не SLIP. Также, не сообщается скорость SLIP канала.
Очень важно понять взаимосвязь между оператором get-next и порядком расположения информации в таблице, а именно колонка-строка. Когда мы задаем next ifDescr.1, возвращается следующая строка таблицы для этой переменной, а не следующую переменную в этой же строке. Однако если бы таблицы хранились в порядке строка-колонка, мы могли подобным образом перейти к следующему появлению заданной переменной.
Каждый раз, когда мы описываем переменные в SNMP таблице, первая строка рисунка содержит значение "index", используемое для обращения к каждой строке таблицы. Мы покажем некоторые примеры того, как это делается, в следующем разделе.
Когда TCP отрабатывает тайм-ауты и осуществляет повторные передачи, он не должен повторно передавать идентичные сегменты (если исходно было 10 пакетов по одному байту, то при повторной передаче можно передать 1 пакет размером десять байт). Вместо этого, TCP разрешено осуществлять пересборку пакетов (repacketization), отправляя сегменты большего размера, что может увеличить производительность. (В действительности, сегменты большего размера не могут превосходить по размеру MSS, объявленный удаленным получателем.) Протокол TCP может себе это позволить, потому что он идентифицирует данные, которые были отправлены и подтверждены, по номерам байтов, а не по номерам сегментов.
Мы можем легко посмотреть, как это происходит. Воспользуемся программой sock, чтобы подсоединиться к discard серверу, и напечатаем одну строку. Затем мы отсоединяем Ethernet кабель и вводим вторую строку. Пока эта вторая строка будет повторно передаваться, мы вводим третью строку. При этом ожидается, что следующая повторная передача будет содержать обе и вторую, и третью строки.
bsdi % sock svr4 discard
hello there первая строка отправлена нормально
отсоединяем Ethernet кабель
line number 2 эта строка будет повторно передаваться
and 3 вводим эту строку перед тем, как вторая отправлена нормально
подсоединяем обратно Ethernet кабель
На рисунке 21.13 показан вывод команды tcpdump. (Мы удалили установление соединения, прерывание соединения и все объявления окна.)
1 0.0 bsdi.1032 > svr4.discard: P 1:13(12) ack 1
2 0.140489 ( 0.1405) svr4.discard > bsdi.1032: . ack 13
здесь отсоединен Ethernet кабель
3 26.407696 (26.2672) bsdi.1032 > svr4.discard: P 13:27(14) ack 1
4 27.639390 ( 1.2317) bsdi.1032 > svr4.discard: P 13:27(14) ack 1
5 30.639453 ( 3.0001) bsdi.1032 > svr4.discard: P 13:27(14) ack 1
здесь напечатана третья строка
6 36.639653 ( 6.0002) bsdi.1032 > svr4.discard: P 13:33(20) ack 1
7 48.640131 (12.0005) bsdi.1032 > svr4.discard: P 13:33(20) ack 1
8 72.640768 (24.0006) bsdi.1032 > svr4.discard: P 13:33(20) ack 1
здесь Ethernet кабель подсоединен обратно
9 72.719091 ( 0.0783) svr4.discard > bsdi.1032: . ack 33
В строках 1 и 2 показана первая строка ("hello there"), которая отправлена и подтверждена. Затем мы отсоединяем Ethernet кабель и печатаем "line number 2" (14 байт, включая символ новой строки). Эти байты передаются в строке 3, а затем повторно передаются в строках 4 и 5.
Перед тем как осуществляется повторная передача в строке 6, мы печатаем "and 3" (6 байт, включая символ новой строки) и видим, что повторная передача на этот раз содержит 20 байт: обе строки, которые мы напечатали. Подтверждение, которое прибывает в строке 9, подтверждает все 20 байт.
Менеджер отправляет эти три запроса на UDP порт 161. Агент отправляет ловушки (trap) на UDP порт 162. Так как используются два разных порта, одна система может выступать в роли менеджера и агента одновременно. (См. упражнение 1 в конце главы.)
На рисунке 25.2 показан формат пяти SNMP сообщений, инкапсулированных в UDP датаграмму.
Более новые реализации TCP хранят большинство из показателей, которые мы описали в этой главе в записях таблицы маршрутизации. Предположим, что TCP соединение закрыто, при этом по соединению было отправлено достаточное количество данных, чтобы получить статистику, и запись в таблице маршрутизации для определенного пункта назначения не является маршрутом по умолчанию. При выполнении этих условий в записи таблицы маршрутизации сохраняется следующая информация (эта информация будет использована при следующих обращениях к этой записи): хэшированный RTT, хэшированное среднее отклонение и порог медленного старта. Понятие "достаточное количество данных" - означает 16 окон данных. При этом можно получить 16 примеров RTT, что позволяют фильтру хэшированного RTT получить значение с отклонением в пределах 5% от реального.
Помимо этого, администратор может воспользоваться командой route(8) , чтобы установить показатели для заданного маршрута: три показателя, упомянутых в предыдущем параграфе, а также MTU, емкость исходящего канала в зависимости от полосы пропускания (раздел "Пропускная способность для неинтерактивных данных" главы 20) и емкость входящего канала в зависимости от полосы пропускания.
Когда устанавливается новое TCP соединение, либо активное, либо пассивное, и пункт таблицы маршрутизации, который используется для этого соединения, имеет значения для этих показателей, соответствующие переменные инициализируются значениями показателей.
Существуют четыре режима, в которых функционирует большинство Telnet клиентов и серверов.
Полудуплексный.
Это режим по умолчанию, который, однако, редко используется в настоящее время. NVT по умолчанию это полудуплексное устройство, которое требует исполнения команды GO AHEAD (GA) от сервера, перед тем как будет принят ввод от пользователя. Ввод пользователя отображается локальным эхом от NVT клавиатуры на NVT принтер, таким образом, от клиента к серверу посылаются только полные строки.
Таким образом, обеспечивается минимальная поддержка терминала, однако подобным образом невозможно обеспечить полнодуплексную связь с хостами, которые поддерживают полнодуплексную форму общения, что является нормой на сегодняшний день. RFC 857 [Postel and Reynolds 1983c] определяет опцию ECHO (эхо), а RFC 858 [Postel and Reynolds 1983d] определяет опцию SUPPRESS GO AHEAD (запрещение команды go ahead). Комбинация этих двух опций предоставляет поддержку для следующего режима, символ за один раз, с удаленным эхом.
Символ за один раз.Именно таким образом работает Rlogin. Каждый вводимый символ отправляется серверу отдельно от других. Сервер отражает эхом большинство символов, если только у приложения на сервере не отключено отражение эхом.
Проблемы, связанные с этим режимом, в основном связаны с задержками, вызванными отражением эхом по медленным сетям, и с большим объемом сетевого траффика. Однако, мы увидим, что на сегодняшний день это наиболее распространенный режим и являющийся к тому же режимом по умолчанию.
Для того чтобы сервер мог войти в этот режим, у него должна быть включена опция SUPPRESS GO AHEAD. Обсуждение этой опции осуществляется следующим образом: клиент посылает DO SUPPRESS GO AHEAD (требуя от сервера, чтобы тот включил опцию), или сервер посылает WILL SUPPRESS GO AHEAD клиенту (спрашивая о возможности включить эту опцию для самого себя). Затем сервер осуществляет WILL ECHO, спрашивая о возможности включить отражение эхом.
Строка за один раз.Часто это называется "kludge line mode", потому что его реализация приходит от чтения между строк в RFC 858. Этот RFC декларирует, что должны присутствовать обе опции ECHO и SUPPRESS GO AHEAD, чтобы обеспечить ввод символа за один раз с удаленным эхом. Таким образом, если какая-либо из этих опций не включена, Telnet находится в режиме строка за один раз. В следующем разделе мы увидим пример того, как происходит обсуждение этого режима, и как он может быть отключен, когда программе сервера необходимо читать каждое нажатие клавиши. (То есть когда программа должна читать каждый символ, введенный пользователем, а не целую строку символов.)
Линейный режим (linemode).В данном случае этот термин означает реальную опцию linemode, определенную в RFC 1184 [Borman 1990]. Эта опция обсуждается клиентом и сервером и корректирует все недостатки в режиме строка за один раз. Новые реализации поддерживают эту опцию.
На рисунке 26.11 показаны режимы функционирования по умолчанию между различными Telnet клиентами и серверами. Выражение "char" означает символ за один раз, "kludge" означает строка за один раз и "linemode" означает реальный линейный режим RFC 1184.
Клиент | Сервер | |||||
SunOS 4.1.3 | Solaris 2.2 | SVR4 | AIX 3.2.2 | BSD/386 | 4.4BSD | |
SunOS 4.1.3 | char | char | char | char | kludge | kludge |
Solaris 2.2 | char | char | char | char | kludge | kludge |
SVR4 | char | char | char | char | kludge | kludge |
AIX 3.2.2 | char | char | char | char | kludge | kludge |
BSD/386 | char | char | char | char | linemode | linemode |
4.4BSD | char | char | char | char | linemode | linemode |
В строке 1 vangogh отправляет SYN на порт 25 на первый IP адрес хоста sun: 140.252.1.29. В строке 2 мы видим отказ. SMTP клиент на vangogh пробует следующий IP адрес sun: 140.252.13.33 (строка 3), что также вызывает возврат RST (строка 4).
SMTP клиент не старается изменить свое поведение в результате ошибки, полученной на активное открытие, которое он осуществлял в строке 1, именно поэтому он старается обратиться к другому IP адресу в строке 2. Если ошибка была подобна "хост недоступен" (host unreachable) для первой попытки, возможно, что вторая попытка сработает.
Если причина, по которой активное открытие SMTP клиента не сработало, заключается в том, что хост выключен, мы увидим, что клиент будет повторно выдавать SYN на IP адрес 140.252.1.29 в течение 75 секунд (примерно так же, как на рисунке 18.6), затем отправит еще три SYN на IP адрес 140.252.13.33 в течение других 75 секунд. После 150 секунд клиент перейдет к следующей MX записи с более высоким значением предпочтительности.
В строке 1 MTA запрашивает свой DNS сервер о MX записи для mlfarm.com. Знак плюс, следующий за 2, означает, что установлен флаг "необходима рекурсия". Отклик в строке 2 имеет установленным бит полномочности (звездочка, следующая за 2) и содержит 2 записи ресурса (RR) ответа (два имени MX хостов), 0 записей ресурса RR прав доступа и две дополнительные RR (IP адреса двух хостов).
В строках 3-5 устанавливается TCP соединение с SMTP сервером на хосте mercury.hsi.com. Первоначальный отклик сервера (220) показан в строке 6.
Каким-либо образом хост mercury.hsi.com должен доставить это почтовое сообщение в пункт назначения - mlfarm.com. Протокол UUCP является распространенным способом обмена почтой с MX узлами для систем, которые не подключены к Internet.
В этом примере MTA запрашивает MX запись, получает ее и посылает почту. К сожалению, взаимодействие между MTA и DNS может отличаться в зависимости от реализации. RFC 974 указывает, что MTA должен сначала запросить MX записи, и если они не найдены, попробовать доставить почту на хост назначения (то есть запросить DNS на предмет записи A для хоста, его IP адрес). MTA должен также поинтересоваться записями CNAME в DNS (канонические имена).
Например, если мы пошлем почту хосту rstevens@mailhost.tuc.noao.edu с BSD/386 хоста, MTA (Sendmail) проделает следующее.
Sendmail запрашивает DNS, существуют ли записи CNAME для хоста mailhost.tuc.noao.edu. Мы видим, что записи CNAME существуют:
sun % host -t cname mailhost.tuc.noao.edu
mailhost.tuc.noao.edu CNAME noao.edu
sun % host -t mx noao.edu
noao.edu MX noao.edu
Запрос CNAME не осуществляется для данных, возвращенных в MX записи (noao.edu). Данные в MX записи не могут быть псевдонимами - они должны быть именем хоста, который имеет запись A.
Версия Sendmail, распространяемая с SunOS.4.1.3, обращается к DNS только с запросами о существовании MX записей и сразу прекращает все попытки, если MX запись не найдена.
Затемненная часть отправляющего буфера это неиспользуемая часть буфера размером в 4096 байт. На рисунке 26.7 приведена временная диаграмма для этого примера.
В сегментах 1-3 сервер отправляет клиенту сегменты полного размера. Подтверждение (ACK) в сегменте 4 всего лишь объявляет окно равное 1024, потому что вывод остановлен: так как клиент не может писать на терминал, он не может читать из сети. Сегмент 5 не полного размера, а ACK в сегменте 6 объявляет только оставшееся пространство в приемном буфере, размер премного буфера составляет 4096 байт. Клиент должен объявить окно размером 349 байт, потому что если он объявит окно равное 0 (как мы ожидаем, основываясь на алгоритме избежания "глупого окна", глава 22, раздел "Синдром "глупого" окна"), это приведет к тому, что правая граница окна сдвинется влево, что не должно произойти (глава 20, раздел "Изменение размера окна"). Так как сервер не может отправить буфер полного размера, когда он принимает сегмент 6, он прибегает к алгоритму предотвращения "глупого окна", не посылая ничего, при этом устанавливается устойчивый таймер (5 секунд). Когда таймер истекает, отправляется 349 байт (сегмент 7), и так как вывод клиента все еще остановлен, подтверждение в сегменте 8 объявляет окно равное 0.
В этот момент мы вводим символ прерывания, который передается в сегменте 9. Все еще объявлено окно равное 0. Когда Rlogin сервер получает символ прерывания, он передает его приложению (cat), и приложение прекращает свою работу. Так как приложение было остановлено вводом символа прерывания с терминала, его вывод сбрасывается и это передается серверу Rlogin. Это заставляет сервер посылать команду "очистить вывод" клиенту с использованием режима срочности TCP. Мы видим это в сегменте 10. Обратите внимание, что командный байт 0x02 имеет номер последовательности 30146 (указатель срочности минус один). Перед командным байтом находится 3419 байт (номера последовательности 26727:30145), они буферизированы сервером и сервер собирается их отправить.
Сегмент 10, с уведомлением срочности, содержит следующий байт данных, который передается от сервера клиенту (номер последовательности 26727). Он не содержит командный байт "очистить вывод". Сервер может послать этот единственный байт в сегменте 10 (глава 22, раздел "Пример"), так как отправитель всегда может проверить, закрыто ли окно, отправив 1 байт данных. TCP модуль клиента немедленно отвечает сегментом 11 с нулевым окном, однако прием уведомления о срочности в сегменте 10 заставляет TCP модуль клиента уведомить Rlogin клиента, что удаленный конец соединения вошел в режим срочности.
Протокол FTP предоставляет различные способы управления передачей и хранения файлов. Необходимо сделать выбор по четырем пунктам. Тип файла.
(а)
ASCII файлы.(b)
EBCDIC файлы.(c)
Двоичные или бинарные файлы. (Image.)(d)
Локальный тип файлов.(a)
Nonprint. (По умолчанию)(b)
Telnet format control.(c)
Fortran carriage control.(a)
Структура файла.(b)
Структура записи.(c)
Структура страницы.(a)
Режим потока.(b)
Режим блоков.(c)
Сжатый режим.Если посчитать количество комбинаций из приведенных вариантов, то получится 72 способа передачи и хранения файла. К счастью, можно игнорировать многие из этих опций, потому что они не поддерживаются в большинстве реализаций.
Самые распространенные Unix реализации FTP клиента и сервера предоставляют следующий выбор: Тип: ASCII или двоичный. Управление форматом: только nonprint. Структура: только файловая структура. Режим передачи: только потоковый режим.
Это ограничивает нас одним из двух режимов: ASCII или двоичный.
Подобная реализация отвечает минимальным требованиям к хостам Host Requirements RFC. (RFC также требует обеспечить поддержку для структуры записи, однако только если операционная система поддерживает это, а Unix, как правило, не поддерживает.)
Большинство не-Unix реализаций предоставляет FTP возможности, которые позволяют обрабатывать их собственные форматы файлов. Требование к хостам Host Requirements RFC говорит: "Протокол FTP включает множество характеристик, некоторые из которых распространены не очень широко. Однако, для каждой характеристики в FTP существует по меньшей мере одна реализация."
Сейчас мы посмотрим, как FTP клиент прекращает передачу файла от сервера. Прекратить передачу файла от клиента к серверу достаточно просто - клиент прекращает посылку данных по соединению данных и посылает ABOR на сервер по управляющему соединению. Прекращение приема, однако, более сложная задача, потому что клиент хочет, чтобы сервер немедленно прекратил передачу данных. Ранее мы упоминали, что для этого используется сигнал синхронизации Telnet.
Мы начали прием, после чего ввели символ прерывания. Ниже приведена интерактивная сессия, процесс идентификации пользователя удален:
ftp> get a.out получаем большой файл
---> TYPE I клиент и сервер - Unix системы с 8-битными байтами
200 Type set to I.
---> PORT 140,252,13,66,4,99
200 PORT command successful.
---> RETR a.out
150 Opening BINARY mode data connection for a.out (28672 bytes).
^? вводим символ прерывания
receive aborted вывод от клиента
waiting for remote to finish abort вывод от клиента
426 Transfer aborted. Data connection closed.
226 Abort successful
1536 bytes received in 1.7 seconds (0.89 Kbytes/s)
После того как введен символ прерывания, клиент немедленно сообщает, что он инициализировал прерывание передачи файла и ожидает, когда сервер его завершит. Сервер посылает два отклика: 426 и 226. Оба отклика посылаются Unix сервером, когда он принимает срочные данные от клиента с командой ABOR.
На рисунках 27.9 и 27.10 показаны временные диаграммы для этой сессии. Мы объединили вместе управляющее соединение (сплошные линии) и соединение данных (прерывистые линии), чтобы показать взаимосвязь между ними.
Проблема, напоминающая управление потоком данных, возникает, когда пользователь вводит символ прерывания (обычно DELETE или Control-C), чтобы прекратить процесс, запущенный на сервере. Сценарий подобен тому, который мы показали на рисунке 26.3. В этом случае также одно полное окно данных в канале между сервером и клиентом будет передано клиенту, до тех пор пока символ прерывания проделает свой путь по соединению в другом направлении. Мы хотим, чтобы символ прерывания остановил или прервал вывод данных на экран так быстро, как это возможно.
Достаточно редко поток данных от клиента к серверу может быть остановлен контролем потока данных. В этом направлении передаются только вводимые с клавитуры символы. Поэтому нет необходимости отправлять эти специальные символы (Control-S или прерывание) от клиента к серверу с использованием режима срочности TCP.
Первые 12 сегментов на рисунке 27.9 как раз такие, как мы ожидали. Команды и отклики по управляющему соединению настраивают системы на передачу файла, открывается соединение данных и от сервера к клиенту посылается первый сегмент данных.
Сегмент 13 на рисунке 27.10 - это квитанция на шестой сегмент данных от сервера по соединению данных. Затем следует сегмент 14, который сгенерирован нашим вводом символа прерывания. Для того чтобы прервать передачу, клиент посылает десять байт:
<IAC, IP, IAC, DM, A, B, O, R, \r, \n>
Мы видим два сегмента (14 и 15), это связано с проблемой определения положения указателя срочности TCP (подробно описанной в разделе "Режим срочности (Urgent Mode)" главы 20). (На рисунке 26.17 мы видели, как Telnet решает эту проблему.) Требование к хостам Host Requirements RFC говорит, что указатель срочности должен указывать на последний байт срочных данных, тогда как большинство Berkeley реализаций указывают на один байт позади последнего байта срочных данных. FTP клиент специально пишет первые 3 байта как срочные данные, зная, что указатель срочности будет (некорректно) указывать на следующий байт, который будет записан (метка данных, DM, с номером последовательности 54). Эта первая запись из 3 байт срочных данных посылается немедленно, вместе с указателем срочности, за ними следуют следующие 7 байт. (BSD FTP сервер не имеет проблемы с интерпретацией указателя срочности, который используется клиентом. Когда сервер принимает срочные данные по управляющему соединению, он читает следующую FTP команду, в поиске ABOR или STAT, игнорируя любые вложенные команды Telnet.)
Несмотря на то, что сервер сообщил о прекращении передачи (сегмент 18, по управляющему соединению), клиент получил еще 14 сегментов данных (номера последовательности 1537 - 5120) по соединению данных. Эти сегменты, скорее всего, были поставлены в очередь в драйвере сетевого устройства на сервере, когда был принят сигнал о прекращении передачи. Однако клиент печатает "1536 байт принято", а это означает, что он проигнорировал эти сегменты данных (сегменты 17 и позже), которые были приняты после отправки прерывания передачи (сегменты 14 и 15).
В случае Telnet, когда пользователь вводит символ прерывания (рисунок 26.17), Unix клиент по умолчанию не посылает команду прерывания процесса в виде срочных данных. В этом нет ничего страшного, потому что существует очень маленькая вероятность того, что поток данных от клиента к серверу остановлен управлением потока данных. В случае FTP клиент также посылает команду прерывания процесса по управляющему соединению, и так как используется два соединения, существует очень небольшая вероятность того, что управляющее соединение будет остановлено управлением потока данных. Почему FTP посылает команду прерывания процесса в виде срочных данных, тогда как Telnet не делает этого? Дело в том, что FTP использует два соединения, тогда как Telnet использует одно, а для некоторых операционных систем довольно сложно обработать информацию приходящую по двум соединениям одновременно. FTP подразумевает, что эти операционные системы по крайней мере смогут понять, что по управляющему соединению срочные прибыли данные, и что в свою очередь позволит серверу переключиться от обработки соединения данных на обработку управляющего соединения.
Колонка | Идентификатор объекта (в лексикографическом порядке) | Сокращенное имя | Значение |
1 | 1.3.6.1.2.1.7.5.1.1.0.0.0.0.67 1.3.6.1.2.1.7.5.1.1.0.0.0.0.161 1.3.6.1.2.1.7.5.1.1.0.0.0.0.520 |
udpLocalAddress.0.0.0.0.67 udpLocalAddress.0.0.0.0.161 udpLocalAddress.0.0.0.0.520 |
0.0.0.0 0.0.0.0 0.0.0.0 |
2 | 1.3.6.1.2.1.7.5.1.2.0.0.0.0.67 1.3.6.1.2.1.7.5.1.2.0.0.0.0.161 1.3.6.1.2.1.7.5.1.2.0.0.0.0.520 |
udpLocalPort.0.0.0.0.67 udpLocalPort.0.0.0.0.161 udpLocalPort.0.0.0.0.520 |
67 161 520 |
Этот раздел является кратким описанием MIME. Для получения более подробной информации и примеров MIME можно обратиться к RFC 1521 и [Rose 1993].
В строке 1 vangogh объявляет окно размером 65535 и указывает опцию масштабирования окна со сдвиговым счетчиком равным 1. Это объявленное окно имеет максимально возможное значение, однако оно меньше чем размер приемного буфера (128000), потому что поле окна в сегменте SYN никогда не масштабируется.
Коэффициент масштабирования равный 1 означает, что vangogh хочет объявить окна размером до 131070 (65535 x 21). Это соотносимо с размером приемного буфера (128000). Так как bsdi не отправлял опцию масштабирования окна в своем SYN (строка 2), эта опция не используется. Обратите внимание на то, что vangogh и дальше продолжает использовать максимально возможный размер окна (65535) для соединения.
Для второго соединения vangogh устанавливает сдвиговый счетчик в значение 2, а это означает, что он собирается отправить объявления окна размером до 262140 (65535 x 22), то есть больше чем размер приемного буфера (220000).
Мы установили соединение и послали 9 байт данных от клиента к серверу (строки 1-3). Два часа спустя клиент отправил первую пробу "оставайся в живых", отклик от сервера содержит сброс. Приложение клиента печатает сообщение об ошибке "Connection reset by peer" (соединение сброшено удаленным концом).
Мы начинаем этот пример так же, как и предыдущий: в строках 1-3 убеждаемся, что соединение функционирует. На первую пробу "оставайся в живых", отправляемую через 2 часа, успешно получен отклик (строки 4 и 5), однако перед тем, как будет отправлена следующая, еще через 2 часа, мы выключили SLIP соединение между маршрутизаторами sun и netb. (Обратитесь к топологии, приведенной на внутренней стороне обложки.)
На пробу "оставайся в живых" в строке 6 генерируется ICMP ошибка о недоступности сети от маршрутизатора sun. Как мы описали в разделе "ICMP ошибки" главы 21, это всего лишь "мягкая" ошибка для принимающего TCP на хосте slip. Он фиксирует, что была принята ICMP ошибка, однако получение ошибки не разрывает соединение. Отправляются еще 9 проб "оставайся в живых", с интервалом в 75 секунд, перед тем, как хост прекращает свои попытки. Ошибка, возвращаемая приложению, генерирует другое сообщение: "No route to host" (нет маршрута к хосту). На рисунке 6.12 мы видели, что это соответствует ICMP ошибке о недоступности сети.
Давайте теперь рассмотрим передачу сегментов данных. На рисунке 21.6 показана зависимость стартового номера последовательности сегмента от времени, когда сегмент был отправлен. Это позволит нам более наглядно представить процесс передачи данных. Обычно точки, соответствующие данным, должны двигаться вверх вправо, при этом наклон соответствует скорости передачи. Повторные передачи показаны отклонением графика вниз вправо.
В начале раздела "Пример RTT" этой главы мы сказали, что полное время передачи составляло примерно 45 секунд, однако на этом рисунке мы показали только 35 секунд. (Потому что, именно 35 секунд потребовалось для передачи только сегментов данных.) Первый сегмент данных не передавался в течении 6,3 секунды после отправки первого SYN, потому что первый SYN был потерян при передаче и передан повторно (рисунок 21.5). После того как последний сегмент данных и FIN были отправлены (момент времени 34,1 на рисунке 21.6), потребовалось еще примерно 4,0 секунды на то, чтобы принять последние 14 ACK от получателя перед тем, как от получателя был получен FIN.
Просматривая соединение с использованием tcpdump и отладочной опции сокетов (которую мы описали в разделе "Пример RTT"), мы можем увидеть значения cwnd и ssthresh при передаче каждого сегмента. Если максимальный размер сегмента (MSS) равен 256 байт, исходные значения cwnd и ssthresh будут равны 256 и 65535 соответственно. Каждый раз, когда принимается ACK, cwnd увеличивается на значение MSS и принимает значения 512, 768, 1024, 1280 и так далее. Предположим, что переполнения не происходит, поэтому окно переполнения достигнет значения окна, объявленного получателем, а это, в свою очередь, будет означать, что объявленное окно ограничивает поток данных.
Однако нам интересно посмотреть, что произойдет в случае возникновения переполнения. Рассмотрим тот же пример из раздела "Пример RTT". В этом примере переполнение появлялось четыре раза. Был тайм-аут при передаче исходного SYN, который предназначался для установления соединения (рисунок 21.5), после чего, в процессе передачи данных, было потеряно три пакета (рисунок 21.6).
На рисунке 21.9 показаны значения двух переменных cwnd и ssthresh, когда осуществлялась повторная передача исходного SYN, за которым следовало семь первых сегментов данных. (Мы показали обмен исходными сегментами данных и их ACK на рисунке 21.2.) Переданные байты данных показаны в формате вывода команды tcpdump: 1:257(256), что означает байты с 1 по 256.
Когда возникает тайм-аут при передаче SYN, ssthresh устанавливается в свое минимальное значение (512 байт, что равно, в этом примере, двум сегментам). cwnd устанавливается в один сегмент (256 байт), чтобы войти в фазу медленного старта.
Когда получены SYN и ACK, с этими переменными ничего не происходит, так как новые данные не были подтверждены.
Когда прибывает ACK 257, мы все еще находимся в режиме медленного старта, так как cwnd меньше либо равно ssthresh, поэтому cwnd увеличивается на 256. То же самое происходит, когда прибывает ACK 513.
Когда прибывает ACK 769, мы больше не находимся в режиме медленного старта, однако переходим в режим предотвращения переполнения. Новое значение cwnd рассчитывается следующим образом
cwnd ¬ cwnd + (разм.сегмента x разм.сегмента)/cwnd + разм.сегмента/8
Это больше на 1/cwnd, чем то, что мы показали ранее, принимая во внимание то, что cwnd рассчитывается в действительности в байтах, а не в сегментах. Для этого примера мы рассчитаем
cwnd ¬ 768 + (256 x 256)/768 + 256/8
Номер сегмента (рисунок 21.2) | Действие | Переменная | |||
Отправлено | Получено | Комментарий | cwnd | ssthresh | |
инициализация | 256 | 65535 | |||
SYN | |||||
SYN |
тайм-аут
повторная передача |
256 | 512 | ||
SYN, ACK | |||||
ACK | |||||
1 | 1:257(256) | ||||
2 | ACK 257 | медленный старт | 512 | 512 | |
3 | 257:513(256) | ||||
4 | 513:769(256) | ||||
5 | ACK 513 | медленный старт | 768 | 512 | |
6 | 769:1025(256) | ||||
7 | 1025:1281(256) | ||||
8 | ACK 769 | предотвращение переполнения | 885 | 512 | |
9 | 1281:1537(256) | ||||
10 | ACK 1025 | предотвращение переполнения | 991 | 512 | |
11 | 1537:1793(256) | ||||
12 | ACK 1281 | предотвращение переполнения | 1089 | 512 |
что равно 885 (с использованием целочисленной арифметики). Когда прибывает следующий ACK 1025, мы рассчитаем
cwnd ¬ 885 + (256 x 256)/885 + 256/8
что равно 991.
Это суммарное увеличение cwnd продолжается до появления первой повторной передачи, которая происходит в районе 10-секундной метки на рисунке 21.6. Рисунок 21.10 это график для тех же самых данных, которые приведены на рисунке 21.6, но сюда добавлены значения cwnd.
Первые шесть значений cwnd на этом рисунке - это значения, которые мы рассчитали для рисунка 21.9. На этом рисунке невозможно указать разницу между экспотенциальным увеличением в течение медленного старта и суммарным увеличением в течение предотвращения переполнения, потому что фаза медленного старта проходит очень быстро.
Нам необходимо объяснить, что происходит в трех точках, когда возникает повторная передача. Вспомним, что каждая повторная передача возникает из-за того, что были приняты три дублированных ACK (это указывает на то, что пакет был потерян). Здесь вступает в действие алгоритм быстрой повторной передачи, описанный в разделе "Быстрая повторная передача и алгоритм быстрого восстановления". ssthresh сразу же устанавливается в половину размера окна, который соответствовал тому моменту, когда была осуществлена повторная передача, однако cwnd позволяет продолжать увеличение до тех пор, пока принимаются дублированные ACK, так как каждый дублированный ACK означает, что сегмент все еще находится в сети (принимающий TCP буферизировал его, ожидая прибытия отсутствующих данных).
Значения cwnd увеличиваются постоянно, от последнего значения на рисунке 21.9 для сегмента 12 (1089), до первого значения на рисунке 21.11 для сегмента 58 (2426). Значение ssthresh осталось тем же самым (512), так как за этот период не было осуществлено повторных передач.
Когда прибыли первые два дублированные ACK (сегменты 60 и 61), они просто подсчитываются (третий дублированный ACK не прибыл), а cwnd остается неизменным. (Эта неизменяющаяся часть рисунка 21.10, предваряет первую повторную передачу.) Однако когда прибывает третий ACK, ssthresh устанавливается в значение равное половине cwnd. cwnd устанавливается в значение ssthresh плюс размер сегмента, умноженный на количество дублированных ACK (1024 + 3 x 256). Затем осуществляется повторная передача.
Прибывает еще пять дублированных ACK (сегменты 64-66, 68 и 70), при этом cwnd каждый раз увеличивается на размер сегмента. Наконец, прибывает новый ACK (сегмент 72), и cwnd устанавливается в значение ssthresh (1024), при этом стартует стандартный алгоритм предотвращения переполнения. Так как cwnd меньше или равно ssthresh (они равны), к cwnd добавляется размер сегмента, при этом получается значение 1280. Когда прибывает следующий ACK (который не показан на рисунке 21.11), cwnd больше чем ssthresh, поэтому cwnd устанавливается в значение 1363.
В течение фазы быстрой повторной передачи и быстрого восстановления мы передаем новые данные после получения дублированных ACK в сегментах 66, 68 и 70, однако не после получения дублированных ACK в сегментах 64 и 65. Причина этого заключается в значении cwnd по сравнению с количеством неподтвержденных байтов данных. Когда прибывает сегмент 64, cwnd становится равным 2048, однако мы имеем 2304 неподтвержденных байта (девять сегментов: 46, 48, 50, 52, 54, 55, 57, 59 и 63). Мы ничего не можем послать. Когда прибывает сегмент 65, cwnd становится равным 2304, поэтому мы все еще ничего не можем отправить. Однако, когда прибывает сегмент 66, cwnd становится равным 2560, теперь мы можем послать новый сегмент данных. Точно так же, когда прибывает сегмент 68, cwnd становится равным 2816, что больше чем 2560 байт неподтвержденных данных, таким образом, мы можем послать еще один новый сегмент данных. То же самое происходит, когда прибывает сегмент 70.
Когда в момент времени 14,3 происходит следующая повторная передача (см. рисунок 21.10), которая также вызвана приемом трех дублированных ACK, мы видим такое же увеличение cwnd как если бы прибыл еще один дублированный ACK, после чего происходит уменьшение до 1024.
Повторная передача в момент времени 21,1 на рисунке 21.10 также происходит при приходе дублированных ACK. Мы получили еще три дублированных ACK после повторной передачи, поэтому мы видим три дополнительных увеличения cwnd, после чего следует уменьшение до 1280. Для остальной передачи cwnd увеличивается линейно с окончательным значением 3615.
Как только Rlogin клиент получает уведомление о срочности от своего TCP, он начинает считывание данных, которые уже ожидают этого, окно открывается (сегмент 13). Данные, буферизированные сервером, отправляются (сегменты 14, 15, 17 и 18). Последний сегмент содержит последний байт срочных данных (номер последовательности 30146), который содержит командный байт, передаваемый от сервера клиенту. Когда клиент считывает этот байт, он отбрасывает все данные, которые он прочитал в сегментах 14, 15, 17 и 18, и очищает свою выходную очередь на терминал. Следующие 2 байта в сегменте 19 содержат эхо символа прерывания: "^?". Последний сегмент, который мы показали (21), содержит приглашение shellа от клиента.
Этот пример показывает, как данные могут быть буферизированы на обоих концах соединения, когда клиент вводит символ прерывания. Если сброшено будет только 3419 байт, буферизированных на сервере, при этом 4096 байт у клиента сброшено не будет, эти 4096 байт данных вместе с тем, что было буферизировано в выходной очереди терминала у клиента, появятся в выводе.
В этой главе мы рассмотрим примеры, которые проиллюстрируют детали разных реализаций TCP тайм-аутов и повторных передач, медленный старт и предотвращение переполнения.
С помощью программы sock отправлено 32768 байт данных с хоста slip на discard сервис хоста vangogh.cs.berkeley.edu:
slip % sock -D -i -n32 vangogh.cs.berkeley.edu discard
Обратимся к рисунку, находящемуся на внутренней стороне обложки. Из рисунка видно, что slip подсоединен к Ethernet 140.252.1 двумя SLIP каналами, а затем через Internet к пункту назначения. Так как используется два SLIP канала (со скоростью 9600 бит в секунду), мы ожидаем появления определенных задержек.
Команда, приведенная выше, осуществит 32 записи по 1024 байта. Так как MTU между slip и bsdi составляет 296, то будет сгенерировано 128 сегментов, каждый из которых будет содержать 256 байт пользовательских данных. Полное время передачи займет примерно 45 секунд, и мы увидим один тайм-аут и три повторные передачи.
Пока осуществляется эта передача, мы запустим tcpdump на хосте slip, чтобы увидеть все сегменты, которые были переданы и приняты. В дополнение мы указали опцию -D, чтобы включить отладку сокетов (см. приложение A, раздел "Опция отладки сокета"). Кроме того, у нас была возможность запустить модифицированную версию программы trpt(8), которая позволяет напечатать некоторые переменные в блоке управления соединением, имеющие отношение к временам задержки, медленному старту и предотвращению переполнения.
Из-за того, что вывод достаточно большой, мы не можем показать его целиком, однако рассмотрим его по частям, в процессе изучения главы. На рисунке 21.2 показана передача данных и подтверждений в течение первых 5 секунд. Мы немного модифицировали этот вывод от предыдущего вывода команды tcpdump. Мы только оценили моменты времени, когда пакет отправлялся или принимался хостом, на котором запущена программа tcpdump, на этом рисунке мы хотели показать, что пакет двигается по сети (так как это соединение в локальной сети не похоже на распределенный Ethernet), и показать, когда принимающий хост, возможно, генерирует подтверждения. (Мы удалили все объявления окна. slip всегда объявляет окно размером 4096, а vangogh всегда объявляет окно 8192.)
Физические адреса AppleTalk с номером интерфейса - 2 имеют 32-битные значения, а не 48-битные, как у привычных Ethernet адресов. Также обратите внимание на то, что существуют запись для нашего маршрутизатора (netb находится по адресу 140.252.1.183), так как kinetics и netb находятся на одном и том же Ethernet кабеле (140.252.1), и kinetics должен использовать ARP, чтобы послать нам SNMP отклик.
В сегментах 1-13 осуществляется обычная передача данных от клиента к серверу, при этом заполняется окно размером 9216 байт. Сервер объявил окно равное 4096 и имеет размер буфера сокета по умолчанию равный 4096, однако в действительности принимает 9216 байт. Это является формой взаимодействия между кодами TCP/IP и потоковой подсистемой в SVR4.
В сегменте 13 сервер подтверждает предыдущие четыре сегмента данных, однако объявляет окно равное 0, приостанавливая тем самым передачу данных от клиента. Клиент вынужден установить свой устойчивый таймер. Если клиент не получил обновление окна до истечения таймера, он осуществляет пробу пустого окно, чтобы проверить не было ли потеряно обновление окна. Так как процесс сервера спит, 9216 байт данных буферизированы TCP и ожидают, что будут переданы приложению.
Обратите внимание на промежутки времени между пробами окна, которые осуществляет клиент. Первая (сегмент 14) происходит через 4,949 секунды после получения окна нулевого размера. Следующая (сегмент 16) - через 4,996 секунды. Затем промежутки между пробами становятся приблизительно равными 6, 12, 24, 48 и 60 секунд после предыдущей пробы.
Почему промежутки всегда на какую-то долю секунды меньше чем 5, 6, 12, 24, 48 и 60? Пробы осуществляются в соответствии с 500-миллисекундным таймером TCP. Когда таймер истекает, отправляется проба окна, а отклик принимается примерно через 4 миллисекунды. Получение отклика, вновь устанавливает таймер, однако время до следующего тика часов составляет примерно 500 минус 4 миллисекунды.
При расчете устойчивого таймера используется обычное экспотенциальное наращивание TCP. Первый тайм-аут рассчитывается как 1,5 секунды для стандартного соединения по локальной сети. Затем это значение умножается на 2 для второго тайм-аута (значение равное 3 секундам). Множитель равный 4-м дает следующее значение равное 6, множитель равный 8-ми дает значение 12, и так далее. Однако, устойчивый таймер всегда находится в диапазоне между 5 и 60 секундами, и именно эти значения мы видим на рисунке 22.1.
Проба окна содержит 1 байт данных (номер последовательности 9217). TCP всегда позволяет послать 1 байт данных, после того как окно было закрыто. Однако подтверждения, возвращающиеся с номером окна равным 0, не подтверждают этот байт. (Эти ACK получены для всех байтов с номером меньше чем 9216, и на байт с номером 9216.) Поэтому, этот байт будет передан повторно.
Характеристика устойчивого состояния отличается от тайм-аутов при повторной передаче, описанных в главе 21, тем, что TCP никогда не прекращает отправку проб окна. Эти пробы окна отправляются с 60-секундными интервалами до тех пор, пока окно не будет открыто, или приложение решит, что соединение должно быть разорвано.
В следующем примере мы читаем файл с NFS сервера, когда сервер выходит из строя и перезагружается. Это покажет как "безразличность" сервера позволяет, клиенту "не знать" о том, что сервер вышел из строя. Все то время, пока сервер сломался и перезагружается, клиент не знает о проблеме, и приложение клиента работает так же, как и раньше.
На клиенте sun мы стартовали cat с очень большим файлом в качестве аргумента (/usr/share/lib/termcap на NFS сервере svr4), отсоединили Ethernet кабель в процессе передачи, выключили и перезагрузили сервер и затем снова подсоединили кабель. Клиент был сконфигурирован таким образом, чтобы читать 1024 байта за одно NFS чтение. На рисунке 29.9 показан вывод tcpdump.
Строки 1-10 соответствуют открытию файла клиентом. Эта операция напоминает ту, что показана на рисунке 29.7. В строке 11 мы видим первое чтение (READ) из файла 1024-х байт данных; отклик возвратился в строке 12. Это продолжается до строки 129 (чтение READ по 1024 байта и затем отклик OK).
В строках 130 и 131 мы видим два запроса, которые отработаны по тайм-ауту и повторно переданы в строках 132 и 133. Первый вопрос: мы видим два запроса на чтение, один начинается со смещения 65536, а другой начинается со смещения 73728, почему? Ядро клиента определило, что приложение клиента осуществляет последовательное считывание, и постаралось получить блоки данных заранее. (Большинство Unix ядер осуществляют это чтение вперед (read-ahead).) Ядро клиента также запустило несколько NFS демонов блочного ввода-вывода (I/O) (biod процессы), которые стараются сгенерировать несколько RPC запросов от имени клиента. Один демон считывает 8192 байта, начиная с 65536 (в 1024-байтных цепочках), а другие осуществляют чтение вперед по 8192 байта, начиная с 73728.
Повторные передачи клиента появляются в строках 130-168. В строке 169 мы видим, что сервер перезагрузился, и послал ARP запрос перед тем, как откликнуться на NFS запрос клиента из строки 168. Отклик на строку 168 посылается в строке 171. Запросы клиента на чтение (READ) продолжаются.
1 0.0 sun.7ade > svr4.nfs: 104 getattr
2 0.007653 ( 0.0077) svr4.nfs > sun.7ade: reply ok 96
3 0.009041 ( 0.0014) sun.7adf > svr4.nfs: 116 lookup "share"
4 0.017237 ( 0.0082) svr4.nfs > sun.7adf: reply ok 128
5 0.018518 ( 0.0013) sun.7ae0 > svr4.nfs: 112 lookup "lib"
6 0.026802 ( 0.0083) svr4.nfs > sun.7ae0: reply ok 128
7 0.028096 ( 0.0013) sun.7ae1 > svr4.nfs: 116 lookup "termcap"
8 0.036434 ( 0.0083) svr4.nfs > sun.7ae1: reply ok 128
9 0.038060 ( 0.0016) sun.7ae2 > svr4.nfs: 104 getattr
10 0.045821 ( 0.0078) svr4.nfs > sun.7ae2: reply ok 96
11 0.050984 ( 0.0052) sun.7ae3 > svr4.nfs: 116 read 1024 bytes @ 0
12 0.084995 ( 0.0340) svr4.nfs > sun.7ae3: reply ok 1124
считывание
128 3.430313 ( 0.0013) sun.7b22 > svr4.nfs: 116 read 1024 bytes @ 64512
129 3.441828 ( 0.0115) svr4.nfs > sun.7b22: reply ok 1124
130 4.125031 ( 0.6832) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
131 4.868593 ( 0.7436) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
132 4.993021 ( 0.1244) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
133 5.732217 ( 0.7392) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
134 6.732084 ( 0.9999) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
135 7.472098 ( 0.7400) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
136 10.211964 ( 2.7399) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
137 10.951960 ( 0.7400) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
138 17.171767 ( 6.2198) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
139 17.911762 ( 0.7400) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
140 31.092136 (13.1804) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
141 31.831432 ( 0.7393) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
142 51.090854 (19.2594) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
143 51.830939 ( 0.7401) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
144 71.090305 (19.2594) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
145 71.830155 ( 0.7398) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
повторные передачи
167 291.824285 ( 0.7400) sun.7b24 > svr4.nfs: 116 read 1024 bytes @ 73728
168 311.083676 (19.2594) sun.7b23 > svr4.nfs: 116 read 1024 bytes @ 65536
сервер перезагрузился
169 311.149476 ( 0.0658) arp who-has sun tell svr4
170 311.150004 ( 0.0005) arp reply sun is-at 8:0:20:3:f6:42
171 311.154852 ( 0.0048) svr4.nfs > sun.7b23: reply ok 1124
172 311.156671 ( 0.0018) sun.7b25 > svr4.nfs: 116 read 1024 bytes @ 66560
173 311.168926 ( 0.0123) svr4.nfs > sun.7b25: reply ok 1124
считывание
Сейчас мы рассмотрим некоторые примеры использования FTP: как осуществляется управление соединением данных, как передаются текстовые файлы с использованием NVT ASCII, как в FTP используется сигнал синхронизации Telnet для прекращения процесса передачи. В завершение мы рассмотрим "анонимный FTP" (anonymous FTP).
Каждая переменная в MIB должна быть идентифицирована, когда SNMP обращается к ней, чтобы получить или установить ее значение. Во-первых, обращение осуществляется только к листовым узлам (листовой узел в древовидной структуре - любой самый удаленный элемент от корня). SNMP не работает с целыми рядами или колонками таблицы. Возвращаясь к рисунку 25.7, мы видим, что листовыми узлами дерева, являются те четыре, что мы описали на рисунке 25.8, и два на рисунке 25.9. Узлы mib, udp, udpTable и udpEntry не являются листовыми узлами.
Давайте воспользуемся tcpdump, чтобы посмотреть, какие NFS процедуры привлекаются клиентом для обычных операций с файлом. Когда tcpdump определяет, что UDP датаграмма содержит RPC вызов (call равен 0 на рисунке 29.1) с портом назначения 2049, он декодирует датаграмму как NFS запрос. Точно так же, если UDP датаграмма содержит RPC отклик (reply равен 1 на рисунке 29.2) с портом источника равным 2049, он декодирует датаграмму как NFS отклик.
Сейчас мы просмотрим сценарии 2, 3 и 4 из предыдущего раздела, чтобы рассмотреть обмен пакетами при использовании опции "оставайся в живых".
Мы рассмотрим два примера: первый показывает протокол клиент-сервер в начале Rlogin сессии, а второй показывает, что произойдет при вводе клавиши прерывания, для того чтобы остановить процесс, работающий на сервере и генерирующий много вывода. На рисунке 19.2 показан обычный поток данных по Rlogin сессии.
В предыдущем разделе показана обычная доставка почты; сейчас рассмотрим, как для доставки почты записи используются MX, а также проиллюстрируем работу команд VRFY и EXPN.
Сейчас мы рассмотрим обсуждение опций Telnet, вместе с тремя различными режимами функционирования: символ за один раз, реальный линейный режим и режим передачи строки за один раз. Также мы увидим, что происходит, когда пользователь прекращает работу процесса на сервере с использованием символа прерывания.
MTA добавляет первые три строки, а также Received: и Message-Id:, а следующие девять строк генерируются пользовательским агентом.