Книга: Системное программирование в среде Windows

Пример: усовершенствованный сервер на основе сокетов

Программа serverSK (программа 12.2) аналогична программе serverNP (программа 11.3), являясь ее видоизмененным и усовершенствованным вариантом.

• В усовершенствованном варианте программы серверные потоки создаются по требованию (on demand), а не в виде пула потоков фиксированного размера. Каждый раз, когда сервер принимает запрос клиента на соединение, создается серверный рабочий поток, и когда клиент прекращает работу, выполнение потока завершается.

• Сервер создает отдельный поток приема (accept thread), что позволяет основному потоку опрашивать глобальный флаг завершения работы, пока вызов accept остается блокированным. Хотя сокеты и могут определяться как неблокирующиеся, потоки обеспечивают удобное универсальное решение. Следует отметить, что значительная часть расширенных функциональных возможностей Winsock призвана поддерживать асинхронные операции, тогда как потоки Windows дают возможность воспользоваться более простой и близкой к стандартам функциональностью синхронного режима работы сокетов.

• За счет некоторого усложнения программы усовершенствовано управление потоками, что позволило обеспечить поддержку состояний каждого потока.

• Данный сервер поддерживает также внутрипроцессные серверы (in-process servers), что достигается путем загрузки библиотеки DLL во время инициализации. Имя библиотеки DLL задается в командной строке, и серверный поток сначала пытается определить точку входа этой DLL. В случае успеха серверный поток вызывает точку входа DLL; в противном случае сервер создает процесс аналогично тому, как это делалось в программе serverNP. Пример DLL приведен в программе 12.3. Поскольку генерация исключений библиотекой DLL будет приводить к уничтожению всего серверного процесса, вызов функции DLL защищен простым обработчиком исключений.

При желании можно включить внутрипроцессные серверы и в программу serverNP. Самым большим преимуществом внутрипроцессных серверов является то, что они не требуют никакого контекстного переключения на другие процессы, в результате чего производительность может заметно улучшиться.

Поскольку в коде сервера использованы специфические для Windows возможности, в частности, возможности управления потоками и некоторые другие, он, в отличие от кода клиента, оказывается привязанным к Windows.

Программа 12.2. serverSK: сервер на основе сокета с внутрипроцессными серверами 

/* Глава 12. Клиент-серверная система. ПРОГРАММА СЕРВЕРА. ВЕРСИЯ НА ОСНОВЕ СОКЕТА. */
/* Выполняет указанную в запросе команду и возвращает ответ. */
/* Если удается обнаружить точку входа разделяемой библиотеки, команды */
/* выполняются внутри процесса, в противном случае – вне процесса. */
/* ДОПОЛНИТЕЛЬНАЯ ВОЗМОЖНОСТЬ: argv [1] может содержать имя библиотеки */
/* DLL, поддерживающей внутрипроцессные серверы. */
#define _NOEXCLUSIONS
#include "EvryThng.h"
#include "ClntSrvr.h" /* Определяет структуру записей запроса и ответа. */
struct sockaddr_in SrvSAddr;
/* Адресная структура сокета сервера. */
struct sockaddr_in ConnectSAddr; /* Подключенный сокет. */
WSADATA WSStartData; /* Структура данных библиотеки сокета. */
typedef struct SERVER_ARG_TAG { /* Аргументы серверного потока. */
 volatile DWORD number;
 volatile SOCKET sock;
 volatile DWORD status;
 /* Пояснения содержатся в комментариях к основному потоку. */
 volatile HANDLE srv_thd;
 HINSTANCE dlhandle; /* Дескриптор разделяемой библиотеки. */
} SERVER_ARG;
volatile static ShutFlag = FALSE;
static SOCKET SrvSock, ConnectSock;
int _tmain(DWORD argc, LPCTSTR argv[]) {
 /* Прослушивающий и подключенный сокеты сервера. */
 BOOL Done = FALSE;
 DWORD ith, tstatus, ThId;
 SERVER_ARG srv_arg[MAX_CLIENTS];
 HANDLE hAcceptTh = NULL;
 HINSTANCE hDll = NULL;  
 /* Инициализировать библиотеку WSA; задана версия 2.0, но будет работать и версия 1.1. */
 WSAStartup(MAKEWORD(2, 0), &WSStartData);
 /* Открыть динамическую библиотеку команд, если ее имя указано в командной строке. */
 if (argc > 1) hDll = LoadLibrary(argv[1]);
 /* Инициализировать массив arg потока. */
 for (ith = 0; ith < MAXCLIENTS; ith++) {
  srv_arg[ith].number = ith;
  srv_arg[ith].status = 0;
  srv_arg[ith].sock = 0;
  srv_arg[ith].dlhandle = hDll;
  srv_arg[ith].srv_thd = NULL;
 }
 /* Следовать стандартной процедуре вызова последовательности функций socket/bind/listen/accept клиентом. */
 SrvSock = socket(AF_INET, SOCK_STREAM, 0);
 SrvSAddr.sin_family = AF_INET;
 SrvSAddr.sin_addr.s_addr = htonl(INADDR_ANY);
 SrvSAddr.sin_port = htons(SERVER_PORT);
 bind(SrvSock, (struct sockaddr *)&SrvSAddr, sizeof SrvSAddr);
 listen(SrvSock, MAX_CLIENTS);
 /* Основной поток становится потоком прослушивания/соединения/контроля.*/
 /* Найти пустую ячейку в массиве arg потока сервера. */
 /* параметр состояния: 0 – ячейка свободна; 1 – поток остановлен; 2 — поток выполняется; 3 – остановлена вся система. */
 while (!ShutFlag) {
  for (ith = 0; ith < MAX_CLIENTS && !ShutFlag; ) {
   if (srv_arg[ith].status==1 || srv_arg[ith].status==3) { /* Выполнение потока завершено либо обычным способом, либо по запросу останова. */
    WaitForSingleObject(srv_arg[ith].srv_thd INFINITE);
    CloseHandle(srv_arg[ith].srv_tnd);
    if (srv_arg[ith].status == 3) ShutFlag = TRUE;
    else srv_arg[ith].status = 0;
    /* Освободить ячейку данного потока. */
   }
   if (srv_arg[ith].status == 0 || ShutFlag) break;
   ith = (ith + 1) % MAXCLIENTS;
   if (ith == 0) Sleep(1000);
   /* Прервать цикл опроса. */
   /* Альтернативный вариант: использовать событие для генерации сигнала, указывающего на освобождение ячейки. */
  }
  /* Ожидать попытки соединения через данный сокет. */
  /* Отдельный поток для опроса флага завершения ShutFlag. */
  hAcceptTh = (HANDLE)_beginthreadex(NULL, 0, AcceptTh, &srv_arg[ith], 0, &ThId); 
  while (!ShutFlag) {
   tstatus = WaitForSingleObject(hAcceptTh, CS_TIMEOUT);
   if (tstatus == WAIT_OBJECT_0) break; /* Соединение установлено. */
  }
  CloseHandle(hAcceptTh);
  hAcceptTh = NULL; /* Подготовиться к следующему соединению. */
 }
 _tprintf(_T("Остановка сервера. Ожидание завершения всех потоков сервераn"));
 /* Завершить принимающий поток, если он все еще выполняется. */
 /* Более подробная информация об используемой логике завершения */
 /* работы приведена на Web-сайте книги. */
 if (hDll != NULL) FreeLibrary(hDll);
 if (hAcceptTh != NULL) TerminateThread(hAcceptTh, 0);
 /* Ожидать завершения всех активных потоков сервера. */
 for (ith = 0; ith < MAXCLIENTS; ith++) if (srv_arg [ith].status != 0) {
  WaitForSingleObject(srv_arg[ith].srv_thd, INFINITE);
  CloseHandle(srv_arg[ith].srv_thd);
 }
 shutdown(SrvSock, 2);
 closesocket(SrvSock);
 WSACleanup();
 return 0;
}
static DWORD WINAPI AcceptTh(SERVER_ARG * pThArg) {
 /* Принимающий поток, который предоставляет основному потоку возможность опроса флага завершения. Кроме того, этот поток создает серверный поток. */
 LONG AddrLen, ThId;
 AddrLen = sizeof(ConnectSAddr);
 pThArg->sock = accept(SrvSock, /* Это блокирующий вызов. */
  (struct sockaddr *)&ConnectSAddr, &AddrLen);
 /* Новое соединение. Создать серверный поток. */
 pThArg->status = 2;
 pThArg->srv_thd = (HANDLE)_beginthreadex (NULL, 0, Server, pThArg, 0, &ThId);
 return 0; /* Серверный поток продолжает выполняться. */
}
static DWORD WINAPI Server(SERVER_ARG * pThArg)
/* Функция серверного потока. Поток создается по требованию. */
{
 /* Каждый поток поддерживает в стеке собственные структуры данных запроса, ответа и регистрационных записей. */
 /* … Стандартные объявления из serverNP опущены … */
 SOCKET ConnectSock; 
 int Disconnect = 0, i;
 int (*dl_addr)(char *, char *);
 char *ws = " tn"; /* Пробелы. */
 GetStartupInfo(&StartInfoCh);
 ConnectSock = pThArg->sock;
 /* Создать имя временного файла. */
 sprintf(TempFile, "%s%d%s", "ServerTemp", pThArg->number, ".tmp");
 while (!Done && !ShutFlag) { /* Основной командный цикл. */
  Disconnect = ReceiveRequestMessage(&Request, ConnectSock);
  Done = Disconnect || (strcmp(Request.Record, "$Quit") == 0) || (strcmp(Request.Record, "$ShutDownServer") == 0);
  if (Done) continue;
  /* Остановить этот поток по получении команды "$Quit" или "$ShutDownServer". */
  hTrapFile = CreateFile(TempFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  /* Проверка наличия этой команды в DLL. Для упрощения команды */
  /* разделяемой библиотеки имеют более высокий приоритет по сравнению */
  /* с командами процесса. Прежде всего, необходимо извлечь имя команды.*/
  i = strcspn(Request.Record, ws); /* Размер лексемы. */
  memcpy(sys_command, Request.Record, i) ;
  sys_command[i] = '';
  dl_addr = NULL; /* Будет установлен в случае успешного выполнения функции GetProcAddress. */
  if (pThArg->dlhandle != NULL) {/* Проверка поддержки "внутрипроцессного" сервера. */
   dl_addr = (int (*)(char *, char *))GetProcAddress(pThArg->dlhandle, sys_command);
   if (dl_addr != NULL) __try {
    /* Защитить серверный процесс от исключений, возникающих в DLL*/
    (*dl_addr)(Request.Record, TempFile);
   } __except (EXCEPTION_EXECUTE_HANDLER) {
    ReportError(_T("Исключение в DLL"), 0, FALSE);
   }
  }
  if (dl_addr == NULL) { /* Поддержка внутрипроцессного сервера отсутствует. */
   /* Создать процесс для выполнения команды. */
   /* … То же, что в serverNP … */
  }
  /* … То же, что в serverNP … */
 } /* Конец основного командного цикла. Получить следующую команду. */
 /* Конец командного цикла. Освободить ресурсы; выйти из потока. */ 
 _tprintf(_T("Завершение работы сервера# %dn"), pThArg->number);
 shutdown(ConnectSock, 2);
 closesocket(ConnectSock);
 pThArg->status = 1;
 if (strcmp(Request.Record, "$ShutDownServer") == 0) {
  pThArg->status = 3;
  ShutFlag = TRUE;
 }
 return pThArg->status;
}
 

Оглавление книги


Генерация: 1.192. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз