UNIX: разработка сетевых приложений
67 }68 }69 }70 exit(0);71 }Обработка всех готовых дескрипторовТеперь мы анализируем каждый элемент массива структур39-55, чтобы определить, какие дескрипторы нужно обрабатывать. Если установлен флагfileи дескриптор включен либо в наборе чтения, либо в наборе записи, неблокируемая функцияF_CONNECTINGзавершается. Как мы говорили при описании листинга 16.7, мы вызываем функциюconnect, чтобы получить ожидающую обработки ошибку для сокета. Если значение ошибки равно нулю, соединение успешно завершилось. В этом случае мы выключаем дескриптор в наборе флагов записи и вызываем функциюgetsockoptдля отправки запроса HTTP серверу.write_get_cmdПроверка, есть ли у дескриптора данныеЕсли установлен флаг56-67и дескриптор готов для чтения, мы вызываем функциюF_READING. Если соединение было закрыто другим концом, мы закрываем сокет, устанавливаем флагread, выключаем дескриптор в наборе чтения и уменьшаем число активных соединений и общее число соединений, требующих обработки.F_DONEЕсть два способа оптимизации, которые мы не используем в этом примере (чтобы не усложнять его еще больше). Во-первых, мы можем завершить цикл for в листинге 16.13, когда мы обработали число дескрипторов, которые, по сообщению функции
, были готовы. Во-вторых, мы могли, где это возможно, уменьшить значениеselect, чтобы функцияmaxfdне проверяла биты дескрипторов, которые уже сброшены. Поскольку число дескрипторов, используемых в этом коде, в любой момент времени, вероятно, меньше 10, а не порядка тысяч, вряд ли какая-либо из этих оптимизаций стоит дополнительных усложнений.selectЭффективность одновременных соединений
Каков выигрыш в эффективности при установлении множества одновременных соединений? В табл. 16.1 показано время, необходимое для выполнения определенной задачи, которая состоит в том, чтобы получить от веб-сервера домашнюю страницу и девять картинок. Время обращения RTT для данного соединения с сервером равно приблизительно 150 мс. Размер домашней страницы — 4017 байт, а средний размер девяти файлов с изображениями составил 1621 байт. Размер сегмента TCP равен 512 байт. Для сравнения мы также представляем в этой таблице значения для многопоточной версии данной программы, которую мы создаем в разделе 26.9.
Таблица 16.1. Время выполнения задания для разного количества одновременных соединений в разных версиях программы
Количество одновременных соединений Затраченное время (в секундах), отсутствие блокирования Затраченное время (в секундах), использование потоков 1 6,0 6,3 2 4,1 4,2 3 3,0 3,1 4 2,8 3,0 5 2,5 2,7 6 2,4 2,5 7 2,3 2,3 8 2,2 2,3 9 2,0 2,3 ПРИМЕЧАНИЕМы показали пример использования одновременных соединений, поскольку он служит хорошей иллюстрацией применения неблокируемого ввода-вывода, а также потому, что в данном случае эффективность применения одновременных соединений может быть измерена. Это свойство также используется в популярном приложении — веб-браузере Netscape. В этой технологии могут появиться некоторые «подводные камни», если сеть перегружена. В главе 21 [111] подробно описываются алгоритмы TCP, называемые алгоритмами медленного старта (slow start) и предотвращения перегрузки сети (congestion avoidance). Когда от клиента к серверу устанавливается множество соединений, то взаимодействие между соединениями на уровне TCP отсутствует. То есть если на одном из соединений происходит потеря пакета, другие соединения с тем же сервером не получают соответствующего уведомления, и вполне возможно, что другие соединения вскоре также столкнутся с потерей пакетов, пока не замедлятся. По этим дополнительным соединениям будет продолжаться отправка слишком большого количества пакетов в уже перегруженную сеть. Эта технология также увеличивает нагрузку на сервер.
Максимальное увеличение эффективности происходит при трех одновременных соединениях (время уменьшается вдвое), а при четырех и более одновременных соединениях прирост производительности значительно меньше.
16.6. Неблокируемая функция accept
Как было сказано в главе 6, функция
сообщает, что прослушиваемый сокет готов для чтения, когда установленное соединение готово к обработке функциейselect. Следовательно, если мы используем функциюacceptдля определения готовности входящих соединений, то нам не нужно делать прослушиваемый сокет неблокируемым, потому что когда функцияselectсообщает нам, что соединение установлено, функцияselectобычно не является блокируемой.acceptК сожалению, существует определенная проблема, связанная со временем, способная запутать нас [34]. Чтобы увидеть эту проблему, изменим код нашего эхо- клиента TCP (см. листинг 5.3) таким образом, чтобы после установления соединения серверу отсылался сегмент RST. В листинге 16.14 представлена новая версия.
Листинг 16.14. Эхо-клиент TCP, устанавливающий соединение и посылающий серверу сегмент RST
//nonblock/tcpcli03.c1 #include "unp.h"2 int3 main(int argc, char **argv)4 {5 int sockfd;6 struct linger ling;7 struct sockaddr_in servaddr;8 if (argc != 2)