UNIX: разработка сетевых приложений
23 }24 Write(fileno(stdout), buf, n);25 }26 if (FD_ISSET(fileno(fp), &rset)) { /* есть данные на входе */27 if ((n = Read(fileno(fp), buf, MAXLINE)) == 0) {28 stdineof = 1;29 Shutdown(sockfd, SHUT_WR); /* отправка сегмента FIN */30 FD_CLR(fileno(fp), &rset);31 continue;32 }33 Writen(sockfd, buf, n);34 }35 }36 }5-8— это новый флаг, инициализируемый нулем. Пока этот флаг равен нулю, мы будем проверять готовность стандартного потока ввода к чтению с помощью функцииstdineof.selectЕсли мы считываем на сокете признак конца файла, когда нам уже встретился ранее признак конца файла в стандартном потоке ввода, это является нормальным завершением и функция возвращает управление. Но если конец файла в стандартном потоке ввода еще не встречался, это означает, что процесс сервера завершился преждевременно. В новой версии мы вызываем функции16-24иreadи работаем с буферами, а не со строками, благодаря чему функцияwriteдействует именно так, как мы рассчитывали.selectКогда нам встречается признак конца файла на стандартном устройстве ввода, наш новый флаг25-33устанавливается в единицу и мы вызываем функциюstdineofсо вторым аргументомshutdownдля отправки сегмента FIN.SHUT_WRЕсли мы измерим время работы нашего клиента TCP, использующего функцию
, показанную в листинге 6.2, с тем же файлом из 2000 строк, это время составит 12,3 с, что почти в 30 раз быстрее, чем при использовании версии этой функции, работающей в режиме остановки и ожидания.str_cliМы еще не завершили написание нашей функции
: в разделе 15.2 мы разработаем ее версию с использованием неблокируемого ввода-вывода, а в разделе 23.3 — версию, работающую с программными потоками.str_cli6.8. Эхо-сервер TCP (продолжение)
Вернемся к нашему эхо-серверу TCP из разделов 5.2 и 5.3. Перепишем сервер как одиночный процесс, который будет использовать функцию
для обработки любого числа клиентов, вместо того чтобы порождать с помощью функцииselectпо одному дочернему процессу для каждого клиента. Перед тем как представить этот код, взглянем на структуры данных, используемые для отслеживания клиентов. На рис. 6.11 показано состояние сервера до того, как первый клиент установил соединение.forkРис. 6.11. Сервер TCP до того, как первый клиент установил соединение
У сервера имеется одиночный прослушиваемый дескриптор, показанный на рисунке точкой.
Сервер обслуживает только набор дескрипторов для чтения, который мы показываем на рис. 6.12. Предполагается, что сервер запускается в приоритетном (foreground) режиме, а дескрипторы 0, 1 и 2 соответствуют стандартным потокам ввода, вывода и ошибок. Следовательно, первым доступным для прослушиваемого сокета дескриптором является дескриптор 3. Массив целых чисел
содержит дескрипторы присоединенного сокета для каждого клиента. Все элементы этого массива инициализированы значением -1.clientРис. 6.12. Структуры данных для сервера TCP с одним прослушиваемым сокетом
Единственная ненулевая запись в наборе дескрипторов — это запись для прослушиваемого сокета, и поэтому первый аргумент функции
будет равен 4.selectКогда первый клиент устанавливает соединение с нашим сервером, прослушиваемый дескриптор становится доступным для чтения и сервер вызывает функцию
. Новый присоединенный дескриптор, возвращаемый функциейaccept, будет иметь номер 4, если выполняются приведенные выше предположения. На рис. 6.13 показано соединение клиента с сервером.acceptРис. 6.13. Сервер TCP после того как первый клиент устанавливает соединение
Теперь наш сервер должен запомнить новый присоединенный сокет в своем массиве
, и присоединенный сокет должен быть добавлен в набор дескрипторов. Изменившиеся структуры данных показаны на рис. 6.14.clientРис. 6.14. Структуры данных после того как установлено соединение с первым клиентом
Через некоторое время второй клиент устанавливает соединение, и мы получаем сценарий, показанный на рис. 6.15.
Рис. 6.15. Сервер TCP после того как установлено соединение со вторым клиентом
Новый присоединенный сокет (который имеет номер 5) должен быть размещен в памяти, в результате чего структуры данных меняются так, как показано на рис. 6.16.
Рис. 6.16. Структуры данных после того как установлено соединение со вторым клиентом
Далее мы предположим, что первый клиент завершает свое соединение. TCP-клиент отправляет сегмент FIN, превращая тем самым дескриптор номер 4 на стороне сервера в готовый для чтения. Когда наш сервер считывает этот присоединенный сокет, функция
возвращает нуль. Затем мы закрываем сокет, и соответственно изменяются наши структуры данных. Значениеreadlineустанавливается в -1, а дескриптор 4 в наборе дескрипторов устанавливается в нуль. Это показано на рис. 6.17. Обратите внимание, что значение переменнойclient[0]не изменяется.maxfdРис. 6.17. Структуры данных после того как первый клиент разрывает соединение
Итак, по мере того как приходят клиенты, мы записываем дескриптор их присоединенного сокета в первый свободный элемент массива
(то есть в первый элемент со значением -1). Следует также добавить присоединенный сокет в набор дескрипторов для чтения. Переменнаяclient— это наибольший используемый в данный момент индекс в массивеmaxi, а переменнаяclient(плюс один) — это текущее значение первого аргумента функции select. Единственным ограничением на количество обслуживаемых сервером клиентов является минимальное из двух значений:maxfdи максимального числа дескрипторов, которое допускается для данного процесса ядром (о чем мы говорили в конце раздела 6.3).FD_SETSIZE






