Листинг 16.5. Отсортированный вывод функции tcpdump и данных диагностики
solaris % <b>tcpdump -r tcpd -N | sort diag -</b>
10:18:34.486392 solaris.33621 > linux.echo: S 1802738644:1802738644(0) win 8760 <mss 1460>
10:18:34.488278 linux.echo > solaris.33621: S 3212986316 3212986316(0) ack 1802738645 win 8760 <mss 1460>
10:18:34.488490 solaris.33621 > linux.echo: . ack 1 win 8760
10:18:34.491482: read 4096 bytes from stdin
10:18:34.518663 solaris.33621 > linux.echo: P 1461(1460) ack 1 win 8760
10:18:34.519016: wrote 4096 bytes to socket
10:18:34.528529 linux echo > solaris.33621. P 1:1461(1460) ack 1461 win 8760
10:18:34 528785 solaris.33621 > linux.echo: . 1461 2921(1460) ack 1461 win 8760
10:18:34.528900 solaris.33621 > linux echo: P 2921:4097(1176) ack 1461 win 8760
10:18:34.528958 solaris 33621 > linux.echo: ack 1461 win 8760
10:18:34.536193 linux echo: > solaris.33621: . 1461:2921(1460) ack 4097 win 8760
10:18:34.536697 linux.echo: > solaris.33621: P 2921.3509(588) ack 4097 win 8760
10:18.34.544636: read 4096 bytes from stdin 10:18:34.568505: read 3508 bytes from socket
10:18:34.580373 solaris 33621 > linux.echo: . ack 3509 win 8760
10:18:34.582244 linux.echo > solaris.33621: P 3509.4097(588) ack 4097 win 8760
10:18:34.593354: wrote 3508 bytes to stdout
10:18:34.617272 solaris.33621 > linux.echo: P 4097.5557(1460) ack 4097 win 8760
10:18:34.617610 solaris 33621 > linux.echo: P 5557:7017(1460) ack 4097 win 8760
10:18:34.617908 solaris.33621 > linux.echo: P 7017.8193(1176) ack 4097 win 8760
10:18:34.618062: wrote 4096 bytes to socket
10:18:34.623310 linux.echo > solaris.33621: . ack 8193 win 8760
10:18:34.626129 linux.echo > solaris.33621: . 4097.5557(1460) ack 8193 win 8760
10:18:34.626339 solaris.33621 > linux.echo: . ack 5557 win 8760
10:18:34.626611 linux.echo > solaris.33621: P 5557:6145(588) ack 8193 win 8760
10:18:34.628396 linux.echo > solaris.33621: 6145:7605(1460) ack 8193 win 8760
10:18:34.643524: read 4096 bytes from stdin 10:18:34.667305. read 2636 bytes from socket
10:18:34.670324 solaris.33621 > linux echo: . ack 7605 win 8760
10:18:34.672221 linux.echo > solaris.33621: P 7605.8193(588) ack 8193 win 8760
10:18:34.691039: wrote 2636 bytes to stdout
Мы удалили записи (
DF
) из сегментов, отправленных Solaris, означающие, что устанавливается бит DF (он используется для определения величины транспортной MTU).
Используя этот вывод, мы можем нарисовать временную диаграмму происходящих событий (рис. 16.3). На этой диаграмме представлены события в различные моменты времени, причем ориентация диаграммы такова, что более поздние события расположены ниже на странице.
Рис. 16.3. Временная диаграмма событий для примера неблокируемого ввода
На этом рисунке мы не показываем сегменты ACK. Также помните, что если программа выводит сообщение
wrote N bytes to stdout
(записано
N байт в стандартное устройство вывода), это означает, что завершилась функция
write
, возможно, заставившая TCP отправить один или более сегментов данных.
По этому рисунку мы можем проследить динамику обмена между клиентом и сервером. Использование неблокируемого ввода-вывода позволяет программе использовать преимущество этой динамики, считывая или записывая данные, когда операция ввода или вывода может иметь место. Ядро сообщает нам, когда может произойти операция ввода-вывода, при помощи функции
select
.
Мы можем рассчитать время выполнения нашей неблокируемой версии, используя тот же файл из 2000 строк и тот же сервер (с периодом RTT, равным 175 мс), что и в разделе 6.7. Теперь время оказалось равным 6,9 с по сравнению с 12,3 с в версии из раздела 6.7. Следовательно, неблокируемый ввод-вывод сокращает общее время выполнения этого примера, в котором файл отправляется серверу.
Более простая версия функции str_cli
Неблокируемая версия функции
str_cli
, которую мы только что показали, нетривиальна: около 135 строк кода по сравнению с 40 строками версии, использующей функцию
select
с блокируемым вводом-выводом (см. листинг 6.2), и 20 строками начальной версии, работающей в режиме остановки и ожидания (см. листинг 5.4). Мы знаем, что эффект от удлинения кода в два раза, с 20 до 40 строк оправдывает затраченные усилия, поскольку в пакетном режиме скорость возрастает почти в 30 раз, а применение функции
select
с блокируемыми дескрипторами осуществляется не слишком сложно. Но будут ли оправданы затраченные усилия при написании приложения, использующего неблокируемый ввод-вывод, с учетом усложнения итогового кода? Нет, ответим мы. Если нам необходимо использовать неблокируемый ввод-вывод, обычно бывает проще разделить приложение либо на процессы (при помощи функции
fork
), либо на потоки (см. главу 26).
В листинге 16.6 показана еще одна версия нашей функции
str_cli
, разделяемая на два процесса при помощи функции
fork
.
Эта функция сразу же вызывает функцию
fork
для разделения на родительский и дочерний процессы. Дочерний процесс копирует строки от сервера в стандартный поток вывода, а родительский процесс — из стандартного потока ввода серверу, как показано на рис. 16.4.
Рис. 16.4. Функция str_cli, использующая два процесса
Мы показываем, что соединения TCP являются двусторонними и что родительский и дочерний процессы совместно используют один и тот же дескриптор сокета: родительский процесс записывает в сокет, а дочерний процесс читает из сокета. Есть только один сокет, один буфер приема сокета и один буфер отправки, но на этот сокет ссылаются два дескриптора: один в родительском процессе и один в дочернем.
Листинг 16.6. Версия функции str_cli, использующая функцию fork
//nonblock/strclifork.c
1 #include "unp.h"