Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
ЧАСТЬ I Устаревшие сетевые API Табл. 4-1. (продолжение) Режим открытия Флаг Описание Управление вводом- выводом FILE FLAGJWRITE_ THROUGH FILE FLAG OVERLAPPED Безопасность WRITE DAC Функции записи не возвращают значе- ние, пока данные передаются по сети или находятся в буфере удаленного ком- пьютера. Применяется только для име- нованных каналов, работающих в по- байтовом режиме Позволяет использовать перекрытый ввод-вывод при выполнении операций чтения, записи и соединения Позволяет приложению изменять список DACL именованного канала ACCESS SYSTEM ^SECURITY Позволяет приложению изменять список SACL именованного канала WRITE OWNER Позволяет приложению изменять владельца именованного канала и групповой SID Флаги PIPE_ACCESS_ определяют направление передачи данных между сервером и клиентом. Если открыть канал с флагом PIPE ^ACCESS_DUPLEX, пе- редача по нему будет двунаправленной. При открытии канала с флагом Р1РЕ_ ACCESSJNBOUND или PIPE_ACCESS_OUTBOUND — однонаправленной: дан- ные будут передаваться только от клиента серверу или наоборот. На рис. 4-2 изображены комбинации флагов и направление передачи между сервером и клиентом. PIPE_ACCESSJ)UPLEX PIPE_ACCESS_OUTBOUND PIPE_ACCESSJI\IBOUND Двунаправленная передача Однонаправленная передача Однонаправленная передача GENERIC READI GENERICIWRITE GENERIC_READ GENERIC_WRITE Сервер Клиент Рис. 4-2. Флаги режимов и направление передачи Следующий набор флагов управляет операциями ввода-вывода сервера. При применении флага EILE_ELAG_WRITE_THROUGH функции записи не воз- вращают значение, пока данные не будут переданы по сети или не появятся в буфере удаленного компьютера. Этот флаг используют, когда сервер и кли- ент находятся на разных компьютерах и именованный канал работает в по- Г Л А В А 4 Именованные каналы 83 байтовом режиме. Флаг FILEFLAG^OVERLAPPED позволяет функциям, выпол- няющим операции чтения, записи и соединения, немедленно возвращать значение, даже если их выполнение требует существенных затрат времени. Перекрытый ввод-вывод мы подробно обсудим в разделе, посвященном раз- работке более сложного сервера. Последняя группа флагов — dwOpenMode (табл. 4-1), управляет доступом сервера к дескриптору безопасности, созданному именованным каналом. Их ьспользуют, если приложению требуется обновить или изменить дескрип- тор после создания канала. Флаг WRITE DAC позволяет приложению обнов- лять список избирательного управления доступом (discretionary access cont- rol list, DACL); флаг ACCESS SYSTEM_SECURITY — получить доступ к систем- ному списку управления доступом (system access control list, SACL); флаг WRITEJOWNER — изменять владельца именованного канала и групповой идентификатор безопасности (security ID, SID). Например, можно запре- тить доступ пользователя к именованному каналу, изменив список DACL ка- нала с помощью API-функций. Подробнее о DACL, SACL и SID — в главе 2. Параметр dwPipeMode определяет режимы операций чтения, записи и ожидания. При создании канала нужно указать по одному флагу из каждой категории, объединив их с помощью операции OR (табл. 4-2). Табл. 4-2. Флаги режимов чтения-записи именованного канала Режим Флаг Описание открытия Запись PIPE_TYPE BYTE Данные записываются в канал потоком байтов PIPE_TYPE MESSAGE Данные записываются в канал потоком сообщений Чтение PIPE_READMODE_BYTE Данные считываются из канала потоком байтов PIPE_READMODE_ Данные считываются из канала потоком MESSAGE сообщений Ожидание PIPEWAIT Включен режим блокировки PIPENOWAIT Отключен режим блокировки Если при создании побайтового канала использовались флаги PIPE_READ- MODEJBYTE | PIPE_TYPE_BYTE, то данные будут записываться и считываться из канала только потоком байтов. В этом случае не обязательно уравнове- шивать количество операций чтения и записи, поскольку данные не разделя- ются на отдельные сообщения. Например, если в канал записано 500 байт, по- лучатель может считывать по 100 байт, до тех пор пока не считает все данные. Чтобы сообщения имели четкие границы, создайте канал в режиме сооб- щений, указав флагиPIPE_READMODE_MESSAGE\PIPE_TYPE_MESSAGE. В этом случае необходимо уравновешивать количество операций чтения и записи Данных. Например, если в канал записано 500 байт, то для чтения данных Потребуется буфер размером 500 байт или более, иначе функция ReadFile в ыдаст ошибку ERROR_MORE_DAТА, Флаг PIPE_TYPE_MESSAGE можно комби- нировать с флагом PIPE_READMODE_BYTE. Это позволит отправлять данные 84 ЧАСТЬ I Устаревшие сетевые API в режиме сообщений, а при их получении — считывать произвольное коли- чество байтов. При таком способе передачи разделители сообщений игно- рируются. Флаг PIPEJTYPE BYTE нельзя использовать с флагом PIPEREAD- MODE_MESSAGE, иначе функция CreateNamedPipe выдаст ошибку ERROR_ INVALID PARAMETER, поскольку при таком способе записи поток данных не будет содержать разделителей сообщений. Флаги PIPE_WAIT и PIPE_NOWAIT переводят канал в блокирующий и не- блокирующий режим соответственно. Их можно комбинировать с флагами режима чтения и записи. В режиме блокировки такие операции ввода-выво- да, как ReadFile, блокируются, до тех пор пока запрос ввода-вывода не будет выполнен полностью. Такое поведение является стандартным, когда не ука- занно ни одного флага. Если режим блокировки отключен, операции ввода- вывода возвращают значение немедленно. Однако данный режим не следу- ет использовать для достижения асинхронного выполнения операций вво- да-вывода в приложениях Win32. Он обеспечивает лишь совместимость с ран- ними версиями Microsoft LAN Manager 2.0. Асинхронное выполнение функций ReadFile и WriteFile достигается с помощью перекрытого ввода-вывода. Параметр nMaxInstances определяет максимальное количество экземпля- ров (описателей) канала, которые одновременно может создать сервер. Эк- земпляр канала — это соединение локального или удаленного клиента с сер- вером именованного канала. Параметр может принимать значения от 1 до PIPEJJNLIMITEDJNSTANCES. Например, если сервер должен одновременно поддерживать до пяти соединений, присвойте параметру значение 5- Если параметр равен PIPEJJNLIMITEDJNSTANCES, количество экземпляров кана- ла ограничено только системными ресурсами. Параметры nOutBufferSize и nlnBufferSize функции CreateNamedPipe опре- деляют размеры входящего и исходящего внутренних буферов. При созда- нии экземпляра канала система каждый раз формирует входящий и (или) исходящий буфер, задействуя резидентный пул (физическая память, исполь- зуемая операционной системой). Размер буфера должен быть рациональ- ным: не слишком большим, чтобы система не исчерпала резидентный пул, и не слишком малым, чтобы вместить стандартные запросы ввода-вывода. При попытке записать данные большего размера, система попытается авто- матически расширить объем буфера, используя резидентный пул. Оптималь- ные размеры — те, которые приложение использует при вызове функций ReadFile и WriteFile. Параметр nDefaultTimeOut задает стандартный тайм-аут (время ожидания соединения) в миллисекундах. Действие параметра распространяется толь- ко на клиентские приложения, определяющие, можно ли установить соеди- нение с сервером с помощью функции WaitNamedPipe (подробней — далее в этой главе). Параметр ipSecurityAttributes позволяет приложению указать дескриптор безопасности именованного канала и определяет, сможет ли дочерний про- цесс наследовать созданный описатель. Если этот параметр равен NULL, име- нованный канал использует стандартный дескриптор безопасности, а опи- сатель не может быть унаследован. Стандартный дескриптор безопасности Г Л А В А 4 Именованные каналы 85 подразумевает, что именованный канал имеет те же права доступа, что и со- здавший его процесс (см. модель безопасности Windows NT и Windows 2000 в главе 2). Приложение может предоставить доступ к каналу определенным пользователям и группам, определив структуру SECURITY DESCRIPTOR с по- мощью API-функций. Чтобы открыть доступ к серверу любым клиентам, в структуре SECURITY^DESCRIPTOR следует задать пустой список DACL. Когда функция CreateNamedPipe вернет описатель именованного канала, сервер начинает ждать соединения клиентов. Для установления соединения предназначена функция ConnectNamedPipe, которая определена так: BOOL ConnectNamedPipe( HANDLE hNamedPipe, LPOVERLAPPED lpOverlapped Параметр hNamedPipe — это описатель экземпляра именованного кана- ла, возвращенный функцией CreateNamedPipe. Если при создании канала использовался флаг FILE_FLAG_OVERLAPPED, параметр lpOverlapped позволя- ет ConnectNamedPipe выполняться асинхронно, то есть без блокировки. Если этот параметр равен NULL, ConnectNamedPipe блокируется, до тех пор пока клиент не установит соединение с сервером. Вызов функции ConnectNamedPipe завершается после установления со- единения. Далее сервер отправляет и принимает данные с помощью API- функций WriteFile и ReadFile. Когда обмен данными с клиентом завершен, сервер должен закрыть соединение, вызвав функцию DisconnectNamedPipe. В листинге 4-1 показано, как написать простой сервер, способный обмени- ваться данными с одним клиентом. Листинг 4-1. Простой сервер именованного канала // Server.срр «include «include void main(void) HANDLE PipeHandle; DWORD BytesRead; CHAR buffer[256]; if ((PipeHandle = CreateNaraedPipe("\\\\-\\Pipe\\Jim", PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_READMODE_BYTE, 1,e 0, 0, 1000, NULL)) == INVALID_HANDLE_VALUE) printfC'CreateNamedPipe failed with error Xd\n", GetLastErrorQ); return; } см.спед.стр. 86 ЧАСТЬ I Устаревшие сетевые API Листинг 4-1. {продолжение) printf( Server is now runninfl\n ), if (ConnectNamedPipe(PipeHandle, NULL) == 0) { printf( ConnectNamedPipe failed with error Xd\n GetLastErrorO), CloseHandle(PipeHandle), return, if (ReadFile(PipeHandle, buffer, sizeof(buffer), &BytesRead, NULL) <= 0) { printf( ReadFile failed with error Xd\n , GetLastErrorO), CloseHandle(PipeHandle), return, p r i n t f ( % *s\n', BytesRead, b u f f e r ) , if (DisconnectNamedPipe(PipeHandle) == 0) { p r i n t f ( DisconnectNamedPipe f a i l e d with error Xd\n", GetLastErrorO), return, } CloseHandle(PipeHandle), Формирование пустого списка DACL (Null DACL) При использовании API-функций для создания таких объектов, как файлы и именованные каналы, Windows NT и Windows 2000 позволя- ют приложениям задавать права доступа с помощью структуры SE- CURITY _ATTRIBUTES typedef struct _SECURITY_ATTRIBUTES { DWORD nLength, LPVOID lpSecurityDescriptor, BOOL blnheritHandle } SECURITY_ATTRIBUTES, Поле lpSecurityDescriptor — указатель на структуру SECURITY_ DESC- RIPTOR, определяющую права доступа к объекту Структура SECURI- TY'_DESCRIPTOR содержит поле DACL со списком пользователей и групп, имеющих доступ к объекту Если это поле равно NULI, объект доступен любому пользователю Г Л А В А 4 Именованные каналы 87 Приложения не могут напрямую обращаться к структуре SECURITY^ DESCRIPTOR для этого существуют специальные API-функции Напри мер, чтобы создать пустой список DACL в структуре SECURITY_ DE- SCRIPTOR, необходимо сделать следующее 1 Создайте и инициализируйте структуру SECURITYJDESCRIPTOR с помощью API-функции InitiahzeSecuntyDescrtptor 2 Присвойте полю DACL значение NUIL с помощью функции Set- SecuntyDescnptorDacl Созданную структуру SECURITYJDESCRIPTOR необходимо помес- тить в структуру SECURITY_ATTRIBUTES, после чего ее можно исполь- зовать при вызове таких API-функций, как CreateNamedPipe И Создание объектов структур SECURITY.ATTRIBUTES и SECURITY.DESCRIPTOR SECURITY_ATTRIBUTES sa, SECURITY_DESCRIPTOR sd, // Инициализация нового объекта SECURITY_DESCRIPTOR для очистки значений if (ImtializeSecuntyDescriptor(&sd, SECURITY_DESCRIPT0R_REVISION) == 0) { printf( InitializeSecurityDescnptor failed with error Xd\n , GetLastErrorO), return, // Присвоение полю DACL структуры SECURITY_DESCRIPTOR значения NULL if (SetSecurityDescnptorDacl(&sd TRUE, NULL, FALSE) == 0) { printf( SetSecurityDescriptorDacl failed with error Xd\n', GetLastErrorO), return, // Назначение нового объекта SECURITY_DESCRIPTOR структуре SECURITY_ATTRIBUTES sa nLength = sizeof(SECURITY_ATTRIBUTES), sa lpSecurityDescriptor = &sd, sa blnhentHandle = TRUE, Усовершенствованный сервер каналов В листинге 4-1 приведен код сервера, обслуживающего только один экзем- пляр именованного канала При этом все API-вызовы выполняются синхрон- но, то есть каждый вызов ждет окончания предыдущего запроса ввода-выво- Да Чтобы позволить двум и более клиентам установить соединение, сервер Должен поддерживать несколько экземпляров именованного канала, макси- мальное число которых ограничено значением параметра nMaxInstances Функции CreateNamedPipe Несколько экземпляров канала можно создать с помощью потоков, либо еханизмов асинхронного ввода-вывода Win32, например перекрытого вво- 88 ЧАСТЬ I Устаревшие сетевые API да-вывода и портов завершения. Механизмы асинхронного ввода-вывода позволяют одновременно обслуживать все экземпляры канала из одного потока приложения. Рассмотрим, как создать более сложный сервер с помо- щью потоков и перекрытого ввода вывода (подробнее о портах заверше- ния — в главе 8). Потоки Разработать сложный сервер, способный поддерживать более одного соеди- нения с помощью потоков, не так уж трудно. Для каждого соединения необ- ходимо создать отдельный поток и работать с ним, как с простым сервером. В листинге 4-2 показан код сервера, обслуживающего пять экземпляров име- нованного канала. Это приложение — эхо-сервер, получающий данные от клиента и отправляющий их обратно. Листинг 4-2. Использование потоков при реализации сложного сервера именованного канала // Threads.срр «include «include «include «define NUM_PIPES 5 DWORD WINAPI PipeInstanceProc(LPVOID lpParameter); void main(void) HANDLE ThreadHandle; INT i; DWORD Threadld; for(i = 0; i < NUH_PIPES; m» // Создание потока для обслуживания каждого экземпляра именованного канала if ((ThreadHandle = CreateThread(NULL, 0, PipelnstanceProc, NULL, 0, &ThreadId)) == NULL) { printfC'CreateThread failed with error X\n", GetLastErrorO); return; } CloseHandle(ThreadHandle); p r i n t f ( " P r e s s 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 Функция Pipelns- tanceProc работает точно так же, как простой сервер (листинг 4-1) Сеанс связи завершается вызовом функции DisconnectNamedPipe, после чего при- ложение снова вызывает функцию ConnectNamedPtpe с тем же описателем, чтобы обслужить другого клиента Перекрытый ввод-вывод Это механизм асинхронного выполнения таких API-функций, как ReadFile и WnteFile Прежде всего в функцию необходимо передать структуру OVERLAPPED, которая впоследствии будет использована для получения результатов запроса ввода-вывода с помощью API-функции GetOverlappedResult Если функция вы- зывается с OVERLAPPED, результат возвращается немедленно Чтобы сервер обслуживал несколько экземпляров именованного канала с помощью перекрытого ввода-вывода, значение параметра nMaxInstances функции CreateNamedPipe должно быть больше 1, а параметр dwOpenMode — равен FILE_FLAG_OVERLAPPED На листинге 4-3 показан код сервера, обслу- живающего пять экземпляров именованного канала Это приложение — эхо- сервер, который получает данные от клиента и отправляет их обратно Листинг 4-3. Использование перекрытого ввода-вывода при реализации усовершенствованного сервера именованных каналов // Overlap cpp |