Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование в сетях Windows

.pdf
Скачиваний:
538
Добавлен:
11.03.2015
Размер:
3.02 Mб
Скачать

88 ЧАСТЬ I Устаревшие сетевые API

да-вывода и портов завершения. Механизмы асинхронного ввода-вывода позволяют одновременно обслуживать все экземпляры канала из одного потока приложения. Рассмотрим, как создать более сложный сервер с помощью потоков и перекрытого ввода вывода (подробнее о портах завершения — в главе 8).

Потоки

Разработать сложный сервер, способный поддерживать более одного соединения с помощью потоков, не так уж трудно. Для каждого соединения необходимо создать отдельный поток и работать с ним, как с простым сервером. В листинге 4-2 показан код сервера, обслуживающего пять экземпляров именованного канала. Это приложение — эхо-сервер, получающий данные от клиента и отправляющий их обратно.

Листинг 4-2. Использование потоков при реализации сложного сервера именованного канала

// Threads.срр

«include <windows.h> «include <stdio.h> «include <conio.h>

«define NUM_PIPES 5

DWORD WINAPI PipeInstanceProc(LPVOID lpParameter);

void main(void)

HANDLE ThreadHandle;

INTi; DWORD Threadld;

for(i = 0; i < NUH_PIPES;

// Создание потока для обслуживания каждого экземпляра именованного канала if ((ThreadHandle = CreateThread(NULL, 0, PipelnstanceProc,

NULL, 0, &ThreadId)) == NULL)

{

printfC'CreateThread failed with error X\n", GetLastErrorO);

return;

}

CloseHandle(ThreadHandle);

printf("Press a key to stop the server\n"); _getch();

OO ОНЯ

Г Л А ВА 4 Именованные каналы

89

Листинг 4-2. (продолжение)

II функция: PipelnstanceProc

//описание:

//Эта функция обрабатывает один экземпляр именованного канала

DWORD WINAPI PipeInstanceProc(LPVOID lpParameter)

HANDLE PipeHandle;

DWORD BytesRead;

DWORD BytesWritten;

CHAR Buffer[256];

if ((PipeHandle = CreateNamedPipe("\\\\A\PIPE\\jim", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, NUM.PIPES, 0, 0, 1000, NULL)) == INVALID_HANDLE_VALUE)

printfC'CreateNamedPipe failed with error Xd\n", GetLastErrorO);

return 0;

// Обслуживание соединений клиентов в бесконечном цикле whiled)

{

if (ConnectNamedPipe(PipeHandle, NULL) == 0)

{

printf("ConnectNamedPipe failed with error Xd\n", GetLastErrorO);

«• break;

•JH

II Чтение данных и отправка их обратно клиенту до тех пор, // пока он не прекратит передачу whlle(ReadFile(PipeHandle, Buffer, sizeof(Buffer),

&BytesRead, NULL) > 0)

{

 

 

 

pnntf("Echo Xd bytes to client\n", BytesRead);

<.u

*•

if (WriteFile(PipeHandle, Buffer, BytesRead,

 

 

&BytesWritten, NULL) == 0)

3

 

printf("WriteFile failed with error Xd\n",

 

1

GetLastErrorO);

 

 

break;

 

}

}

 

 

 

 

;[3XTS

см. след. стр.

90

ЧАСТЬ I Устаревшие сетевые API

Листинг 4-2. {продолжение)

if

(DisconnectNamedPipe(PipeHandle) == 0)

{

printfC'DisconnectNamedPipe failed with error Xd\n", GetLastErrorO),

break;

CloseHandle(PipeHandle);

return 0;

С помощью API-функции CreateThread сервер запускает пять потоков, каждый из которых выполняет функцию PipelnstanceProc Функция PipelnstanceProc работает точно так же, как простой сервер (листинг 4-1) Сеанс связи завершается вызовом функции DisconnectNamedPipe, после чего приложение снова вызывает функцию ConnectNamedPtpe с тем же описателем, чтобы обслужить другого клиента

Перекрытый ввод-вывод

Это механизм асинхронного выполнения таких API-функций, как ReadFile и WnteFile Прежде всего в функцию необходимо передать структуру OVERLAPPED, которая впоследствии будет использована для получения результатов запроса ввода-вывода с помощью API-функции GetOverlappedResult Если функция вызывается с OVERLAPPED, результат возвращается немедленно

Чтобы сервер обслуживал несколько экземпляров именованного канала с помощью перекрытого ввода-вывода, значение параметра nMaxInstances функции CreateNamedPipe должно быть больше 1, а параметр dwOpenMode — равен FILE_FLAG_OVERLAPPED На листинге 4-3 показан код сервера, обслуживающего пять экземпляров именованного канала Это приложение — эхосервер, который получает данные от клиента и отправляет их обратно

Листинг 4-3. Использование перекрытого ввода-вывода при реализации усовершенствованного сервера именованных каналов

// Overlap cpp

«include <windows.h> «include <stdio h>

«define NUM_PIPES 5

(C

«define BUFFER SIZE

256

void mam(void)

jefl

{

if i'ii,i

HANDLE PipeHandles[NUM_PIPES];

DWORD BytesTransferred,

\ CHAR Buffer[NUM_PIPES][BUFFER_SIZE];

Г Л А В А 4 Именованные каналы

9

Листинг 4-3. (продолжение)

INT 1,

OVERLAPPED Ovlap[NUM_PIPES]; • HANDLE Event[NUM_PIPES],

// Сервер

должен

хранить текущее состояние

каждого канала Для

этого

//

предназначен

массив DataRead

Исходя из

текущего состояния

канала,

//

сервер

будет определять, какую

операцию

ввода-вывода необходимо выполнить.

BOOL DataRead[NUM_PIPES],

DWORD Ret;

DWORD Pipe;

for(i = 0, l < NUM_PIPES, i++)

{

// Создание экземпляра именованного канала

if ((PipeHandles[i] = CreateNamedPipe("\\\\.\\PIPE\\jiin", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, NUM_PIPES,

0, 0, 1000, NULL)) == INVALID_HANDLE_VALUE)

{

pnntfC'CreateNamedPipe for pipe Xd failed " "with error Xd\n", l, GetLastErrorO),

return,

//Для каждого экземпляра канала создается обработчик события,

//который будет использоваться для мониторинга активности

//операций перекрытого ввода-вывода

if ((Eventfi] = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL)

{

 

printf("CreateEvent for pipe Xd failed with

error Xd\n",

l, GetLastErrorO);

 

continue,

 

// Инициализация флага состояния, определяющего

следует ли записывать

// и читать из канала

 

DataRead[i] = FALSE;

 

ZeroMemory(&Ovlap[l], sizeof(OVERLAPPED));

 

0vlap[i].hEvent = Event[i];

 

// Прослушивание клиентских соединений с помощью функции ConnectNamedPipeO if (ConnectNamedPipe(PipeHandles[i], &0vlap[i]) == 0)

{

if (GetLastErrorO

!= ERROR_IO PENDING)

{

 

"••iw

ш.с

92

ЧАСТЬ I Устаревшие сетевые API

Листинг 4-3. (продолжение)

pnntfCConnectNamedPipe for pipe Xd failed with" " error Xd\n", l, GetLastErrorO);

CloseHandle(PipeHandles[i]);

return,

}

}

}

printf ("Server is now runmng\n");

// Чтение и отправка данных обратно клиенту в бесконечном цикле while(1)

{

if ((Ret = WaitForMultipleObjects(NUM_PIPES, Event, FALSE, INFINITE)) == WAIT_FAILED)

{

printf("WaitForMultipleObjects failed with error Xd\n", GetLastErrorO);

return;

Pipe = Ret - WAIT_0BJECT_0,

ResetEvent(Event[Pipe]);

// Проверка результатов. При возникновении ошибки соединение // устанавливается заново, иначе выполняется чтение и запись

if (GetOverlappedResult(PipeHandles[Pipe], &Ovlap[Pipe], &BytesTransferred, TRUE) == 0)

{

printf("GetOverlapped result failed Xd start over\n", GetLastErrorO);

if

(DisconnectNamedPipe(PipeHandles[Pipe]) == 0)

 

 

 

<

 

 

 

Л

 

 

 

printf("DisconnectNamedPipe failed with error Xd\nV н \\

 

 

v

GetLastErrorO);

 

 

 

*4sJ

 

return;

 

 

 

 

 

}

 

AJDW

 

«XoieS

 

*

 

 

 

 

 

 

n if

(ConnectNamedPipe(PipeHandles[Pipe],

 

 

 

 

<№

&0vlap[Pipe]) ==

0)

ix

вин*,

jqfi

\\

, {

 

 

 

ХЧЬвадИ

 

i>0)

 

if (GetLastErrorO

1= ERROR_IO_PENDING)

 

 

}

 

 

{

 

 

 

 

 

 

// Обработка ошибки канала и закрытие описателя

 

 

If-

printf("ConnectNamedPipe for pipe Jfd

failed

with"

 

 

Г Л А ВА 4 Именованные каналы

, 9 3

Листинг 4-3. (продолжение)

" error Xd\n", i, GetLastError()), CloseHandle(PipeHandles[Pipe]),

DataRead[Pipe]=FALSE,

}

else

{// Проверка состояния канала. Если значение переменной

//DataRead равно FALSE, асинхронно читаем отправленные клиентом данные,

//иначе отправляем данные обратно клиенту.

if (DataRead[Pipe] == FALSE)

//Подготовка к чтению данных от клиента путем

//асинхронного вызова функции ReadFile

ZeroMemory(&Ovlap[Pipe], sizeof(OVERLAPPEO));

Ovlap[Pipe].hEvent = Event[Pipe];

if (ReadFile(PipeHandles[Pipe], Buffer[Pipe], BUFFER.SIZE, NULL, &Ovlap[Pipe]) == 0)

if (GetLastErrorO i= ERROR_IO_PENDING)

printfC'ReadFile failed with error Xd\n", GetLastErrorO);

DataRead[Pipe] = TRUE;

}

else

{

//Отправка данных обратно клиенту путем

//асинхронного вызова функции WriteFile printf("Received Xd bytes, echo bytes back\n",

BytesTransferred);

2eroMemory(&0vlap[Pipe], sizeof(OVERLAPPED)); Ovlap[Pipe].hEvent = Event[Pipe];

if (WnteFile(PipeHandles[Pipe], Buffer[Pipe], BytesTransferred, NULL, &0vlap[Pipe]) == 0)

{

if (GetLastErrorO != ERROR_IO_PENDING)

см.след.стр.

94

ЧАСТЬ I Устаревшие сетевые API

Листинг 4-3. (продолжение)

pnntf( WriteFile failed with error Xd\n GetLastErrorO),

DataRead[Pipe] = FALSE,

}

1ЭЗ fl.'UH«>

Чтобы получить описатели каждого канала, приложение пять раз вызывает функцию CreateNamedPipe Затем сервер начинает прослушивать каждый канал Для этого он пять раз асинхронно вызывает функцию ConnectNamedPipe, передавая ей структуру OVERLAPPED При подключении клиента все операции вводавывода обрабатываются асинхронно По завершении соединения с клиентами сервер вызывает функцию DisconnectNamedPipe, а затем повторно инициирует прослушивание каждого канала с помощью функции ConnectNamedPipe

Олицетворение

Именованные каналы позволяют воспользоваться встроенными возможностями защиты Windows NT и Windows 2000 для управления доступом клиентов Windows NT и Windows 2000 поддерживают так называемое олицетворение, позволяющее серверу выполняться в контексте безопасности клиента Обычно сервер именованного канала работает в контексте безопасности процесса, который его запустил Например, если сервер именованного канала запущен пользователем, обладающим привилегиями администратора, этот пользователь получает доступ практически к любым ресурсам Windows NT или Windows 2000 Данная ситуация не безопасна, ведь структура

SECURITY_ DESCRIPTOR, переданная в функцию CreateNamedPipe, открывает доступ к каналу любым пользователям

Установив соединение с клиентом с помощью функции ConnectNamedPipe, сервер может заставить свой поток выполняться в контексте безопасности клиента, вызвав API-функцию ImpersonateNamedPipeChent

BOOLImpersonateNamedPipeClient( HANDLEhNamedPipe

Параметр hNamedPipe — это описатель экземпляра именованного канала, возвращенный функцией CreateNamedPipe При вызове функции ImpersonateNamedPipeChent операционная система меняет контекст безопасности сервера на контекст безопасности клиента В результате при обращении к разным ресурсам (например, файлам) сервер будет использовать права доступа клиента Это позволяет сохранить управление доступом к ресурсам, независимо от того, кто запустил серверное приложение

При работе в контексте безопасности клиента поток сервера использует один из четырех основных уровней олицетворения анонимный (Апопу-

Г Л А ВА 4 Именованные каналы

> 95

mous), идентификация (Identification), олицетворение (Impersonation) и делегирование (Delegation) Уровни олицетворения определяют степень, до которой сервер вправе представлять клиента Завершив сеанс связи, сервер должен вызвать функцию RevertToSelf, чтобы вернуться к первоначальному контексту безопасности Эта функция не имеет параметров

BOOL RevertToSelf(VOID),

Детали реализации клиента

Реализация клиента именованного канала подразумевает разработку приложения, которое может подключаться к серверу Клиенты не могут создавать экземпляры именованных каналов и устанавливают соединение с уже существующими на сервере Для создания простого клиента выполните следующие действия

1Для проверки наличия свободного экземпляра канала вызовите API-функ- цию WaitNamedPipe

2Для установления соединения используйте API-функцию CreateFile

3Для отправки и получения данных используйте API-функции WrtteFtle и ReadFile

4 Для завершения соединения используйте API-функцию CloseHandle

Перед установлением соединения клиент должен проверить наличие свободного экземпляра именованного канала функцией WaitNamedPipe

BOOLWaitNamedPipe( LPCTSTRlpNamedPipeName, DWORDnTimeOut

),

Параметр lpNamedPipeName определяет название именованного канала в формате UNC, а параметр nTimeOut — сколько времени клиент будет ждать свободного экземпляра канала

В случае успешного выполнения функции WaitNamedPipe откройте экземпляр именованного канала с помощью API-функции CreateFile

HANDLE CreateFile(

LPCTSTR lpFlleName,

DWORD dwDesiredAccess,

DWORD dwShareMode,

LPSECURITY.ATTRIBUTES lpSecurltyAttributes,

DWORD dwCreatlonDlsposition,

DWORD dwFlagsAndAttrlbutes,

HANDLE hTemplateFile

Параметр lpFlleName — название открываемого канала в формате UNC араметр dwDesiredAccess задает режим доступа и должен быть равен GENEMCjtEAD при чтении, или GENERIC_WRTTE — при записи данных в канал Можно указать оба флага, объединив их с помощью операции OR Режим до-

96

ЧАСТЬ I Устаревшие сетевые API

ступа должен соответствовать направлению передачи (параметр dwOpenMode), заданному при создании канала Например, если канал создан сфлагомPLPE_ACCESSJNBOUND,клиентуказываетфлагGENERICJWR1TE

Параметр dwShareMode должен быть равен 0, поскольку в каждый момент времени только один клиент может получить доступ к экземпляру канала Параметр ipSecuntyAttnbutes NULL, если вы не хотите, чтобы дочерний процесс наследовал описатель клиента Этот параметр нельзя использовать для управления доступом, так как с помощью функции CreateFtle невозможно создать экземпляры именованного канала Параметр dwCreationDispositionследуетопределитькакOPEN_EXISTING,тогдафункцияCreateFileбудет возвращать ошибку, если канала не существует

Параметр dwFlagsAndAttnbutes обязательно должен включать флаг FILE_ ATTRIBUTE_NORMAL Кроме того, можно указать флаги FILE_FLAG_ WRITEJTHROUGH,FILEJ'LAG^OVERLAPPEDиSECURITYJQOSPRESENT,объединив их с перрым операцией ИЛИ Флаги FILE_FLAG_WR1TE_ THROUGH и FILE_FLAGjOVERLAPPEDпредназначеныдляуправлениявводом-выводом(см. описаниефункцииCreateNamedPipe) ФлагSECURITY_SQOS_PRESENTопределяет уровень олицетворения клиента на сервере именованного канала Уровни олицетворения задают степень свободы действий серверного процесса от имени клиентского Клиент указывает эту информацию при подключении ксерверу Если клиент выбираетфлаг SECURITY_SQOS_PRESENT,

он должен указать один или несколько флагов, перечисленных в следующем списке

Ш SECURITYANONYMOUS — задает уровень анонимности Сервер не может получить информацию о клиенте и выполняться в контексте безопасности клиента

SECURITYIDENTIFICATION — задает уровень идентификации Сервер может получить информацию о клиенте, например идентификаторы и привилегии защиты, но не выполняться в контексте безопасности клиента Это полезно, когда серверу необходимо идентифицировать клиента, но не нужно играть его роль

И SECURITYIMPERSONATION задает уровень олицетворения Сервер может получить информацию о клиенте, а контекст безопасности клиента распространяется только на локальную систему Этот флаг позволяет серверу получить доступ к любым локальным ресурсам на сервере, как если бы он был клиентом Сервер не может представлять клиента на удаленных системах

SECURITYDELEGATION — задает уровень делегирования Сервер может получить информацию о клиенте и выполняться в контексте безопасности клиента на его локальной системе и удаленных системах

ПРИМЕЧАНИЕ В Windows NT не реализовано делегирование безопасности,поэтомуфлагSECURITYJDELEGATIONследуетприменять,только если сервер работает под управлением Windows 2000

01

Г Л А ВА 4 Именованные каналы

< 97

т SECUR1TYCONTEXTTRACKING — задает динамический режим наблюдения защиты Если этот флаг не указан, то режим статический

Ш SECURITYEFFECTIVEONLY указывает, что только включенные аспекты контекста безопасности клиента доступны серверу Если этот флаг не назначен, серверу доступны любые аспекты контекста безопасности клиента

Последний параметр функции CreateFile hTemplateFtle, не применяется при работе с именованными каналами и должен быть равен NULL После успешного выполнения функции CreateFile, клиент может отправлять и принимать данные с помощью функций ReadFile и WnteFile Завершив передачу, клиент закрывает соединение функцией CloseHandle

В листинге 4-4 приведен код простого клиента именованного канала При успешном соединении клиент отправляет серверу сообщение «This is a test».

Листинг 4-4. Простой клиент именованного канала

// Client cpp

«include <wmdows h> ((include <stdio h>

((define PIPE.NAME M\\\\.\\Pipe\\jinr

void mam(void)

{

HANDLEPipeHandle, DWORDBytesWntten;

if (WaitNamedPipe(PIPE_NAME, NMPWAIT_WAIT_FOREVER) == 0)

{

printfC'WaitNamedPipe failed with error SSd\n', GetLastError()),

return,

// Создание описателя файла именованного канала if ((PipeHandle = CreateFile(PIPE_NAME,

GENERIC_READ | GENERIC_WRITE, 0, (INSECURITY,ATTRIBUTES) NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,

(HANDLE) NULL)) == INVALID HANDLE_VALUE)

{

printf("CreateFile failed with error Xd\n", GetLastErrorO); return,

if (WriteFile(PipeHandle, 'This is a test', 14, &BytesWritten, NULL) == 0)

см.след.стр.