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

       

Используйте программу ttcp


| | |

Часто необходимо иметь утилиту, которая может посылать произвольный объем данных другой (или той же самой) машине по протоколу TCP или UDP и собирать статистическую информацию о полученных результатах. В этой книге уже написано несколько программ такого рода. В этом разделе вы познакомитесь с готовым инструментом, обладающим той же функциональностью. Подобное средство можно использовать для тестирования собственного приложения или для получения информации о производительности конкретного стека TCP/IP или сети. Такая информация может оказаться бесценной на этапе создания прототипа.

Этот инструмент - программа ttcp, бесплатно распространяемая Лабораторией баллистических исследований армии США (BRL - Ballistics Research Laboratory). Ее авторы Майк Муусс (автор программы ping) и Терри Слэттери. Эта утилита доступна на множестве сайтов в Internet. В книге будет использована версия, которую Джон Лин модифицировал с целью включения дополнительной статистики; ее можно получить по анонимному FTP с сайта gwen.cs.purdue.edu из каталога /pub/lin. Версия без модификаций Лина находится, например, на сайте ftp.sgi.com в каталоге sgi/ src/ttcp, в состав ее дистрибутива входит также страница руководства.

У программы ttcp есть несколько опций, позволяющих управлять: объемом Посылаемых данных, длиной отдельных операций записи и считывания, размерами буферов приема и передачи сокета, включением или отключением алгоритма Нейгла и даже выравниванием буферов в памяти. На рис. 4.11 приведена информация о порядке использования ttcp. Дается перевод на русский язык, хотя оригинальная программа, естественно, выводит справку по-английски.

Порядок вызова:ttcp -t [-опции] хост [ < in ]

               ttcp -r [-опции > out]

Часто используемые опции:

-1 ## длина в байтах буферов, в которые происходит считывание из  сети и запись в сеть (по умолчанию 8192)

-u использовать UDP, а не TCP

-p ## номер порта, в который надо посылать данные или прослушивать (по умолчанию 5001)

-s -t: отправить данные в сеть


   -r: считать ( и отбросить) все данные из сети


-А выравнивать начало каждого буфера на эту границу(по умолчанию 16384)
-O считать, что буфер начинается с этого смещения относительно границы (по умолчанию 0)
-v печатать более подробную статистику
-d установить опцию сокета SO_DEBUG
-b ## установить размер буфера сокета (если поддерживает операционная система)
-f X формат для вычисления скорости обмена: к,К = кило (бит, байт);
               m,М = мега; g,G = гига
Опции, употребляемые вместе с -t:
-n ## число буферов, записываемых в сеть (по умолчанию 2048)
-D не буферизовать запись по протоколу TCP (установить опцию сокета TCP_NODELAY)
Опции, употребляемые вместе с -r:
-В для -s, выводить только полные блоки в соответствии с опцией -1 (для TAR)
-Т "touch": обращаться к каждому прочитанному байту
Рис. 4.11. Порядок вызова ttcp
Поэкспериментируем с размером буфера передачи сокета. Сначала прогоним тест с размером буфера, выбранным по умолчанию, чтобы получить точку отсче В одном окне запустим экземпляр ttcp-потребителя:
bsd: $ ttcp –rsv
а в другом - экземпляр, играющий роль источника:
bsd: $ ttcp -tsv bsd
ttcp-t: buflen=8192, nbuf=2048, align=16384/0, port=5013 tcp -> bsd
ttcp-t: socket
ttcp-t: connect
ttcp-t: 16777216 bytes in 1.341030 real seconds
= 12217.474628 KB/sec (95.449021 Mb/sec)
ttcp-t: 16777216 bytes in 0.00 CPU seconds
= 16384000.000000 KB/cpu sec
ttcp-t: 2048 I/O calls, msec/call = 0.67, calls/sec = 1527.18
ttcp-t: buffer address 0x8050000
bsd: $
Как видите, ttcp дает информацию о производительности. Для передачи 16 Мб потребовалось около 1,3 с.
Примечание: Аналогичная статистика печатается принимающим процессом, но поскольку цифры, по существу, такие же, они здесь не приводятся.
Также был выполнен мониторинг обмена с помощью tcpdump. Вот типичная строка выдачи:
13:05:44.084576 bsd.1061 >  bsd.5013: . 1:1449(1448)
ack Iwinl7376 <nop,nop,timestamp 11306 11306> (DF)
Из нее видно, что TCP посылает сегменты по 1448 байт. Теперь следует установить размер буфера передачи равным 1448 байт, и по­вторить эксперимент. Приемник данных нужно оставить без изменения.


bsd: $ ttcp -tsvb 1448 bsd
ttcp-t: socket
ttcp-t: sndbuf
ttcp-t: connect
ttcp-t: buflen=8192, nbuf=2048, align=16384/0, port=5013,
sockbufsize=1448 tcp -> bsd
ttcp-t: 16777216 bytes in 2457.246699 real seconds
= 6.667625 KB/sec (0.052091 Mb/sec)
ttcp-t: 16777216 bytes in 0.00 CPU seconds
= 16384000.000000 KB/cpu sec
ttcp-t: 2048 I/O calls, msec/call = 1228.62, calls/sec = 0.83 ttcp-t: buffer address 0x8050000
bds: $
На этот раз передача заняла почти 41 мин. Следует отметить, что, хотя по часам для передачи потребовалось больше 40 мин, время, затраченное процессором, Попрежнему очень мало, даже не поддается измерению. Поэтому, что бы ни произошло, это не связано с загрузкой процессора.
Теперь посмотрим, что показывает tcpdump. На рис. 4.12 приведены четыре типичные строки:
16:03:57.168093 bsd.1187 > bsd.5013: Р 8193:9641(1448)
ack 1 win 17376 <nор,ор,timestamp 44802 44802> (DF)
16:03:57.368034 bsd.5013 > bsd.1187: . ack 9641 win 17376
<nop,nор,timestamp 44802 44802> (DF)
16:03:57.368071 bsd.1187. > bsd.5013: P 9641:11089(1448)
ack 1 win;17376 <nop,nор,timestamp 44802 44802> (DF)
16:03:57.568038 bsd.5013 > bsd. 1187: .ack 11089 win 17376
<nop,nор,timestamp 44802 44802> (DF)
Рис. 4.12. Типичная выдача tcpdump для запуска ttcp -tsvb 1448 bsd
Обратите внимание, что время между последовательными сегментами составляет почти 200 мс. Возникает подозрение, что тут замешано взаимодействие между алгоритмами Нейгла и отложенного подтверждения (совет 24). И действительно именно АСК задерживаются.
Эту гипотезу можно проверить, отключив алгоритм Нейгла с помощью опции-D. Повторим эксперимент:
bsd: $ ttcp -tsvDb 1448 bsd
ttcp-t: buflen=8192, nbuf=2048, align=16384/0, port=5013,
sockbufsize=1448 tcp -> bsd ttcp-t  socket ttcp-t  sndbuf ttcp-t: connect
ttcp-t: nodelay
ttcp-t: 16777216 bytes in 2457.396882 real seconds
= 6.667218 KB/sec (0.052088 Mb/sec)
ttcp-t: 16777216 bytes in 0.00 CPU seconds
= 16384000.000000 KB/cpu sec


ttcp-t: 2048 I/O calls, msec/call = 1228.70, calls/sec = 0.83 ttcp-t: buffer address 0x8050000
bds: $
Как ни странно, ничего не изменилось.
Примечание: Это пример того, как опасно делать поспешные заключения. Стоило немного подумать и стало бы ясно, что алгоритм Нейгла тут ни при чем, так как посылаются заполненные сегменты. В частности, этому служит самый первый тест, - чтобы определить величину MSS.
В совете 39 будут рассмотрены средства трассировки системных вызовов. Тогда вы вернетесь к этому примеру и обнаружите, что выполняемая ttcp операция записи не возвращает управление в течение примерно 1,2 с. Косвенное указание на это видно и из выдачи ttcp, где каждый вызов операции ввода/вывода занимает приблизительно 1,228 мс. Но, как говорилось в совете 15, TCP обычно не блокирует операции записи, пока буфер передачи не окажется заполненным. Таким образом, становится понятно, что происходит. Когда ttcp записывает 8192 байта, ядро копирует первые 1448 байт в буфер сокета, после чего блокирует процесс, так как места в буфере больше нет. TCP посылает все эти байты в одном сегменте, но послать больше не может, так как в буфере ничего не осталось.
Примечание: Из рис. 4.12 видно, что дело обстоит именно так, поскольку в каждом отправленном сегменте задан флаг PSH, а стеки, берущие начало в системе BSD, устанавливают этот флаг только тогда, когда выполненная операция передачи опустошает буфер.
Поскольку приемник данных ничего не посылает в ответ, запускается механизм отложенного подтверждения, из-за которого АСК не возвращается до истечения тайм-аута в 200 мс.
В первом тесте TCP мог продолжать посылать заполненные сегменты данных, поскольку буфер передачи был достаточно велик (16 Кб на машине bsd) для сохранения нескольких сегментов. Трассировка системных вызовов для этого теста показывает, что на операцию записи уходит около 0,3 мс.
Этот пример наглядно демонстрирует, как важно, чтобы буфер передачи отправителя был, по крайней мере, не меньше буфера приема получателя. Хотя получатель был готов принимать данные и дальше, но в выходном буфере отправителя задержался последний посланный сегмент. Забыть про него нельзя, пока не придет АСК, говорящий о том, что данные дошли до получателя. Поскольку размер одного сегмента значительно меньше, чем буфер приема (16 Кб), его получение не приводит к обновлению окна (совет 15). Поэтому АСК задерживается на 200 мс. Подробнее о размерах буферов рассказано в совете 32.
Однако смысл этого примера в том, чтобы показать, как можно использовать ttcp для проверки эффекта установки тех или иных параметров TCP-соединения. Вы также видели, как анализ информации, полученной от ttcp, tcpdump и программы трассировки системных вызовов, может объяснить работу TCP.
Следует упомянуть о том, как использовать программу ttcp для организации «сетевого конвейера» между хостами. Например, скопировать всю иерархию каталогов с хоста А на хост В. На хосте В вводите команду
ttcp -rB | tar -xpf -
на хосте А - команду
tar -cf - каталог | ttcp -t A
Можно распространить конвейер на несколько машин, если на промежуточ­ных запустить команду
ttcp -r | ttcp -t следующий_узел

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