- •6.4. Внешние коммуникации
- •6.4.1. Протоколы тср/ip
- •6.4.1.1. Определение и достоинства протокола tcp/ip
- •6.4.1.2. Архитектура tcp/ip
- •6.4.1.3. Соответствие между моделями tcp/ip и iso osi
- •6.4.2. Протокол ip
- •6.4.2.1. Структура заголовка ip-пакета
- •Ip-датаграмма
- •6.4.2.2. Ip-адресация
- •190.50.1.1
- •190.50.1.2
- •190.50.2.1
- •190.50.2.2
- •6.4.3. Протокол udp
- •6.4.4. Протокол tcp
- •6.4.4.1. Структура tcp-сегмента
- •6.4.4.2. Этапы tcp-сеанса
- •6.4.5. Программные средства поддержки сетей
- •6.4.5.1. Состав программных средств поддержки сетей
- •1. Почтовые ящики
- •2. Именованные каналы
- •3. Удаленные вызовы процедур
- •4. Протокол NetBios
- •6.4.5.2. Программные средства работы с сокетами
- •6.4.6. Сетевое программное обеспечение уровня приложений
- •6.4.6.1. Протокол ftp
- •6.4.6.2. Протокол smtp
- •Заключение
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()) канал закрывается.
Здесь уместно рассказать более подробно об асинхронном вводе-выводе.
Основы асинхронного ввода-вывода
Существует два варианта ввода-вывода:
Синхронный ввод-вывод, при котором поток, вызвавший эту операцию, блокируется до завершения
Асинхронный ввод-вывод, при котором поток вызывает операцию ввода-вывода (ReadFile() или WriteFile()) и сразу же получает управление обратно. Операция ввода-вывода выполняется не зависимо от потока, но когда она завершается, поток узнает об этом с помощью дополнительных средств.
Существует три способа асинхронного ввода-вывода.
Синхронный ввод-вывод в отдельном потоке. Самый простой и многими рекомендуемый способ. Программа продолжает выполняться в своем потоке, а ожидание завершения ввода-вывода происходит в дополнительном потоке.
Ввод-вывод с перекрытием. В этом случае к операции ввода-вывода “привязывается” событие. Когда операция ввода-вывода начинается, процесс переходит к ожиданию этого события. При этом ожидание может выполняться без блокировки. Когда операция ввода-вывода завершается, событие взводится и сигнализирует процессу о своем завершении.
Ввод-вывод с процедурой завершения. К операции ввода-“привязывается” специальная процедура, которая вызывается операционной системой, когда операция завершается. В начале работы операционной системе надо передать адрес этой процедуры. Внутри процедуры можно взводить, например, некоторый флаг, установка которого говорит о том, что операция завершилась.
Про первый вариант говорить не будем, т.к. он использует традиционный вариант вызовов 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, // pointer to security attributes
BOOL bManualReset, // flag for manual-reset event
BOOL bInitialState, // flag for initial state
LPCTSTR lpName // pointer to event-object name
);
После выполнения операции ReadFile() необходимо перейти к ожиданию события OverLap.hEvent, сигнализирующего о завершении операции ввода-вывода.
WaitForSingleObject(OverLap.hEvent, TIME_OUT);
DWORD WaitForSingleObject(
HANDLE hHandle, // handle to object to wait for
DWORD dwMilliseconds // time-out interval in milliseconds
);
Значение параметра TIME_OUT может варьироваться от 0 до INFINITE. При значении 0 функция сразу же завершится.
DWORD result;
while (1) {
result = WaitForSingleObject(OverLap.hEvent, TIME_OUT);
if (WAIT_TIMEOUT == result){
// do something
continue;
}else if (WAIT_OBJECT_0 == result) {
break;
}
}
Когда операция ввода-вывода завершится, процесс выйдет из цикла по break-у, и тогда можно узнать количество реально переданных данных.
Это делается операцией:
DWORD bytesTrans;
GetOverlappedResult(hPipe, &OverLap, &bytesTrans, FALSE);
BOOL GetOverlappedResult(
HANDLE hFile, // handle to file, pipe, or comm device
LPOVERLAPPED lpOverlapped, // pointer to overlapped structure
LPDWORD lpNumberOfBytesTransferred, // pointer to actual bytes count
BOOL bWait // wait flag
);
Т.е. в буфере buffer находится bytesTrans вновь полученных байтов.
Подводя итог, перечислим этапы ввода-вывода с перекрытием:
Создать объект с флагом: FILE_FLAG_OVERLAPPED;
Создать структуру перекрытия: OVERLAPPED OverLap;
Создать событие в этой структуре: OverLap.hEvent = CreateEvent(…);
Вызвать функцию ввода-вывода: ReadFile(…, &OverLap);
Перейти к ожиданию события: WaitForSingleObject(OverLap.hEvent);
При наступлении события узнать количество принятых байтов вызовом GetOverlappedResult()
В заключении материала об асинхронном вводе-выводе следует отметить сложность реализации такого способа ввода-вывода.