Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
os5.doc
Скачиваний:
0
Добавлен:
01.07.2025
Размер:
494.08 Кб
Скачать

2. Именованные каналы

Именованные каналы предоставляют приложениям надежную двухстороннюю связь.

Вопрос (

Именованный канал создается вызовом:

NP = CreateNamedPipe("\\\\.\\pipe\\mypipe",

PIPE_ACCESS_DUPLEX,

PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,

1,

0,

0,

INFINITE,

NULL);

Приложение, создавшее такой канал, становится сервером.

)Вопрос

Вопрос (

Сервер переходит в режим ожидания запросов клиента с помощью вызова:

ConnectNamedPipe(NP, NULL);

Поскольку вызов функции ConnectNamedPipe() приводит к ожиданию процесса, то лучше ее вызывать в отдельном потоке.

Клиент подключается к каналу с помощью вызова:

NP = CreateFile("\\\\pc_name\\pipe\\mypipe",

GENERIC_READ|GENERIC_WRITE,

0,

NULL,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

NULL);

)Вопрос

Вопрос (

После того, как соединение между сервером и клиентом будет установлено, они могут обмениваться данными с помощью вызовов ReadFile() и WriteFile(), аналогично предыдущему случаю.

Аналогичным же образом (CloseHandle()) канал закрывается.

)Вопрос

Здесь уместно рассказать более подробно об асинхронном вводе-выводе.

Основы асинхронного ввода-вывода

Вопрос (

Существует два варианта ввода-вывода:

  1. Синхронный ввод-вывод, при котором поток, вызвавший эту операцию, блокируется до завершения ввода/вывода.

  2. Асинхронный ввод-вывод, при котором поток вызывает операцию ввода-вывода (ReadFile() или WriteFile()) и сразу же получает управление обратно. Операция ввода-вывода выполняется не зависимо от потока, но когда она завершается, поток узнает об этом с помощью дополнительных средств.

)Вопрос

Вопрос (

Существует три способа асинхронного ввода-вывода.

  1. Синхронный ввод-вывод в отдельном потоке. Самый простой и многими рекомендуемый способ. Программа продолжает выполняться в своем потоке, а ожидание завершения ввода-вывода происходит в дополнительном потоке.

  2. Ввод-вывод с перекрытием. В этом случае к операции ввода-вывода “привязывается” событие. Когда операция ввода-вывода начинается, процесс переходит к ожиданию этого события. При этом ожидание может выполняться без блокировки. Когда операция ввода-вывода завершается, событие взводится и сигнализирует процессу о своем завершении.

  3. Ввод-вывод с процедурой завершения. К операции ввода-“привязывается” специальная процедура, которая вызывается операционной системой, когда операция завершается. В начале работы операционной системе надо передать адрес этой процедуры. Внутри процедуры можно взводить, например, некоторый флаг, установка которого говорит о том, что операция завершилась.

)Вопрос

Про первый вариант говорить не будем, т. к. он использует традиционный вариант вызовов ReadFile() и WriteFile().

Про третий вариант поговорим, когда будем рассматривать протокол NetBIOS.

На данном этапе рассмотрим второй вариант, при этом для конкретности рассмотрим его применительно к именованным каналам.

Первое, что нужно сделать, это создавать объект (в данном случае, именованный канал) с флагом FILE_FLAG_OVERLAPPED.

NP = CreateNamedPipe("\\\\.\\pipe\\mypipe",

PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,...

В операции ввода-вывода последним параметром передается указатель на специальную структуру данных, которая называется структурой перекрытия.

OVERLAPPED OverLap;

Обычно в качестве последнего параметра передается NULL, что указывает на синхронный ввод-вывод.

ReadFile(hPipe, buffer, BUF_SIZE, &NumberOfBytesRead, &OverLap);

Если такая структура передана в вызов, то операция ReadFile() завершается сразу же. Т. е. как бы дается команда “начать ввод-вывод”. За состоянием ввода-вывода можно следить теперь, получая информацию из этой структуры.

Как выглядит структура перекрытия:

typedef struct _OVERLAPPED

DWORD Internal; //резервируется ОС

DWORD InternalHigh; //резервируется ОС

DWORD Offset; //используется при вводе-выводе с дисковыми файлами

DWORD OffsetHigh; //используется при вводе-выводе с дисковыми файлами

HANDLE hEvent; //ссылка на событие

} OVERLAPPED

Для коммуникационных устройств типа COM-портов и именованных каналов большинство параметров не используются.

Для нас важно, что в эту структуру входит событие hEvent.

Событие надо создать вызовом:

OverLap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes, //указатель на атрибуты безопасности

BOOL bManualReset, // флаг события ручного сброса

BOOL bInitialState, // флаг начального состояния

LPCTSTR lpName // указатель на имя объекта-события

);

После выполнения операции ReadFile() необходимо перейти к ожиданию события OverLap.hEvent, сигнализирующего о завершении операции ввода-вывода.

WaitForSingleObject(OverLap.hEvent, TIME_OUT);

DWORD WaitForSingleObject(

HANDLE hHandle, // ссылка на объект ожидания

DWORD dwMilliseconds // интервал тайм-аута в ms

);

Значение параметра TIME_OUT может варьироваться от 0 до INFINITE. При значении 0 функция сразу же завершится.

DWORD result;

while (1) {

result = WaitForSingleObject(OverLap.hEvent, TIME_OUT);

if (WAIT_TIMEOUT == result){

// делать что-то

continue;

}else if (WAIT_OBJECT_0 == result) {

break;

}

}

Когда операция ввода-вывода завершится, процесс выйдет из цикла по break-у, и тогда можно узнать количество реально переданных данных.

Это делается операцией:

DWORD bytesTrans;

GetOverlappedResult(hPipe, &OverLap, &bytesTrans, FALSE);

BOOL GetOverlappedResult(

HANDLE hFile, //ссылка на файл, канал или com-устройство

LPOVERLAPPED lpOverlapped, //указатель на структуру перекрытия

LPDWORD lpNumberOfBytesTransferred, //указатель на число пртнятых байтов

BOOL bWait //флаг ожидания

);

Т.е. в буфере buffer находится bytesTrans вновь полученных байтов.

Пример потока, выполняющего операцию чтения с перекрытием, представлен ниже:

UINT ReadThread(LPVOID pParam)

{

char buffer[IN_BUF_SIZE];

DWORD NumberOfBytesRead;

DWORD bytesTrans;

OVERLAPPED OverLapRd;

memset(&OverLapRd,0,sizeof(OVERLAPPED));

OverLapRd.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

while (1) {

memset(buffer,0,IN_BUF_SIZE);

if (!ReadFile(hPipe, buffer, IN_BUF_SIZE, &NumberOfBytesRead, &OverLapRd)) {

if (GetLastError() == ERROR_IO_PENDING) {//операция не завершена

WaitForSingleObject(OverLapRd.hEvent, INFINITE);

}else{

AfxMessageBox("ReadFile error!");

return 0;

}

}

GetOverlappedResult(hPipe, &OverLapRd, &bytesTrans, FALSE);

pReadEdit->SetWindowText(buffer);

}

return 0;

}

Вопрос (

Подводя итог, перечислим этапы ввода-вывода с перекрытием:

  1. Создать объект с флагом: FILE_FLAG_OVERLAPPED;

  2. Создать структуру перекрытия: OVERLAPPED OverLap;

  3. Создать событие в этой структуре: OverLap.hEvent = CreateEvent(…);

  4. Вызвать функцию ввода-вывода: ReadFile(…, &OverLap);

  5. Перейти к ожиданию события: WaitForSingleObject(OverLap.hEvent);

  6. При наступлении события узнать количество принятых байтов вызовом GetOverlappedResult()

Вопрос (

В заключении материала об асинхронном вводе-выводе следует отметить сложность реализации такого способа ввода-вывода.

Основы безопасности объектов

Еще одна проблема обременяет использование именованных каналов, а именно, проблема безопасности.

Если создать именованный канал вызовом:

CreateNamedPipe(..., NULL);

Т.е. если последним параметром этой функции передать NULL, то обмен данными между сервером и клиентом, расположенными на разных машинах, производится не будет.

Дело в том, что последний параметр этой функции (как и почти всех функций, начинающихся с префикса “Create”) представляет собой указатель на атрибут безопасности, на структуру данных, которая содержит информацию, позволяющую регламентировать доступ к объекту.

Атрибут безопасности содержит три параметра, важнейшим из которых является указатель на дескриптор безопасности. Именно эта структура и содержит права доступа к объекту.

Дескриптор безопасности содержит информацию о следующих компонентах безопасности объекта:

  1. Идентификатор безопасности владельца (owner sid)

  2. Идентификатор безопасности группы (group sid)

  3. Собственный список доступа (DACL)

  4. Системный список доступа (SACL)

Списки доступа могут быть проинициализированы вызовом, затем в них могут быть добавлены элементы (sid и права (разрешающие и запрещающие)).

Затем список может быть добавлен либо к SACL либо к DACL.

С другой стороны каждый процесс привязан к т. н. маркеру доступа, который описывает контекст безопасности процесса. Система использует этот маркер, когда процесс обращается к объекту со специфицированными атрибутами безопасности.

Система в этом случае ищет SID, содержащийся в маркере доступа, среди SID списков доступа объекта, и действует соответственно правам, привязанным к данному SID, - разрешает заявленную операцию или отказывает в ней.

Чтобы использовать дескриптор безопасности объекта, надо выделить под него память, а затем проинициализировать его. Пример вызова представлен ниже:

PSECURITY_DESCRIPTOR pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR,SECURITY_DESCRIPTOR_MIN_LENGTH);

InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION);

Где PSECURITY_DESCRIPTOR pSD; - указатель на дескриптор безопасности.

/************************************************************************************

Затем необходимо найти идентификатор безопасности пользователя, который будет владеть объектом.

Это делается по имени пользователя следующим образом:

LookupAccountName(NULL, buffer, pSid, &cbSid, pDomainName, &cbDomainName, &eUse)

Где buffer содержит имя пользователя;

pSid – это указатель на идентификатор беопасности;

cbSid – размер идентификатора;

pDomainName – домен, в котором находится имя пользователя;

cbDomainName – размер строки, описывающей домен;

eUse – тип идентификатора.

Затем идентификатор безопасности привязывается к дескриптору безопасности. Это делается вызовом:

BOOL SetSecurityDescriptorOwner(

PSECURITY_DESCRIPTOR pSecurityDescriptor, // адрес дескриптора безопасности

PSID pOwner, // адрес SID владельца

BOOL bOwnerDefaulted // флаг умолчания

);

То же самое делается и для группы, владеющей объектом.

Затем создается список контроля доступа:

BOOL InitializeAcl(

PACL pAcl, // адрес списка контроля доступа

DWORD nAclLength, // размер списка контроля доступа

DWORD dwAclRevision // уровень редакции списка контроля доступа

);

и в него вносятся элементы, либо позволяющие операции определенному пользователю (через его sid, найденный функцией LookupAccountName), либо запрещающие какие-либо операции:

BOOL AddAccessAllowedAce(

PACL pAcl, // адрес списка контроля доступа

DWORD dwAceRevision, // уровень редакции списка контроля доступа

DWORD AccessMask, // маска доступа

PSID pSid // адрес идентификатора безопасности

);

BOOL AddAccessDeniedAce(

PACL pAcl, // адрес списка контроля доступа

DWORD dwAceRevision, // уровень редакции списка контроля доступа

DWORD AccessMask, // маска доступа

PSID pSid // адрес идентификатора безопасности

);

На последнем этапе заполненный список заносится в дескриптор безопасности:

BOOL SetSecurityDescriptorDacl(

PSECURITY_DESCRIPTOR pSecurityDescriptor, // адрес дескриптора безопасности

BOOL bDaclPresent, // флаг присутствия собственного списка ACL

PACL pDacl, // адрес собственного списка ACL

BOOL bDaclDefaulted // флаг собственного списка ACL по умолчанию

);

********************************************************************************************/

Если указатель на список контроля доступа будет равен NULL, то будет использован список (NULL discretionary ACL), позволяющий полный доступ к объекту:

SetSecurityDescriptorDacl(pSD, TRUE, (PACL)NULL, FALSE);

Т.е. этапы, отмеченные /*** ... ***/, могут быть опущены, если нет необходимости регламентировать для различных пользователей доступ к объекту.

Дескриптор безопасности вносится в атрибут безопасности, а указатель на атрибут безопасности передается в качестве параметра в функцию создания объекта.

SECURITY_ATTRIBUTES sa;

sa.nLength = sizeof(sa);

sa.lpSecurityDescriptor = pSD;

sa.bInheritHandle = TRUE;

hPipe = CreateNamedPipe ( ..., &sa);

Важно отметить, что отсутствие атрибута безопасности (NULL вместо &sa) и использование NULL-списка контроля доступа приводят к противоположным результатам.

В первом случае доступ полностью отсутствует, а во втором случае предоставляется полный доступ.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]