
СПО / Semestr 2 / Lectures 2semestr / Lecture 2_03 / Multiservices_servers
.docМультисервисные серверы
В большинстве случаев для предоставления доступа к каждой службе разрабатывается отдельный сервер. В предыдущем пункте главе было показано, что сервер, в котором используется несколько протоколов, позволяет сэкономить системные ресурсы и упростить сопровождение. Те же преимущества, которые служат стимулом к применению мультипротокольных серверов, лежат в основе стремления объединить средства поддержки нескольких служб в один мультисервисный сервер, что позволяет резко уменьшить число одновременно выполняемых процессов. Кроме того, поскольку поддержка многих мелких служб сводится к выполнению простейших вычислений, то основная часть кода сервера будет относиться к реализации всех ньюансов приема запросов и передачи ответов. Объединение средств поддержки многих служб в одном сервере позволяет уменьшить суммарный объем кода.
В
мультисервисных серверах могут
использоваться транспортные протоколы
с установлением и без установления
логического соединения. Рассмотрим
одну из возможных схем организации
процессов мультисервисного сервера
без установления логического соединения.
Последовательный мультисервисный сервер без установления логического соединения обычно состоит из одного потока управления. Сервер открывает набор сокетов UDP и привязывает каждый из них к общепринятому порту каждой из предоставляемых служб. В нем используется небольшая таблица для определения соответствия сокетов службам. Для каждого дескриптора сокета в таблице регистрируется адрес процедуры обработки запросов к службе, доступ к которой предоставляется через этот сокет. В сервере использутся системный вызов select для перехода в состояние ожидания поступления дейтаграммы через один из этих сокетов. После поступления дейтаграммы сервер вызывает соответствующую процедуру для формирования ответа и его отправки.
Мультисервисный
сервер с установлением логического
соединения также работает по
последовательному алгоритму. В данном
случае один поток выполнения
мультисервисного сервера заменяет
ведущие потоки ряда серверов с
установлением логического соединения.
На самом верхнем уровне организации
работы в мультисервисном сервере для
выполнения его функций применяется
асинхронный ввод/вывод. В каждый момент
времени в одном потоке управления
имеется один открытый сокет для каждой
службы, а для обслуживания конкретного
соединения открыт лишь один дополнительный
сокет. Начиная выполнение, мультисервисный
сервер создает один сокет для каждой
службы привязывает сокет к общепринятому
порту этой службы, а затем вызывает
функцию select для перехода в состояние
ожидания запросов на установление
соединения через любой из этих сокетов.
Если готов один из сокетов, сервер
вызывает функцию accept для установления
нового соединения, которая создает для
него новый сокет. Сервер использует
этот сокет для обмена данными с клиентом,
а затем закрывает его. Кроме одного
ведущего сокета для каждой службы, в
сервере в любое время открыто не более
одного дополнительного сокета.
В
данном сервере также ведется таблица
соответствия, позволяющая определить
способ обработки каждого входящего
соединения. Начиная работу, сервер
распределяет ведущие сокеты. Для каждого
ведущего сокета сервер вносит запись
в таблицу соответствия с указанием
номера сокета и процедуры, реализующей
службу, доступ к которой предоставляется
через этот сокет. После распределения
ведущего сокета для каждой службы сервер
вызывает функцию select для перехода в
состояние ожидания запросов на
установление соединения. После поступления
такого запроса сервер использует таблицу
соответствия для определения того,
какая из многих внутренних процедур
должна быть вызвана для поддержки
службы, затребованной клиентом.
Процедура, вызываемая мультисервисным сервером при получении запроса на установление соединения, может сама принимать и обслуживать новое соединение (и в этом случае сервер становится последовательным) или создавать ведомый процесс для обработки этого запроса (в таком случае сервер становится параллельным). Мультисервисный сервер может быть запрограммирован на последовательную поддержку одних служб и параллельную поддержку других, в зависимости от требуемых каждой службой операций. Параллельная работа может быть обеспечена с использованием нескольких однопотоковых процессов или одного многопотокового процесса.
Как и в последовательной реализации, сразу после завершения взаимодействия с клиентом процедура закрывает используемое ею новое соединение. В случае параллельной реализации ведущий процесс сервера закрывает соединение сразу после создания им ведомого процесса; соединение остается открытым в ведомом процессе. Ведомый процесс работает точно так же, как и ведомый процесс в обычном параллельном сервере с установлением логического соединения. Закончив сеанс взаимодействия, ведомый процесс закрывает сокет и заканчивает работу.
Реализация однопотокового мультисервисного сервера встречается редко, но возможно также обеспечить выполнение всей работы мультисервисного сервера в одном потоке. Вместо создания ведомого процесса для каждого входящего запроса на усановление соединения, сервер добавляет сокет для каждого нового соединения к применяемому им в функции select набору сокетов. Если готов один из ведущих сокетов, сервер вызывает функцию accept; если готов один из ведомых сокетов, сервер вызывает функцию recv для получения входящего запроса от клиента формирует ответ и вызывает функцию send для передачи ответа клиенту.
Одним из основных недостатков большинства проектов, описанных выше, является отсутствие модульности: для смены кода поддержки любой отдельно взятой службы требуется перетрансляция всего мультисервисного сервера. Этот недостаток кажется незначительным до тех пор, пока речь не идет о сервере, поддерживающим много служб. Предпочтительно разбить крупный, монолитный, мульсервисный сервер на независимые компоненты с использованием отдельно отанслированных программ для поддержки каждой службы.
Рассмотрим
параллельный сервер с установлением
логического соединения. Ведущий сервер
ожидает поступления запросов на
установление соединений через набор
ведущих сокетов. После поступления
запроса на соединение ведущий сервер
вызывает функцию fork для создания ведомого
процесса, который будет обслуживать
соединение. Сервер должен иметь доступ
к коду для всех служб, оттранслированному
в целях включения в ведущую программу.
В сервере используется функция execve для
вызова на выполнение отдельной программы,
когда возникает необходимость обслужить
каждое соединение.
Поскольку функция execve выполняет выборку новой программы из файла, то проект, описанный выше, позволяет системному администратору в случае необходимости заменить только один файл без перетрансляции мультисервисного сервера, останова главного сервера или его перезапуска. Применение функции execve позволяет отделить программы поддержки каждой службы от кода ведущего сервера, который устанавливает соединения.
Возможен также мультипротокольный проект мультисервисного сервера. Для обозначения такого сервера часто используют термин суперсервер. В принципе, суперсервер функционирует во многом аналогично обычному мультисервисному серверу. Первоначально сервер открывает один или два ведущих сокета для каждой из предоставляемых им служб. Ведущие сокеты для каждой конкретной службы соответствуют протоколу UDP или TCP. В сервере используется функция select для перехода в состояние ожидания готовности к работе любого сокета. Если готов сокет UDP, сервер вызывает процедуру, которая считывает следующий дейтаграмму из сокета, формирует ответ и передает его клиенту. Если готов сокет TCP, сервер вызывает процедуру, которая получает с помощью функции accept, вызванной с этим сокетом, новое соединение и обслуживает его. Сервер может сам обслуживать соединение, и в этом случае он является последовательным, или создавать новый процесс для обслуживания соединения, и тогда он считается параллельным.
Пример с мультисервисным сервером
После инициализации структур данных и открытия сокетов для каждой из предоставляемых служб, главная процедура входит в бесконечный цикл. При каждом проходе по циклу вызывается функция select для перехода в состояние ожидания готовности одного из сокетов. После возврата управления из функции select сервер перебирает в цикле все возможные дескрипторы сокетов и использует макрокоманду FD_ISSET для проверки готовности дескриптора. Найдя готовый к работе дескриптор сокета, сервер вызывает функцию для обработки содержащегося в сокете запроса. Для этого в сервере используется массив fd2sv, который установливает соответствие между номером дескриптора и записью в массиве svent.
Каждая запись в массиве svent содержит структуру типа service, которая устанавливает соответствие между дескриптором сокета и службой. Поле sv_func содержит адрес функции, предназначенной для поддержки службы. После установления соответствия между дескриптором и записью массива svent программа вызывает выбранную функцию. Для обслуживания сокета UDP сервер вызывает обработчик службы непосредственно, а для обслуживания сокета TCP сервер вызывает обработчик службы косвенно, через процедуру doTCP.
Для служб TCP требуется дополнительная процедура, поскольку сокет TCP в сервере с установлением логического соединения соответствует ведущему сокету. Переход в состояние готовности такого сокета означает, что в него поступил запрос на установление соединения. В сервере для обслуживания соединения должен быть создан новый процесс. Поэтому в процедуре doTCP вызывается функция accept для получения нового соединения. В ней затем вызывается функция fork для создания нового ведомого процесса. После закрытия лишних дескрипторов файлов в процедуре doTCP вызывается функция обработчика службы (sv_func). После возврата Управления из этой функции поддержки службы ведомый процесс завершается.
#define TCP_SERV 1
#define UDP_SERV 0
#define NOSOCK -1
struct service {
char *sv_name;
char sv_useTCP;
int sv_sock;
void (*sv func)(int);
};
int passiveTCP(const char *service, int qlen);
int passiveUDP(const char *service);
void TCPservice1(int), TCPservice2(int), TCPservice3(int), TCPservice4(int);
void doTCP(struct service *psv);
void reaper(int sig);
struct service svent[] = {
{ "srv1", TCP_SERV, NOSOCK, TCPservice1 },
{ "srv2", TCP_SERV, NOSOCK, TCPservice2 },
{ "srv3", TCP_SERV, NOSOCK, TCPservice3 },
{ "srv4", TCP_SERV, NOSOCK, TCPservice4 },
{ 0, 0, 0, 0 }, };
int main(int argc, char *argv[]) {
struct service *psv; /* Указатель таблицы service */
struct service *fd2sv[NOFILE]; /* Отображение дескриптора файла на указатель таблицы служб */
int fd, nfds;
fd_set afds, rfds; /* Дескрипторы, готовые к чтению */
nfds = 0;
FD_ZERO(&afds);
for (psv = &svent[0]; psv->sv_name; ++psv) {
if (psv->sv_useTCP)
psv->sv_sock = passiveTCP(psv->sv_name, QLEN);
else
psv->sv sock = passiveUDP(psv->sv_name);
fd2sv[psv->sv_sock] = psv;
nfds = MAX(psv->sv_sock+l, nfds);
FD_SET(psv->sv sock, Safds);
}
signal(SIGCHLD, reaper);
while (1) {
memcpy(&rfds, &afds, sizeof(rfds));
if (select(nfds, &rfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0) <0) { if (errno == EINTR)continue; /* ошибка */ }
for (fd=0; fd<nfds; ++fd)
if (FD_ISSET(fd, &rfds)) {
psv = fd2sv[fd];
if (psv->sv_useTCP)
doTCP(psv);
else
psv->sv_func(psv->sv_sock);
}
}
}
void doTCP(struct service *psv) {
ssock = accept(psv->sv_sock, (struct sockaddr *)&fsin, salen); if (ssock <0) {/* ошибка*/}
switch (fork()) {
case 0:
break;
case -1: /* ошибка */
return;
default:
close(ssock);
return;
}
for (fd = NOFILE; fd>= 0; —fd)
if (fd != ssock)
close(fd);
psv->sv_func(ssock);
exit(0);
}
На практике во многих операционных системах предусмотрен механизм суперсервера, в который системные администраторы могут встраивать дополнительные службы. В целях упрощения эксплуатации суперсерверы часто являют настраиваемыми: набор служб, поддерживаемых суперсервером, может быть изменен без перетрансляции исходного кода. Возможны конфигурации двух типов: статическая и динамическая. Статическая конфигурация применяется в тот момент, когда суперсервер вызывается на выполнение. Как правило, информация о конфигурации помещается в файле, считываемом суперсервером во время запуска на выполнение. В файле конфигурации определен набор служб, предоставляемый суперсервером, а также перечень выполняемых программ, которые должны использоваться для каждой службы.
Динамическая конфигурация применяется во время работы суперсервера. Как и статически настраиваемый, динамически настраиваемый суперсервер считывает файл конфигурации во время запуска на выполнение. Файл конфигурации определяет первоначальный набор служб, поддерживаемых суперсервером. Однако в отличие от статически настраиваемого, динамически настраиваемый суперсервер позволяет переопределить набор предоставляемых им служб без перезапуска. Для изменения состава службы системный администратор вносит изменения в файл конфигурации, а затем передает суперсерверу указание, что требуется реконфигурация. Суперсервер проверяет файл конфигурации и соответствующим образом меняет свое поведение.
/etc/inetd.conf — файла конфигурации inetd.