
СПО / Semestr 2 / Lectures 2semestr / Lecture 2_03 / Lecture 2_03
.docЛекция 3
Однопотоковые параллельные серверы
В приложениях клиент/сервер, характеризующихся тем, что затраты на опечение ввода/вывода превышают затраты на подготовку ответа на запрос, в сервере может использоваться асинхронный ввод/вывод для организации псевдопараллельной работы клиентов. Необходимо предусмотреть, чтобы единственный поток выполнения в сервере
держал открытыми соединения с несколькими клиентами и обеспечивал обслуживание сервером того соединения, через которое в определенный момент поступают данные. Сам факт поступления данных используется для активизации обработки данных сервером.
Допустим, что один поток сервера обслуживает соединения TCP одновременно с несколькими клиентами. Поток блокируется, ожидая поступления данных. Сразу после поступления данных через любое соединение поток активизируется, обрабатывает запрос и передает ответ. Затем он снова блокируется, ожидая поступления данных из другого соединения. При условии, что процессор работает достаточно быстро для того, чтобы выдержать нагрузку, возложенную на сервер, однопотоковая версия сможет обслуживать соединения с таким же успехом, как и версия с несколькими потоками. Однопотоковая реализация не требует переключения между контекстами потоков или процессов, поэтому она может дан выдержать более высокую нагрузку по сравнению с реализацией, в которой используются несколько потоков или процессов.
В основе разработки программы однопотокового, параллельного сервера лежит использование асинхронного ввода/вывода, организованного с помощь системного вызова select. Сервер создает сокет для каждого соединения, которое он должен поддерживать, а затем вызывает функцию select, которая ожидает поступления данных через каждое из них. Функция select может ожидать поступления запросов на выполнение операций ввода/вывода через все возможные сокеты, в том числе и одновременно ожидать поступления новых запросов на установление соединения.
Один единственный поток остается привязанным к общепринятому порту, через который он должен принимать запросы на установление соединения Каждый из ведомых сокетов в наборе соответствует соединению, для которого ведомый поток должен обрабатывать запросы. Сервер передает набор дескрипторов сокетов функции select в качестве параметра и ожидает активизации любого из них. После возврата управления функция select передает в вызывающий oпeратор битовую маску, которая указывает, какой из дескрипторов в наборе готов к работе. В сервере для принятия решения о том, с каким из дескрипторов нужно продолжить работу, используется порядок их активизации.
Для определения того, какие операции (ведущего или ведомого потока) должны быть выполнены для данного дескриптора, в однопотоковом сервере используется сам дескриптор. Если к работе готов дескриптор, соответствующий ведущему сокету, сервер выполняет для него такие же операции, какие выполнил бы ведущий поток: он вызывает функцию accept с этим сокетом для получения нового соединения. Если же к работе готов дескриптор, соответствующий ведомому сокету, сервер выполняет операцию ведомого потока: вызывает функцию read для получения запроса, а затем отвечает на этот запрос.
Пример однопотокового сервера
int msock; /* Ведущий сокет сервера */
fd_set rfds; /* Набор дескрипторов, готовых к чтению */
fd_set afds; /* Набор активных дескрипторов */
int fd, nfds, ssock;
/* инициализация пассивного сокета msock*/
nfds = getdtablesize();
FD_ZERO(&afds);
FD_SET(msock, &afds);
while (1) {
memcpy(&rfds, &afds, sizeof(rfds));
if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) <0) { /* ошибка */}
if (FD_ISSET(msock, &rfds)) {
alen = sizeof(fsin);
ssock = accept(msock, (struct sockaddr *)&fsin, &alen); if (ssock <0) {/* ошибка */}
FD_SET(ssock, &afds);
}
for (fd=0; fd<nfds; fd++)
if (fd != msock && FD_ISSET(fd, &rfds))
if (handler(fd) == 0) { /* число полученных байт */
close(fd);
FD_CLR(fd, &afds);
}
}
Выполнение потока этого сервера, как и выполнение ведущего потока сервера в параллельной реализации, начинается с открытия пассивного сокета в общепринятый порт. Используется системная функция getdtablesize для определения максимального числа дескрипторов, а затем применяются макрокоманды FD_ZERO и FD_SET для создания битового вектора, соответствующего дескрипторам сокетов, которые должны контролироваться функцией select. Затем сервер входит в бесконечный цикл, в котором он вызывает функцию select для ожидания готовности к работе одного или нескольких дескрипторов. Если готов дескриптор ведущего сокета, сервер вызывает функцию accept для получения нового соединения. Он добавляет дескриптор нового соединения к управляемому им набору и снова переходит в состояние ожидания активизации следующих дескрипторов. Если же готов дескриптор ведомого сокета, сервер вызывает процедуру handler для обработки клиентского запроса. Если один из дескрипторов ведомого сокета сообщает о получении признака конца файла (число полученных байт равно 0), сервер закрывает дескриптор и использует макрокоманду FD_CLR для удаления его из набора дескрипторов, используемых функцией select.
ции inetd.
Мультипротокольный сервер
Недостаток использования отдельных серверов для каждого протокола связан с неэффективным использованием ресурсов. Масштабы этой проблемы становятся очевидными, если учесть, что стандартах TCP/IP определены десятки служб.
Мультипротокольный сервер состоит из одного потока выполнения, в котором используется асинхронный ввод/вывод для обслуживания соединений по протоколу UDP или TCP. В этом сервере первоначально открываются два сокета; в одном применяется транспортный протокол UDP, а в другом – TCP. Затем в сервере используются средства асинхронного ввода/вывода для по
лучения информации о готовности к работе одного из сокетов. Если готов к работе сокет TCP, это означает, что от одного из клиентов поступил запрос на установление соединения TCP. Сервер вызывает функцию accept для получения нового соединения, а затем обменивается данными с клиентом через это соединение. Если же готов сокет UDP, это означает, что один из клиентов передал запрос в форме дейтаграммы UDP. В сервере для чтения запроса и регистрации адреса оконечной точки отправителя используется функция recvfrom. После формирования ответа сервер отправляет этому клиенту ответ с помощью функции sendto.
Для приема запросов, поступающих по нескольким транспортным протоколам, применяется один поток выполнения, в котором может быть в любое время открыто не более трех сокетов: один для запросов UDP, другой – для запросов на установление соединения TCP, и еще один (временный) сокет – для обслуживания отдельного соединения TCP.
Пример мультипротокольного сервера
int tsock; /* Ведущий сокет TCP */
int usock; /* Сокет UDP */
int ssock; /* Ведомый сокет TCP */
fd set rfds; /* Дескрипторы, готовые к чтению */
/* инициализация сокетов tsock и usock */
nfds = MAX(tsock, usock) +1; /* Длина битовой маски для набора дескрипторов */
FD_ZERO(&rfds);
while (1) {
FD_SET(tsock, &rfds); FD_SET(usock, &rfds);
if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) <0) {/* ошибка */}
if (FD_ISSET(tsock, &rfds)) {
len=sizeof(fsin);
ssock = accept(tsock, (struct sockaddr *)&fsin,&len); if(ssock <0){/* ошибка */}
handler(buf);
if(send(ssock,buf, sizeof(buf))<0) {/* ошибка */}
close(ssock);
}
if (FD_ISSET(usock, &rfds)) {
len = sizeof(fsin);
if (recvfrom(usock, buf, sizeof(buf), 0, (struct sockaddr *)&fsin, &len) <0) {/*ошибка*/}
handler(buf);
if(sendto(usock, buf, strlen(buf), 0, (struct sockaddr *)&fsin, sizeof(fsin))<0)
{/*ошибка*/}
}
}
После создания ведущих сокетов сервер выполняет подготовку к использованию ФУНКЦИИ select для перехода в состояние ожидания готовности одного или обоих из этих сокетов к вводу/выводу. Переменной nfds присваивается значение индекса битовой маски дескриптора старшего из этих двух сокетов, и битовая маска очищается, после чего сервер входит в бесконечный цикл. При каждом проходе по циклу используется макрокоманда FD_SET для построения битовой маски с битами, установленными для дескрипторов, соответствующих двум ведущим сокетам. Затем вызывается функция select для перехода в состояние ожидания активизации ввода через любой из них. Возврат управления из функции select свидетельствует о том, что готов один или оба ведущих сокета. В сервере для проверки сокета TCP, а затем сокета UDP используется макрокоманда FD_ISSET. Сервер должен проверить оба сокета, поскольку дейтаграмма UDP может поступить одновременно с запросом на установление соединения TCP, и в этом случае должны быть готовы оба сокета.
Если готов сокет TCP, это означает, что какой-то клиент инициализировал запрос на установление соединения. Сервер вызывает функцию accept для установления соединения. Эта функция возвращает дескриптор нового, временного сокета, применяемого только для обмена данными через это новое соединение. Сервер вызывает процедуру handler для формирования ответа и передает ответ через новое соединение, а затем закрывает его.
Если готов сокет UDP, это означает, что какой-то клиент прислал дейтаграмму. Сервер вызывает функцию recvfrom для чтения входящей дейтаграммы и регистрации адреса оконечной точки клиента. В нем также используется процедура handler для формирования ответа затем вызывается функция sendto для передачи его клиенту.
Замечание1: Сервер, рассматриваемый в приведенном примере, иллюстрирует важный принцип. Существует единственная разделяемая процедура (handler), которая отвечает на запросы к данной сетевой службе, и она используется независимо от того, по какому протоколу (TCP или UDP) получены эти запросы. В данном примере разделяемый код занимает несколько строк, однако в большинстве применяемых на практике серверов код, необходимый для формирования ответа, может занимать сотни или тысячи строк. Размещение всего разделяемого кода в одном месте позволяет упростить сопровождение и обеспечить идентичность службы, предоставляемой по обоим транспортным протоколам.
Замечание2: Как и в однопротокольном сервере, в рассматриваемом примере с мультипротокольным сервером используется последовательный метод обработки запросов. Последовательная реализация может оказаться неприемлемой для создания служб, которые требуют большего объема вычислений при обработке каждого запроса. В подобных случаях мультипротокольный проект может быть расширен в целях обеспечения параллельной обработки запросов. В простейшем случае мультипротокольный сервер может создавать новый поток или процесс для одновременного обслуживания каждого из соединений TCP, в то время как обработка запросов UDP будет осуществляться последовательно. Мультипротокольный сервер может быть также расширен путем применения реализации, описанной в предыдущем пункте. Такая реализация предусматривает псевдопараллельную обработку запросов, поступающих через несколько соединений TCP или через сокет UDP.