/* Обработчик управляющих сигналов консоли, используемый для остановки сервера. */
SetConsoleCtrlHandler(Handler, TRUE);
/* Периодически создавать имя широковещательного канала потока. */
hMonitor = (HANDLE)_beginthreadex(NULL, 0, ServerBroadcast, NULL, 0, &MonitorId);
/* Создать экземпляр канала и временный файл для каждого серверного потока. */
for (iNp = 0; iNp < MAX_CLIENTS; iNp++) {
hNp = CreateNamedPipe(SERVER_PIPE, PIPE_ACCESS_DUPLEX, PIPE_READMODE_MESSAGE | PIPE_TYPE_MESSAGE | PIPE_WAIT, MAXCLIENTS, 0, 0, INFINITE, pNPSA);
ThArgs[iNp].hNamedPipe = hNp;
ThArgs[iNp].ThreadNo = iNp;
GetTempFileName(_T("."), _T("CLP"), 0, ThArgs[iNp].TmpFileName);
hSrvrThread[iNp] = (HANDLE)_beginthreadex(NULL, 0, Server, &ThArgs[iNp], 0, &ThreadId);
}
/* Ждать завершения выполнения всех потоков. */
WaitForMultipleObjects(MAXCLIENTS, hSrvrThread, TRUE, INFINITE);
WaitForSingleObject(hMonitor, INFINITE);
CloseHandle(hMonitor);
for (iNp = 0; iNp < MAXCLIENTS; iNp++) {
/* Закрыть дескрипторы канала и удалить временные файлы. */
CloseHandle(hSrvrThread[iNp]);
DeleteFile(ThArgs[iNp].TmpFileName);
}
_tprintf(_T("Серверный процесс завершил выполнение.\n"));
return 0;
}
static DWORD WINAPI Server(LPTHREAD_ARG pThArg)
/* Функция потока сервера; по одной для каждого потенциального клиента. */
{
HANDLE hNamedPipe, hTmpFile = INVALID_HANDLE_VALUE, hConTh, hClient;
DWORD nXfer, ConThId, ConThStatus;
STARTUPINFO StartInfoCh;
SECURITY_ATTRIBUTES TempSA = {sizeof(SECURITY_ATTRIBUTES), NULL, TRUE};
PROCESS_INFORMATION ProcInfo;
FILE *fp;
REQUEST Request;
RESPONSE Response;
GetStartupInfo(&StartInfoCh);
hNamedPipe = pThArg->hNamedPipe;
hTmpFile = CreateFile(pThArg->TmpFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, &TempSA, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL);
while (!ShutDown) { /* Цикл соединений. */
/* Создать поток соединения; ждать его завершения. */
hConTh = (HANDLE)_beginthreadex(NULL, 0, Connect, pThArg, 0, &ConThId);
/* Ожидание соединения с клиентом и проверка флага завершения работы.*/
while (!ShutDown && WaitForSingleObject(hConTh, CS_TIMEOUT) == WAIT_TIMEOUT) { /* Пустое тело цикла. */ };
CloseHandle(hConTh);
if (ShutDown) continue; /*Флаг может быть установлен любым потоком.*/
/* Соединение существует. */
while (!ShutDown && ReadFile(hNamedPipe, &Request, RQ_SIZE, &nXfer, NULL)) {
/* Получать новые команды до отсоединения клиента. */
ShutDown = ShutDown || (_tcscmp(Request.Record, ShutRqst) == 0);
if (ShutDown) continue; /* Проверяется на каждой итерации. */
/* Создать процесс для выполнения команды. */
StartInfoCh.hStdOutput = hTmpFile;
StartInfoCh.hStdError = hTmpFile;
StartInfoCh.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
StartInfoCh.dwFlags = STARTF_USESTDHANDLES;
CreateProcess(NULL, Request.Record, NULL, NULL, TRUE, /* Унаследовать дескрипторы. */
0, NULL, NULL, &StartInfoCh, &ProcInfo);
/* Выполняется процесс сервера. */
CloseHandle(ProcInfo.hThread);
WaitForSingleObject(ProcInfo.hProcess, INFINITE);
CloseHandle(ProcInfo.hProcess);
/* Отвечать по одной строке за один раз. Здесь удобно использовать функции библиотеки С для работы со строками. */
fp = _tfopen(pThArg->TmpFileName, _T("r"));
Response.Status = 0;
while(_fgetts(Response.Record, MAX_RQRS_LEN, fp) != NULL) WriteFile(hNamedPipe, &Response, RS_SIZE, &nXfer, NULL);
FlushFileBuffers(hNamedPipe);
fclose(fp);
/* Уничтожить содержимое временного файла. */
SetFilePointer(hTmpFile, 0, NULL, FILE_BEGIN);
SetEndOfFile(hTmpFile);
/* Отправить признак конца ответа. */
Response.Status = 1;
strcpy(Response.Record, "");
WriteFile(hNamedPipe, &Response, RS_SIZE, &nXfer, NULL);
}
/* Конец основного командного цикла. Получить следующую команду. */
/* Принудительно завершить выполнение потока, если он все еще активен.*/
GetExitCodeThread(hConTh, &ConThStatus);
if (ConThStatus == STILL_ACTIVE) {
hClient = CreateFile(SERVER_PIPE, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN EXISTING, FILE ATTRIBUTE NORMAL, NULL);
if (hClient != INVALID_HANDLE_VALUE) CloseHandle (hClient);
WaitForSingleObject (hConTh, INFINITE);
}
/* Клиент отсоединился или имеется запрос останова. */
FlushFileBuffers(hNamedPipe);
DisconnectNamedPipe(hNamedPipe);
}
/* Конец командного цикла. Освободить ресурсы; выйти из потока. */
if (hTmpFile != INVALID_HANDLE_VALUE) CloseHandle(hTmpFile);
DeleteFile(pThArg->TmpFileName);
_tprintf(_T("Выход из потока номер %d\n"), pThArg->ThreadNo);
_endthreadex(0);
}
static DWORD WINAPI Connect(LPTHREAD_ARG pThArg) {
/* Поток соединения разрешает серверу опрос флага ShutDown. */
ConnectNamedPipe(pThArg->hNamedPipe, NULL);
_endthreadex(0);
return 0;
}
BOOL WINAPI Handler(DWORD CtrlEvent) {
/* Завершить работу системы. */
ShutDown = TRUE;
return TRUE;
}
Комментарии по поводу клиент-серверного процессора командной строки
Данное решение характеризуется рядом особенностей и ограничений, которые будут обсуждаться в последующих главах.
• Соединяться с сервером и выполнять параллельные запросы могут сразу несколько серверов; каждому клиенту назначается серверный (или рабочий) поток, выделяемый из пула потоков.
• Сервер и клиенты могут выполняться либо в ответ на отдельные подсказки командной строки, либо под управлением программы JobShell (программа 6.3).
• Если во время попыток клиента соединиться с сервером все экземпляры именованного канала оказываются задействованными, то новый клиент будет находиться в состоянии ожидания до тех пор, пока другой клиент не разорвет соединение в ответ на получение команды $Quit, тем самым делая его доступным для ожидающего клиента. Возможны ситуации, когда сразу несколько новых клиентов будут одновременно пытаться создать соединение с сервером, соревнуясь между собой за право открытия доступного экземпляра; потоки, проигравшие в этой конкурентной борьбе, будут вынуждены вновь перейти в состояние ожидания.