UNIX: разработка сетевых приложений
2 void3 str_cli(FILE *fp, int sockfd)4 {5 pid_t pid;6 char sendline[MAXLINE], recvline[MAXLINE];7 if ((pid = Fork()) == 0) { /* дочерний процесс: сервер -> stdout */8 while (Readline(sockfd, recvline, MAXLINE) > 0)9 Fputs(recvline, stdout);10 kill(getppid(), SIGTERM); /* в случае, если родительский процессвсе еще выполняется */11 exit(0);12 }13 /* родитель: stdin -> сервер */14 while (Fgets(sendline, MAXLINE, fp) != NULL)15 Writen(sockfd, sendline, strlen(sendline));16 Shutdown(sockfd, SHUT_WR); /* конец файла на stdin, посылаем FIN */17 pause();18 return;19 }Нам нужно снова вспомнить о последовательности завершения соединения. Обычное завершение происходит, когда в стандартном потоке ввода встречается конец файла. Родительский процесс считывает конец файла и вызывает функцию
для отправки сегмента FIN. (Родительский процесс не может вызвать функциюshutdown, см. упражнение 16.1.) Но когда это происходит, дочерний процесс должен продолжать копировать от сервера в стандартный поток вывода, пока он не получит признак конца файла на сокете.closeТакже возможно, что процесс сервера завершится преждевременно (см. раздел 5.12), и если это происходит, дочерний процесс считывает признак конца файла на сокете. В таком случае дочерний процесс должен сообщить родительскому, что нужно прекратить копирование из стандартного потока ввода в сокет (см. упражнение 16.2). В листинге 16.6 дочерний процесс отправляет родительскому процессу сигнал
, в случае, если родительский процесс еще выполняется (см. упражнение 16.3). Другим способом обработки этой ситуации было бы завершение дочернего процесса, и если родительский процесс все еще выполнялся бы к этому моменту, он получил бы сигналSIGTERM.SIGCHLDРодительский процесс вызывает функцию
, когда заканчивает копирование, что переводит его в состояние ожидания того момента, когда будет получен сигнал. Даже если родительский процесс не перехватывает никаких сигналов, он все равно переходит в состояние ожидания до получения сигналаpauseот дочернего процесса. По умолчанию действие этого сигнала — завершение процесса, что вполне устраивает нас в этом примере. Родительский процесс ждет завершения дочернего процесса, чтобы измерить точное время для этой версии функцииSIGTERM. Обычно дочерний процесс завершается после родительского, но поскольку мы измеряем время, используя команду оболочкиstr_cli, измерение заканчивается, когда завершается родительский процесс.timeОтметим простоту этой версии по сравнению с неблокируемым вводом-выводом, представленным ранее в этом разделе. Наша неблокируемая версия управляла четырьмя различными потоками ввода-вывода одновременно, и поскольку все четыре были неблокируемыми, нам пришлось иметь дело с частичным чтением и частичной записью для всех четырех потоков. Но в версии с функцией
каждый процесс обрабатывает только два потока ввода-вывода, копируя из одного в другой. В применении неблокируемого ввода-вывода не возникает необходимости, поскольку если нет данных для чтения из потока ввода, то и в соответствующий поток вывода записывать нечего.forkСравнение времени выполнения различных версий функции str_cli
Итак, мы продемонстрировали четыре различных версии функции
. Для каждой версии мы покажем время, которое потребовалось для ее выполнения, в том числе и для версии, использующей программные потоки (см. листинг 26.1). В каждом случае было скопировано 2000 строк от клиента Solaris к серверу с периодом RTT, равным 175 мс:str_cli■ 354,0 с, режим остановки и ожидания (см. листинг 5.4);
■ 12,3 с, функция
и блокируемый ввод-вывод (см. листинг 6.2);select■ 6,9 с, неблокируемый ввод-вывод (см. листинг 16.1);
■ 8,7 с, функция
(см. листинг 16.6);fork■ 8,5 с, версия с потоками (см. листинг 26.1).
Наша версия с неблокируемым вводом-выводом почти вдвое быстрее версии, использующей блокируемый ввод-вывод с функцией
. Наша простая версия с применением функцииselectмедленнее версии с неблокируемым вводом- выводом. Тем не менее, учитывая сложность кода неблокируемого ввода-вывода по сравнению с кодом функцииfork, мы рекомендуем более простой подход.fork16.3. Неблокируемая функция connect
Когда сокет TCP устанавливается как неблокируемый, а затем вызывается функция
, она немедленно возвращает ошибкуconnect, однако трехэтапное рукопожатие TCP продолжается. Далее мы с помощью функцииEINPROGRESSпроверяем, успешно или нет завершилось установление соединения. Неблокируемая функция connect находит применение в трех случаях:select1. Трехэтапное рукопожатие может наложиться на какой-либо другой процесс. Для выполнения функции
требуется один период обращения RTT (см. раздел 2.5), и это может занять от нескольких миллисекунд в локальной сети до сотен миллисекунд или нескольких секунд в глобальной сети. Это время мы можем провести с пользой, выполняя какой-либо другой процесс.connect2. Мы можем установить множество соединений одновременно, используя эту технологию. Этот способ уже стал популярен в применении к веб-браузерам, и такой пример мы приводим в разделе 16.5.
3. Поскольку мы ждем завершения установления соединения с помощью функции
, мы можем задать предел времени для функцииselect, что позволит нам сократить тайм-аут для функцииselect. Во многих реализациях тайм-аут функции connect лежит в пределах от 75 с до нескольких минут. Бывают случаи, когда приложению нужен более короткий тайм-аут, и одним из решений может стать использование неблокируемой функцииconnect. В разделе 14.2 рассматриваются другие способы помещения тайм-аута в операции с сокетами.connectКак бы просто ни выглядела неблокируемая функция
, есть ряд моментов, которые следует учитывать.connect■ Даже если сокет является неблокируемым, то когда сервер, с которым мы соединяемся, находится на том же узле, обычно установление соединения происходит немедленно при вызове функции
.connect