Главная страница

Программирование в сетях Windows. Э. Джонс, Д. Оланд


Скачать 2.88 Mb.
НазваниеЭ. Джонс, Д. Оланд
АнкорПрограммирование в сетях Windows.pdf
Дата12.10.2017
Размер2.88 Mb.
Формат файлаpdf
Имя файлаПрограммирование в сетях Windows.pdf
ТипКнига
#9346
страница22 из 50
1   ...   18   19   20   21   22   23   24   25   ...   50
Листинг 8-5. (продолжение)
II Сокет wParam готов отправлять данные break;
case FD_CLOSE:
// Закрытие соединения
closesocket(wParam);
break;
break;
return TRUE;
}
При обработке уведомления о событии FDWRITE важно следующее. Это уведомление отправляется только в одном из трех случаев:
И после подключения к сокету функцей connect win WSAConnect;
Ж после приема соединения функцией accept или WSAAccept;
Ш когда функции send, WSASend, sendto или WSASendTo возвращают ошибку
WSAEWOULDBLOCK и место в буфере освобождается.
Поэтому приложение должно полагать, что запись в сокет возможна, на- чиная с первого уведомления FDWRITE и до тех пор, пока send, WSASend,
sendto или WSASendTo не вернет ошибку WSAEWOULDBLOCK. После этого нуж- но ждать следующего уведомления FD_WRITE, извещающего, что запись в сокет снова возможна.
Модель WSAEventSelect
В Winsock поддерживается еще одна полезная модель асинхронного ввода- вывода, позволяющая получать уведомления о сетевых событиях на сокетах.
Эта модель похожа на WSAAsyncSelect — приложение получает и обрабаты- вает те же события. Но есть и отличие — сообщения отправляются описате- лю объекта «событие», а не процедуре окна.
Уведомления о событиях
Модель WSAEventSelect требует, чтобы приложение создало объект «событие»
Для каждого сокета, вызвав функцию WSACreateEvent:
WSAEVENT WSACreateEvent(void);
Она возвращает описатель объекта «событие». Получив описатель объек- а
> нужно связать его с сокетом и зарегистрировать те типы сетевых событий,
которые надо отслеживать (см. список в разделе «Модель WSAAsyncSelect. Уве- домления о сообщениях»). Это достигается вызовом функции WSAEventSelect-
l n t
WSAEventSelect(
SOCKET s ,
l W ( i a i J
WSAEVENT hEventObject, "'
i и программирования winsock
long INetworkEvents
);
Здесь параметр 5 — интересующий нас сокет, а параметр hEventObject —
объект «событие», полученный из вызова WSACreateEvent, который нужно связать с сокетом. Последний параметр INetworkEvents — битовая маска получаемая комбинацией масок типов сетевых событий, которые надо от- слеживать. Подробно эти типы обсуждались при описании предыдущей мо- дели — WSAAsyncSelect.
У события, используемого в модели WSAEventSelect, два рабочих состояния
свободное (signaled) и занятое (nonsignaled), а также два оперативных режи- ма —ручного (manual) и автоматического сброса (auto reset). Первоначально событие создается в занятом состоянии и режиме ручного сброса. Когда на сокете происходит сетевое событие, связанный с эти событием объект стано- вится занятым. Так как объект события создается в режиме ручного сброса,
приложение ответственно за его возврат в занятое состояние после обработ- ки ввода-вывода. Это можно сделать, вызвав функцию WSAResetEvenP.
BOOL WSAResetEvent(WSAEVENT hEvent);
Она принимает описатель события в качестве единственного параметра и возвращает TRUE или FALSE, в зависимости от успешности вызова. Закончив работу с объектом события, приложение должно вызвать функцию WSAClo-
seEvent для освобождения системных ресурсов, используемых объектом:
BOOL WSACloseEvent(WSAEVENT hEvent);
Эта функция также принимает описатель события в качестве единствен- ного параметра и возвращает TRUE или FALSE, в зависимости от успешнос- ти вызова.
Сопоставив сокет описателю события, приложение может начать обра- ботку ввода-вывода, ожидая изменения состояния описателя. Функция WSA-
WaitForMultipleEvents будет ожидать сетевое событие и вернет управление,
когда один из заданных описателей объектов событий перейдет в свободное состояние или когда истечет заданный таймаут.
DWORD WSAWaitForMultipleEvents(
DWORD cEvents,
const WSAEVENT FAR * lphEvents,
BOOL fWaitAll,
DWORD dwTimeout,
BOOL fAlertable
);
Здесь параметры cEvents и lphEvents определяют массив объектов типа
WSAEVENT, в котором cEvents — количество элементов, a lphEvents — указа- тель на массив. Функция WSAWaitForMultipleEvents поддерживает не более
WSA_MAXIMUM_WAIT_EVENTS (64) объектов событий. Поэтому данная модель ввода-вывода способна одновременно обслуживать максимум 64 сокета для каждого потока, вызывающего WSAWaitForMultipleEvents.

Г Л А В А 8 Ввод-вывод в Wmsock 219
Если необходимо обслуживать больше сокетов, создайте дополнительные бочие п о т о к и для дополнительных объектов событий. Параметр JWaitAll
о п р е д
е л я е т , как функция WSAWaitForMultipleEvents реагирует на события. Если о
н равен TRUE, функция завершается после освобождения всех событий,
перечисленных в массиве iphEvents. Если же FALSE — функция завершится,
как только будет свободен любой объект события. В последнем случае воз- вращаемое значение показывает, какой именно объект был свободен.
Как правило, приложения присваивают этому параметру FALSE и обраба- тывают одно событие сокета за раз. Параметр dwTimeout указывает, сколь- ко миллисекунд функция должна ожидать сетевого события. Когда истекает таймаут, функция завершается, даже если не выполнены условия, определен- ные параметром JWaitAll. Если таймаут равен 0, функция проверяет состоя- ние заданных объектов и выходит немедленно, что позволяет приложению эффективно проверить все события. Задавать нулевой таймаут не рекомен- дуется из соображений быстродействия. Если нет событий для обработки,
функция WSAWaitForMultipleEvents возвращает значение WSA_WAIT_TIMEOUT.
Если параметр divsTimeout равен WSAJNF1NITE, функция закончит работу только после освобождения какого-либо события. Последним параметром —
fAlertable, можно пренебречь в модели WSAEventSelect, присвоив ему FALSE: он применяется в процедурах завершения процессов в модели перекрытого ввода-вывода, которая описана далее.
Функция WSAWaitForMultipleEvents, получив уведомление о сетевом собы- тии, возвращает значение, определяющее его исходный объект. Найдя собы- тие в массиве событий и связанный с ним сокет, приложение может опре- делить, событие какого типа произошло на конкретном сокете. Для опреде- ления индекса события в массиве IphEvents нужно вычесть из возвращаемо- го значения константу WSA_WAIT_EVENTJ):
Index = WSAWaitForMultipleEvents(...);
MyEvent = EventArraydndex - WSA_WAIT_EVENT_O];
Выяснив сокет, на котором произошло событие, определяют доступные сетевые события, вызвав функцию WSAEnumNetworkEvents-.
int WSAEnumNetworkEvents(
SOCKET s,
WSAEVENT hEventObject,
LPWSANETWORKEVENTS lpNetworkEvents
Параметр s — сокет, на котором произошло сетевое событие. Необяза- тельный параметр hEventObject — описатель связанного события, которое нужно сбросить. Так как событие в этот момент находится в свободном со- стоянии, можно передать его описатель для перевода в занятое состояние,
ли
Н е желательно использовать параметр hEventObject, используйте функ-
Чию WSAResetEvent:
s t r u c t _WSANETWORKEVENTS
интерфейс прикладного программирования Wmsock long INetworkEvents;
int iErrorCode[FD_MAX_EVENTS];
} WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
Последний параметр — ipNetworkEvents, принимает указатель на структуру
WSANETWORKEVENTS, в которой передается тип произошедшего события и код ошибки. Параметр INetworkEvents определяет тип произошедшего события.
ПРИМЕЧАНИЕ При освобождении события иногда генерируется не- сколько типов сетевых событий. Например, интенсивно используемый сервер может одновременно получить сообщения FDJREAD и FDJWRITE.
Параметр iErrorCode — массив кодов ошибок, связанных с событиями из массива INetworkEvents. Для каждого типа сетевого события существует ин- декс события, обозначаемый тем же именем с суффиксом BIT. Например,
для типа события FDJREAD идентификатор индекса в массиве iErrorCode
обозначается FDJREADJ3IT. Вот анализ кода ошибки для события FD READ:
II Обработка уведомления FD_READ
if (NetworkEvents.INetworkEvents & FD_READ)
{
if (NetworkEvents.iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error Xd\n",
NetworkEvents.iErrorCode[FD_READ_BIT]);
После обработки событий, описанных в структуре WSANETWORKEVENTS,
приложение может продолжить ожидание сетевых событий на доступных сокетах. В листинге 8-6 показано применение модели WSAEventSelect для программирования сервера и управления событиями. Выделены обязатель- ные этапы, лежащие в основе программирования сервера, способного обслу- живать несколько сокетов одновременно.
Листинг 8-6. Пример сервера в модели ввода-вывода WSAEventSelect
SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];
WSAEVENT Event[WSA_MAXIMUM_WAIT_EVENTS];
SOCKET Accept, Listen;
DWORD EventTotal = 0;
DWORD Index;
// Настройка ТСР-сокета для прослушивания порта 5150
Listen = socket (PF_INET, SOCK_STREAM, 0);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bmd(Listen, (PSOCKADDR) MnternetAddr,
Sizeof(InternetAddr));
ыГЧ
*

Г Л А В А 8 Ввод-вывод в Winsock
221
Листинг 8-6. (продолжение)
NewEvent = WSACreateEventO;
WSAEventSelect(Listen, NewEvent,
FD_ACCEPT | F0_CL0SE);
listen(Listen, 5);
Socket[EventTotal] = Listen;
Event[EventTotal] = NewEvent;
EventTotal++;
while(TRUE)
// Ожидание события на всех сокетах
Index = WSAWaitForMultipleEvents(EventTotal,
EventArray, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(
SocketArray[Index - WSA_WAIT_EVENT_O],
EventArray[Index - WSA_WAIT_EVENT_O],
&NetworkEvents);
// Проверка наличия сообщений FD_ACCEPT
if (NetworkEvents.lNetworkEvents & FD_ACCEPT)
if (NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0)
printf("FD_ACCEPT failed with error Xd\n",
Netwo rkEvents.iE r ro rCode[FD_ACCEPT_BIT]);
break;
// Прием нового соединения и добавление его
// в списки сокетов и событий
Accept = accept(
SocketArray[Index - WSA_WAIT_EVENT_O],
NULL, NULL);
// Мы не можем обрабатывать более
// WSA_MAXIMUM_WAIT_EVENTS сокетов,
// поэтому закрываем сокет
if (EventTotal > WSA_MAXIMUM_WAIT_EVENTS)
{
printf("Too many connections");
closesocket(Accept);
break;
см. след. стр.
прикладного программирования Winsock
Листинг 8-6. (продолжение)
NewEvent = WSACreateEventO;
WSAEventSelect(Accept, NewEvent,
FD_READ | FD.WRITE | FD_CLOSE);
Event[EventTotal] = NewEvent;
Socket[EventTotal] = Accept;
EventTotal++;
printf("Socket Xd connected\n", Accept);
// Обработка уведомления FD_READ
if (NetworkEvents.lNetworkEvents & FD.READ)
{
if (NetworkEvents iErrorCode[FD_READ_BIT] != 0)
{
printf("FD_READ failed with error Xd\n",
NetworkEvents.iErrorCode[FD_READ_BIT]);
break;
// Чтение данных из сокета
recv(Socket[Index - WSA_WAIT_EVENT_O],
buffer, sizeof(buffer), 0);
// Обработка уведомления FD_WRITE
if (NetworkEvents INetworkEvents & FD.WRITE)
{
if (NetworkEvents.iErrorCode[FD_WRITE_BIT] != 0)
{
pnntf("FD_WRITE failed with error JSd\n",
NetworkEvents.iErrorCode[FD_WRITE_BIT]);
break; )
f
} й% i xetml 3,-
',i
send(Socket[Index - WSA_WAIT_EVENT_O],
buffer, sizeof(buffer), 0);
if (NetworkEvents.lNetworkEvents & FD.CLOSE)
{
if (NetworkEvents.iErrorCode[FD_CLOSE_BIT] 1 = 0 ) vT
< Jot
printf("FD_CLOSE f a i l e d with error Xd\n",
NetworkEvents. iErrorCode[FD_CLOSE_BIT]);
break;
}

Г Л А В А 8 Ввод-вывод в Wmsock 223
Листинг 8-6. (продолжение)
closesocket(Socket[Index - WSA_WAIT_EVENT_O]),
// Удаление сокета и связанного события
// из массивов сокетов (Socket) и событий (Event);
// уменьшение счетчика событий EventTotal
CompressArrays(Event, Socket, &EventTotal);
Модель перекрытого ввода-вывода
Эта модель Winsock более эффективна, чем рассмотренные ранее. С ее по- мощью приложение может асинхронно выдать несколько запросов ввода- вывода, а затем обслужить принятые запросы после их завершения Модель доступна на всех платформах Windows, кроме Windows СЕ Схема ее рабо- ты основана на механизмах перекрытия ввода-вывода в Win32, доступных для выполнения операций ввода-вывода на устройствах с помощью функций
ReadFile и WrtteFtle.
Первоначально модель перекрытого ввода-вывода была доступна для при- ложений Winsock 1 1 только в Windows NT Приложения могли использовать эту модель, вызывая функции ReadFile и WnteFile для описателя сокета и ука- зывая структуру перекрытия (описана далее) Начиная с версии Winsock 2,
модель перекрытого ввода-вывода встроена в новые функции Winsock, такие как WSASend и WSARecv, и теперь доступна на всех платформах, где работает
Winsock 2.
ПРИМЕЧАНИЕ Winsock 2 позволяет использовать перекрытый ввод- вывод с функциями ReadFile и WriteFile в Windows NT и 2000. Впрочем,
эта функциональность не поддерживается для Windows 9x. Из сообра- жений переносимости и производительности, применяйте функции
WSARecv и WSASend, а не ReadFile и WriteFile Мы рассмотрим лишь ис- пользование модели перекрытого ввода-вывода с новыми функциями
Winsock 2
Чтобы задействовать модель перекрытого ввода-вывода для сокета, со- здайте сокет с флагом WSA_FLAG_OVERLAPPED:
s = WSASocket(AF_INET, SOCK.STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED);
Если вы создаете сокет функцией socket, а не WSASocket, флаг WSA_FLAG_
VERLAPPED задается неявно Создав сокет и привязав его к локальному ин- терфейсу, можно использовать перекрытый ввод-вывод, вызвав следующие
Функции Winsock. WSASend, WSASendTo, WSARecv, WSARecvFrom, WSAIoctl,
cceptEx, TransmitFile. Следует также указать необязательную структуру
WSAOVERLAPPED.
ак В Ь 1
, возможно, уже знаете, каждая из этих функций связана с приемом,
и П е редачей данных, или установлением соединения на сокете Их выпол-

2 2 4 ЧАСТЬ II Интерфейс прикладного программирования Wmsock нение потребует много времени, поэтому каждая функция может принимать структуру WSAOVERLAPPED в качестве параметра. Тогда они завершаются немедленно, даже если сокет работает в блокирующем режиме. В этом слу- чае функция полагается на структуру WSAOVERLAPPED для завершения зап- роса ввода-вывода. Есть два способа завершения запросов перекрытого вво- да-вывода: приложение может ожидать уведомления от объекта «событие*
(event object notification) или обрабатывать завершившиеся запросы процеду-
рами завершения (completion routines). У всех перечисленных функций (кро- ме AcceptEx) есть еще один общий параметр — ipCompletionROUTINE. Это нео- бязательный указатель на процедуру завершения, которая вызывается при за- вершении запроса перекрытого ввода-вывода. Сначала рассмотрим способ уведомления о событиях, а затем — использование процедур завершения.
Уведомление о событии
Для использования уведомления о событии в модели перекрытого ввода-вывода необходимо сопоставить объекты события со структурами WSAOVERLAPPED.
Когда функции ввода-вывода, такие как WSASend и WSARecv, вызываются со структурой WSAOVERLAPPED в качестве параметра, они завершаются немедлен- но. В большинстве случаев эти вызовы возвращают ошибку SOCKET
r
_ERROR При этом функция WSAGetLastError возвращает значение WSA_IO_PENDING. Это про- сто означает, что операция ввода-вывода продолжается. Приложению требуется определить, когда завершится перекрытый ввод-вывод. Об этом сообщает со- бытие, связанное со структурой WSAOVERLAPPED. Структура WSAOVERLAPPED
осуществляет связь между началом запроса ввода-вывода и его завершением:
typedef struct WSAOVERLAPPED
DWORD
DWORD
DWORD
DWORD
Internal;
InternalHigh;
Offset;
OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
Поля Internal, InternalHigh, Offset и OffsetHigh используются системой и не должны задаваться приложением. Поле hEvent позволяет приложению свя- зать описатель объекта «событие» с сокетом. Этот описатель создают, выз- вав функцию WSACreateEvent, как и в модели WSAEventSelect. После создания описателя события достаточно присвоить его значение полю hEvent струк- туры WSAOVERLAPPED, после чего можно вызывать функции Winsock, ис- пользующие структуры перекрытой модели, такие как WSASend или WSARecv.
Когда запрос перекрытого ввода-вывода завершится, приложение долж- но извлечь его результаты. При уведомлении посредством событий, по завер- шении запроса Winsock освобождает объект «событие», связанный со струК"
турой WSAOVERLAPPED. Так как описатель этого объекта содержится в струк- туре WSAOVERLAPPED, завершение запроса перекрытого ввода-вывода легко определить, вызвав функцию WSAWaitForMultipleEvents, описанную в в посвященном модели WSAEventSelect.

Г Л А В А 8 Ввод-вывод в Winsock 225
Эта функция ждет указанное время, пока не освободится одно или не- колько событий. Напомним еще раз, что функция WSAWaitForMultipleEvents
способна одновременно обрабатывать не более 64 объектов. Выяснив, какой запрос перекрытого ввода-вывода завершился, нужно проверить, успешен ли вызов, функцией WSAGetOverlappedResult:
BOOL WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORO lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
Параметры 5 — сокет, и lpOverlapped — указатель на структуру WSAOVER-
LAPPED, переданы в запросе перекрытого ввода-вывода. Параметр ipcbTran-
ф
г
_ указатель на переменную типа DWORD, куда записывается количество байт, фактически перемещенных операцией перекрытого ввода-вывода. Па- раметр JWait определяет, должна ли функция ждать завершения операции.
Если он равен TRUE, функция не завершится до завершения операции, если
FALSE и операция еще не завершилась, — функция WSAGetOverlappedResult
вернет FALSE с ошибкой WSA_lO_Ih'COMPLETE. Так как мы ожидаем освобож- дения события для завершения операции ввода-вывода, этот параметр не ва- жен. Последний параметр — lpdwFlags, указатель на DWORD, куда будут запи- саны результирующие флаги, если исходный перекрытый вызов осуществ- лялся функцией WSARecv или WSARecvFrom.
Если функция WSAGetOverlappedResult завершилась успешно, она возвра- щает TRUE. Это означает, что запрос перекрытого ввода-вывода успешен и значение, на которое ссылается lpcbTransfer обновлено. Значение FALSE воз- вращается в следующих случаях:
Ш операция перекрытого ввода-вывода продолжается;
• операция завершена, но с ошибками;
* функция WSAGetOverlappedResult не может определить состояние опера- ции из-за ошибок в параметрах.
В случае неудачи значение по указателю lpcbTransfer не обновляется и приложение должно вызвать функцию WSAGetLastError, чтобы определить причины неудачи.
В листинге 8-7 показана структура простого серверного приложения,
Управляющего перекрытым вводом-выводом на одном сокете с использова- нием уведомлений о событиях. Выделим следующие этапы.
• Создание сокета и ожидание соединения на указанном порте.
• Прием входящего соединения.
3- Создание структуры WSAOVERLAPPED для сокета, привязка описателя объек- та события к этой структуре, а также к массиву событий, который будет ис- пользоваться позднее функцией WSAWaitForMultipleEvents.

I № I D и п и i е р ф е и с прикладного программирования Winsock
4. Асинхронный запрос WSARecv на сокет со структурой WSAOVERLAPPED в качестве параметра.
ПРИМЕЧАНИЕ Как правило, эта функция возвращает ошибку SOC-
KETJERROR со статусом WSA_1O_PENDING.
5. Вызов функции WSAWaitForMultipleEvents с использованием массива собы- тий и ожидание освобождения события, связанного с запросом перекры- того ввода-вывода.
6. По завершении WSAWaitForMultipleEvents — сброс события функцией WSA-
ResetEvent с массивом событий и обработка результатов запроса.
7. Определение состояния запроса перекрытого ввода-вывода функцией
WSAGetOverlappedResult.
8. Отправка нового запроса перекрытого ввода-вывода WSARecv на сокет.
9. Повтор шагов 5-8.
Этот пример легко расширить для обработки нескольких сокетов: выде- лите в отдельный поток части кода, обрабатывающего перекрытый ввод- вывод и разрешите главному потоку приложения обслуживать дополнитель- ные запросы соединения.
Листинг 8-7. Перекрытый ввод-вывод с использованием уведомлений
о событиях
void main(void)
{
WSABUF DataBuf;
DWORD EventTotal = 0;
_
e
WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
WSAOVERLAPPED AcceptOverlapped;
SOCKET ListenSocket, AcceptSocket;
// Шаг 1:
// Инициализация Winsock и начало прослушивания
// Шаг 2:
// Прием входящего соединения
AcceptSocket = accept(ListenSocket, NULL, NULL);
// Шаг 3:
г в |
// Формирование структуры перекрытого ввода-вывода
EventArray[EventTotal] = WSACreateEventO;
ZeroMemo ry(&AcceptOve rlapped,
sizeof(WSAOVERLAPPED));
AcceptOverlapped.hEvent = EventArray[EventTotal];
DataBuf.len = DATA_BUFSIZE;

Г Л А В А 8 Ввод-вывод в Wmsock
227
Листинг 8-7. (продолжение)
DataBuf.buf = buffer;
t EventTotal++;
ц.
II Шаг 4:
// Асинхронный запрос WSARecv для приема данных на сокете
WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, &AcceptOverlapped, NULL);
// Обработка перекрытых запросов на сокете while(TRUE)
{
// Шаг 5:
// Ожидание завершения запроса перекрытого ввода-вывода
Index = WSAWaitForMultipleEvents(EventTotal,
EventArray, FALSE, WSA_INFINITE, FALSE);
// Индекс должен быть равен 0, так как
// в массиве EventArray только один описатель события
// Шаг 6:
// Сброс свободного события
WSAResetEvent(
EventArray[Index - WSA_WAIT_EVENT_O]);
// Шаг 7:
// Определение состояния запроса перекрытого ввода-вывода
WSAGetOverlappedResult(AcceptSocket,
iAcceptOverlapped, &BytesTransferred,
FALSE, &Flags);
// Сначала проверим, не закрыл ли
// партнер соединение, и если да,
// то закроем сокет if (BytesTransferred == 0)
{
printf("Closing socket Xd\n", AcceptSocket);
closesocket(AcceptSocket);
WSACloseEvent(
EventArraydndex - WSA_WAIT_EVENT_O]);
return;
см. след. стр.

228
ЧАСТЬ II Интерфейс прикладного программирования Winsock
Листинг 8-7. (продолжение)
/I Обработка полученных данных,
// содержащихся в OataBuf
1
л
6 !
// Шаг 8
// Асинхронная отправка нового запроса WSARecvO на сокет
Flags = О,
ZeroMemoryC&AcceptOverlapped,
sizeof(WSAOVERLAPPED)),
AcceptOverlapped hEvent = EventArray[Index -
WSA_WAIT_EVENT_O],
DataBuf len = DATA.BUFSIZE,
DataBuf buf = Buffer,
WSARecv(AcceptSocket, &DataBuf, 1,
&ReovBytes, &Flags, &AcceptOverlapped,
NULL),
В Windows NT и 2000 модель перекрытого ввода-вывода позволяет при- ложениям принимать соединения в манере перекрытия, вызывая функцию
AcceptEx на слушающем сокете Эта функция — специальное расширение
Winsock 1 1 и доступна в файле Mswsock h из библиотеки Mswsock lib Пер- воначально она предназначалась для перекрытого ввода-вывода в Windows
NT и 2000, но работает и с Winsock 2 Функция AcceptEx определена так
BOOL AcceptEx (
SOCKET sListenSocket,
SOCKET sAcceptSocket,
PVOID IpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength, "
DWORD dwRemoteAddressLength, '
LPDWORD lpdwBytesReceived,
LPOVERLAPPED lpOverlapped
«00

Параметр sListenSocket обозначает слушающий сокет, a sAcceptSocket —
сокет, принимающий входящее соединение Функция AcceptEx отличается от
accept вы должны передать ей уже готовый принимающий сокет, а не созда- вать его функцией Для создания сокета вызовите функцию socket или WSA-
Socket Параметр IpOutputBuffer — специальный буфер, принимающий три блока данных локальный адрес сервера, удаленный адрес клиента и первый блок данных на новом соединении Параметр dwReceiveDataLength указыва- ет количество байт в IpOutputBuffer, используемых для приема данных Если

Г Л А В А 8 Ввод вывод в Winsock 229
он равен 0, при установлении соединения данные не принимаются Парамет- ры dwLocalAddressLength и duRemoteAddressLength указывают, сколько байт в
lpOutputBuffer резервируется для хранения локального и удаленного адре- сов при принятии соединения Размеры буферов должны быть минимум на
16 байт больше, чем максимальная длина адреса в используемом транспор- тном протоколе Например, в TCP/IP размер буфера равен размеру структу- ры SOCKADDRJN + 16 байт
По адресу ipdwBytesRecewed записывается количество принятых байт дан- ных, если операция завершилась синхронно Если же функция AcceptEx воз- вращает ERRORJO PENDING, данные по этому указателю не обновляются и количество принятых байт нужно получать через механизм уведомления о завершении Последний параметр — ipOverlapped, это структура OVERLAPPED,
позволяющая использовать AcceptEx для асинхронного ввода-вывода Как упоминалось ранее, данная функция работает с уведомлениями о событиях только в приложениях с перекрытым вводом-выводом, так как в ней нет па- раметра процедуры завершения
Функция GetAcceptExSockaddrs из расширения Winsock выделяет локаль- ный и удаленный адреса из параметра IpOutputBujfer
VOID GetAcceptExSockaddrs(
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
Л
DWORD dwLocalAddressLength,
° DWORD dwRemoteAddressLength,
"° LPSOCKADDR «LocalSockaddr,
ыь
LPINT LocalSockaddrLength,
'!< LPSOCKADDR «RemoteSockaddr,
LPINT RemoteSockaddrLength
),
Параметр lpOutputBuffer — указатель lpOutputBuffer, возвращенный функ- цией AcceptEx Параметры dwReceiveDataLength, dwLocalAddressLength и dw-
RemoteAddressLength должны иметь те же значения, что и dwReceiveData-
Length, dwLocalAddressLength и dwRemoteAddressLength, переданные в вызове
AcceptEx Параметры LocalSockaddr и RemoteSockaddr — указатели на струк- туры SOCKADDR с информацией о локальном и удаленном адресах В них хранится смещение от адреса исходного параметра lpOutputBuffer Это об- легчает обращение к элементам структур SOCKADDR, содержащихся в lpOut-
putBuffer Параметры LocalSockaddrLength и RemoteSockaddrLength задают раз- мер локального и удаленного адресов
Процедуры завершения
Данные процедуры представляют собой еще один способ управлять завер- шенными запросами перекрытого ввода-вывода По сути, это функции, ко- торые можно передать запросу перекрытого ввода-вывода и которые вызы- ваются системой по его завершении Их основная роль — обслуживать за- вершенный запрос в потоке, откуда они были вызваны Кроме того, прило- жение может продолжать обработку перекрытого ввода-вывода в процеду- ре завершения

Для использования процедуры завершения приложение должно указать процедуру завершения, а также структуру WSAOVERLAPPED функции ввода- вывода Winsock в качестве параметра (как было описано ранее). У процеду- ры завершения следующий прототип:
void CALLBACK CompletionROUTINE(
DWORD dwError,
DWORD cbTransferred,
LPWSAOVERLAPPED lpOverlapped,
DWORD dwFlags
);
Когда завершается запрос перекрытого ввода-вывода, параметры функ- ции завершения содержат следующую информацию-.
dwError — статус завершения для перекрытой операции, на которую ука- зывает lpOverlapped;
Ш cbTransferred — количество байт, перемещенных при этой операции;
lpOverlapped — структуру WSAOVERLAPPED, переданную в исходный вы- зов функции ввода-вывода;
Ш du Flags не используется и равен 0.
Основное отличие перекрытого запроса с процедурой завершения от запроса с объектом события — поле hEvent структуры WSAOVERLAPPED не используется, то есть нельзя связать объект события с запросом ввода-выво- да. Сделав перекрытый запрос с процедурой завершения, вызывающий по- ток должен обязательно выполнить процедуру завершения по окончании запроса. Для этого нужно перевести поток в состояние ожидания (alertable wait state) и выполнить процедуру завершения позже, по окончании опера- ции ввода-вывода.
Для перевода потока в состояние ожидания используйте функцию WSA-
WaitForMultipleEvents. Тонкость в том, что функции WSAWaitForMultipleEvents
нужен свободный объект события. Если приложение работает только в мо- дели перекрытого ввода-вывода с процедурами завершения, таких событий может и не быть. В качестве альтернативы можно задействовать \С^п32-функ- цию SleepEx для перевода потока в состояние ожидания. Конечно, также можно создать фиктивный, ни с чем не связанный объект события. Если вы- зывающий поток всегда занят и не находится в состоянии ожидания, про- цедура завершения никогда не будет вызвана.
Функция WSAWaitForMultipleEvents также может переводить поток в состо- яние ожидания и вызывать процедуру завершения ввода-вывода, если пара- метр fAlertable равен TRUE. Когда запрос ввода-вывода заканчивается через процедуру завершения, возвращаться будет WSA_IO_COMPLET1ON, а не индекс в массиве событий. Функция SleepEx ведет себя так же, как и функция WSA-
WaitForMultipleEvents, кроме того, что ей не нужны объекты события:
DWORD SleepEx(
DWORD dwMilliseconds,
BOOL bAlertable . i i , ,j

Г Л А В А 8 Ввод-вывод в Winsock 231
Параметр dwMilliseconds определяет, сколько миллисекунд функция Sleep-
Ex будет ждать. Если он равен INFINITE, SleepEx будет ждать бесконечно. Па- раметр bAlertable задает, как будет выполняться процедура завершения. Если он равен FALSE и происходит обратный вызов по окончании ввода-вывода,
процедура завершения не выполняется и операция не завершится, пока не истечет период ожидания, заданный в dwMilliseconds. Если bAlertable равен
TRUE, выполняется процедура завершения и функция SleepEx возвращает
WAITJOJ0OMPLETION.
В листинге 8-8 показана структура простого сервера, управляющего од- ним сокетом с использованием процедур завершения. Выделим следующие этапы.
1. Создание сокета и прослушивание соединений на заданном порте.
2. Прием входящего соединения.
3. Создание структуры WSAOVERLAPPED для установленного соединения.
4. Отправка асинхронного запроса WSARecv на сокет с заданием структуры
WSAOVERLAPPED и процедуры завершения в качестве параметра.
5. Вызов функции WSAWaitForMultipleEvents с параметром fAlertable, равным
TRUE, и ожидание завершения запроса перекрытого ввода-вывода. Когда запрос завершается, автоматически выполняется процедура завершения и функция WSAWaitForMultipleEvents возвращает WSAJOjOOMPLETION. В
процедуре завершения нужно отправить новый запрос перекрытого вво- да-вывода WSARecv с процедурой завершения.
6. Проверка, что функция WSAWaitForMultipleEvents возвращает WSAJO_
COMPLETION.
7. Повтор шагов 5 и 6.
Листинг 8-8. Перекрытый ввод-вывод с использованием процедур завершения
SOCKET AcceptSocket;
WSABUF DataBuf; МШфЬИН
void main(void)
{ ;(
WSAOVERLAPPED Overlapped;
// Шаг 1:
// Запуск Winsock, настройка сокета для прослушивания
// Шаг 2:
// Прием соединения
AcceptSocket = accept(ListenSocket, NULL, NULL); Л* «f
// Шаг 3: »<• ЩП
II Приняв соединение, начинаем \в9ы .ее у
о yiodatj \ см.след.смр-

2 3 2 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Листинг 8-8. (продолжение)
II обработку перекрытого ввода-вывода
// с использованием процедур завершения.
// Для этого в первую очередь
// делаем запрос WSARecv().
X
1 Э 1 >
Flags = 0;
ZeroMemory(&0verlapped, sizeof(WSAOVERLAPPED));
о
DataBuf.len = DATA_BUFSIZE;
DataBuf.buf = Buffer;
// Шаг 4:
// Отправка асинхронного запроса WSARecv() на сокет
// со структурой WSAOVERLAPPED и описанной ниже
// процедурой завершения WorkerRoutine в качестве
// параметров.
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, Overlapped, WorkerRoutine)
== SOCKET_ERROR)
{
if (WSAGetLastErrorO != WSA_IO_PENDING)
{
printf("WSARecv() failed with error Xd\n",
WSAGetLastErrorO);
return;
// Так как функция WSAWaitForMultipleEventsO
// ожидает один или несколько объектов "событие",
// нужно создать фиктивный объект.
// В качестве альтернативы можно использовать
// функцию SleepEx().
EventArray[0] = WSACreateEventO;
while(TRUE)
{
// Шаг 5:
Index = WSAWaitForHultipleEvents(1, EventArray,
FALSE, WSA_INFINITE, TRUE);
// Шаг 6:
if (Index == WAIT_IO_COMPLETI0N)
{
// Процедура завершения запроса перекрытого
// ввода-вывода закончила работу. Продолжаем
// работу с другими процедурами завершения.

Г Л А В А 8 Ввод-вывод в Winsock
233
Листинг 8-8. (продолжение)
break;
I
}
t "gW^HM else
// Произошла ошибка, нужно остановить обработку!
// Если мы также работали с объектом "событие",
// это может быть индекс в массиве событий,
return;
}
void CALLBACK WorkerRoutine(DWORD Error,
DWORD BytesTransferred,
LPWSAOVERLAPPED Overlapped,
DWORD InFlags)
'{
DWORD SendBytes, RecvBytes;
DWORD Flags;
if (Error != 0 || BytesTransferred == 0)
{
// Или на сокете произошла ошибка,
// или сокет закрыт партнером
closesocket(AcceptSocket);
return;
// Запрос перекрытого ввода-вывода WSARecvQ завершился
// успешно. Теперь можно обработать принятые
// данные, содержащиеся в DataBuf. После этого
// нужно отправить другой запрос перекрытого ввода-вывода
// WSARecvO или WSASend(). Для простоты отправим
// запрос WSARecv().
Flags = 0;
ZeroMemory(&Overlapped, sizeof(WSAOVERLAPPED));
'DataBuf.len = DATA.BUFSIZE;
DataBuf.buf = Buffer;
if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes,
&Flags, &Overlapped, WorkerRoutine)
== S0CKET_ERR0R)
if (WSAGetLastErrorO != WSA_10_PENDING )
см. след. стр.

2 3 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Листинг 8-8. (продолжение)
{
printf("WSARecv() failed with error Xd\n",
WSAGetLastErrorO);
return;
} < t
kP
Модель портов завершения
Эта самая сложная модель ввода-вывода. Впрочем, она позволяет достичь наивысшего быстродействия, если приложение должно управлять многими сокетами одновременно. К сожалению, эта модель доступна только в Win- dows NT и 2000. Из-за сложности ее следует использовать, только если при- ложению необходимо управлять сотнями и тысячами сокетов одновремен- но и при этом нужно добиться хорошей масштабируемости при добавлении новых процессоров. Модель оптимальна только при высокоэффективном сервере под управлением Windows NT или 2000, обрабатывающем множе- ство запросов ввода-вывода через сокеты (например, Web-сервера).
Модель портов завершения требует создать Win32-o6beKT порта заверше- ния, который будет управлять запросами перекрытого ввода-вывода, исполь- зуя указанное количество потоков для обработки завершения этих запросов.
Заметим, что на самом деле порт завершения — это конструкция ввода-вы- вода из Win32, Windows NT и 2000, способная работать не только с сокета- ми. Впрочем, здесь мы опишем лишь преимущества этой модели при рабо- те с описателями сокетов. Для начала функцией CreateloCompletionPort созда- дим объект порта завершения, который будет использоваться для управле- ния запросами ввода-вывода для любого количества сокетов:
HANDLE CreateIoCompletionPort(
HANDLE FileHandle,
HANDLE ExistingCompletionPort,
DWORD CompletionKey,
DWORD NumberOfConcurrentThreads
);
Заметьте, функция в действительности используется в двух разных целях:
создания порта завершения и привязки к нему описателя сокета.
При первоначальном создании порта единственный важный параметр —
NumberOfConcurrentThreads, первые три параметра игнорируются. Он опре- деляет количество потоков, которые могут одновременно выполняться на порте завершения. В идеале порт должен обслуживаться только одним по- током на каждом процессоре, чтобы избежать переключений контекста. Зна- чение 0 разрешает выделить число потоков, равное числу процессоров в системе. Создать порт завершения можно так:
CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,
NULL, 0, 0); sot

Г Л А В А 8 Ввод-вывод в Winsock 235
При этом возвращается описатель порта завершения, используемый при привязке сокета.
Рабочие потоки и порты завершения
Теперь нужно связать с портом завершения описатели сокетов. Но прежде чем начинать собственно привязку, необходимо создать один или несколь- ко рабочих потоков для обслуживания порта, когда на него отправляются запросы ввода-вывода сокетов. Но сколько именно потоков понадобится?
Это весьма сложный вопрос, так как требуемое число потоков зависит от структуры приложения.
Важно понимать различие между количеством конкурентных потоков,
задаваемых при вызове CreateloCompletionPort, и количеством создаваемых рабочих потоков — это не одно и то же. Ранее мы рекомендовали при вызо- ве функции CreateloCompletionPort задавать один поток на процессор, что- бы предотвратить переключение контекста между потоками. Параметр Num-
berOfConcurrentThreads функции CreateloCompletionPort явно указывает сис- теме разрешать только п потокам одновременно работать с портом завер- шения. Даже если будет создано более п рабочих потоков для порта завер- шения, только п смогут работать одновременно. (На самом деле, это значе- ние может быть превышено на короткий промежуток времени, но система быстро сократит количество потоков до величины, указанной в вызове Crea-
teloCompletionPort^)
Может показаться странным: зачем создавать рабочих потоков больше,
чем указано в вызове CreateloCompletionPortl Дело в том, что если один из рабочих потоков приостанавливается (путем вызова функции Sleep или Wait-
ForSingleObjecf), другой сможет работать с портом вместо него. Иными сло- вами, всегда желательно иметь столько выполняемых потоков, сколько зада- но в вызове CreateloCompletionPort. Поэтому, если вы ожидаете, что какой-то поток будет блокирован, лучше создать больше рабочих потоков, чем указа- но в параметре CreateloCompletionPort в вызове NumberOfConcurrentThreads.
Создав достаточное число рабочих потоков, начинайте собственно при- вязку описателей сокетов к порту. Вызовите функцию CreateloCompletionPort
Для существующего порта и передайте в первых трех параметрах (FileHandle,
ExistingCompletionPort и CompletionKey) информацию о сокете. Параметр
FileHandle — описатель сокета, который нужно связать с портом завершения,
ExistingCompletionPort определяет порт завершения, a CompletionKey дан-
ные описателя (per-handle data), которые можно связать с конкретным опи- сателем сокета. Используя этот параметр, приложение может связать с со- кетом любые данные. (Мы называем его данными описателя, потому что он представляет данные, связанные с описателем сокета.) Полезно использовать этот параметр, как указатель на структуру данных, содержащую описатель и
Другую информацию о сокете, чтобы процедуры потока, обслуживающие порт завершения, могли ее получать.
Теперь приступим к созданию простого приложения. В листинге 8-9 по- казано, как разработать приложение эхо-сервера, используя модель портов завершения. Выделим следующие этапы:

2 3 6 ЧАСТЬ II Интерфейс прикладного программирования Wmsock
1. Создается порт завершения. Четвертый параметр равен О, что разрешает только одному рабочему потоку на процессор одновременно выполнять- ся на порте завершения.
2 Выясняется, сколько процессоров в системе
3. Создаются рабочие потоки для обслуживания завершившихся запросов ввода-вывода порта завершения с использованием информации о про- цессорах, полученной на шаге 2 В нашем простом примере мы создаем один рабочий поток на процессор, так как не ожидаем приостановки работы потоков. При вызове функции CreateThread нужно указать рабо- чую процедуру, которую поток выполняет после создания. Действия, ко- торые в ней должен выполнить поток, мы обсудим далее.
4 Готовится сокет для прослушивания порта 5150 5. Принимается входящее соединение функцией accept.
6. Создается структура данных описателя и в ней сохраняется описатель принятого сокета.
7. Возвращенный функцией accept новый описатель сокета связывается с портом завершения вызовом функции CreateloCompletionPort Структура данных описателя передается в параметре CompletionKey.
8. Начинается обработка ввода-вывода на принятом соединении. Один или несколько асинхронных запросов WSARecv или WSASend отправляются на сокет с использованием механизма перекрытого ввода-вывода. Когда эти запросы завершаются, рабочий поток обслуживает их и продолжает об- рабатывать новые ( в рабочей процедуре на шаге 3).
9- Шаги 5-8 повторяются до окончания работы сервера.
Листинг 8-9. Настройка порта завершения
StartWinsock();
// Шаг 1:
// Создается порт завершения ввода-вывода
t >
CompletionPort = CreateIoCompletionPort(
INVALID_HANDLE_VALUE, NULL, 0, 0); '
Цо
''
// Шаг 2:
// Выяснение количества процессоров в системе
GetSystemlnfo(&SystemInfо);
// Шаг 3:
// Создание рабочих потоков для доступных процессоров в системе.
// В данном простейшем случае, мы создадим один рабочий поток
// для каждого процессора.
for(i = 0; 1 < Systemlnfо.dwNumberOfProcessors;

Г Л А В А 8 Ввод-вывод в Winsock
237
Листинг 8-9. (продолжение)
{
HANDLE ThreadHandle;
// Создание рабочего потока сервера и передача
// порта завершения в качестве параметра.
// Примечание: процедура ServerWorkerThread
// не определена в этом листинге.
н
!l
ThreadHandle = CreateThread(NULL, 0,
• ' ServerWorkerThread, CompletionPort,
0, &ThreadID);
n
. // Закрытие описателя потока
CloseHandle(ThreadHandle);
// Шаг 4:
// Создание слушающего сокета
Listen = WSASocket(AF_INET, SOCK.STREAM, 0, NULL, 0,
WSA_FLAG_OVERLAPPED);
InternetAddr.sin_family = AF_INET;
InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
InternetAddr.sin_port = htons(5150);
bind(Listen, (PSOCKADDR) AlnternetAddr,
sizeof(InternetAddr));
// Подготовка сокета для прослушивания
listen(Listen, 5);
while(TRUE)
{ .
// Шаг 5:
// Прием соединений и их привязка к порту завершения
Accept = WSAAccept(Listen, NULL, NULL, NULL, 0);
// Шаг 6:
// Создание структуры данных описателя,
// которая будет связана с сокетом
PerHandleData = (LPPER_HANDLE_DATA)
GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA));
printf("Socket number Xd connected\n", Accept);
PerHandleData->Socket = Accept;
см. след. стр.

miврфв>\1, при1\ладни1 о программирования wmsocK
Листинг 8-9. (продолжение)
II Шаг 7
// Привязка сокета к порту завершения
CreateIoCompletionPort((HANDLE) Accept,
CompletionPort, (DWORD) PerHandleData, 0);
// Шаг 8
// Начало обработки ввода-вывода на сокете
// Отправка одного или нескольких запросов WSASendO или
// WSARecv() на сокет, используя перекрытый ввод-вывод
WSARecv( ),
Порты завершения и перекрытый ввод-вывод
После привязки описателя сокета к порту завершения можно обрабатывать запросы ввода-вывода, отправляя сокету запросы на передачу и прием Те- перь вы вправе опираться на уведомления ввода-вывода порта завершения модель портов завершения использует преимущества механизма перекры- того ввода-вывода Win32, в котором вызовы функций Winsock API (таких, как
WSASend и WSARecv) завершаются немедленно после вызова Затем приложе- ние должно правильно извлечь результаты из структуры OVERLAPPED В мо- дели портов завершения это достигается постановкой в очередь ожидания на порте завершения одного или нескольких рабочих потоков с помощью функции GetQueuedCompletionStatus
BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,
LPDWORD lpNumberOfBytesTransferred,
LPDWORD lpCompletionKey,
LPOVERLAPPED * lpOverlapped, - "«
DWORD dwMilliseconds
),
Параметр CompletionPort — порт завершения, на котором будет ждать по- ток Параметр ipNumberOfBytesTransferred принимает байты, перемещенные после операции ввода-вывода, такой как WSASend или WSARecv В параметре
lpCompletionKey возвращаются данные описателя сокета, который был пер- воначально передан в функцию CreateloCompletionPort Как мы уже упомина- ли, лучше передавать через этот параметр описатель сокета В параметр
lpOverlapped записывается перекрытый результат завершенной операции ввода-вывода Это действительно важный параметр — он позволяет получить
данные операции ввода-вывода (per I/O-operation data) Последний пара- метр — dwMilliseconds, задает, сколько миллисекунд вызывающий поток бу- дет ждать появления пакета завершения на порте (если он равен INFINITE,
ожидание длится бесконечно) „
л t л
_
i f c eh

Г Л А В А 8 Ввод-вывод в Winsock 239
Данные описателя и операции
Koi да рабочий поток получает уведомление о завершении ввода-вывода от функции GetQueuedCompletionStatusi, параметры ipCompletionKey и ipOverlapped
содержат информацию о сокете, которая может быть использована для продол- жения обработки ввода-вывода через порт завершения Через эти параметры получают важные данные двух типов описателя и операции
Параметр ipComplettonKey содержит данные, которые мы называем дан- ными описателя, потому что они относятся к описателю сокета в момент перионачальной привязки к порту завершения Эти данные передаются в параметре СотрШюпКеу при вызове функции CreateloCompletionPort Как
\ же упоминалось, приложение может передать через этот параметр любой i пп информации, связанной с сокетом Как правило, здесь хранится описа- ют сокета, связанного с запросом ввода-вывода
Параметр IpOverlapped содержит структуру OVERLAPPED, за которой сле- l\ ю i так называемые данные операции В них содержатся все сведения, не- обходимые рабочему потоку при обработке пакета завершения (эхо-отра- жение данных, прием соединения, новый запрос на чтение и т п) Данные операции могут содержать любое количество байт вслед за структурой OVER-
lAPPbD, переданной в функцию ввода-вывода, которая приняла ее в качестве параметра Для этого проще всего определить свою структуру с первым эле-
\iei 1 гом типа OVERLAPPED Например, мы объявляем следующую структуру для
\ правления данными операции
typedef struct
{
OVERLAPPED Overlapped,
WSABUF DataBuf,
CHAR Buffer[DATA_BUFSIZE],
BOOL OperationType,
> PER_IO_OPERATION_DATA,
В эту структуру входят важные элементы данных, которые вам, может быть придется сопоставить с операцией ввода-вывода например тип завер- шившейся операции (запрос на отправку или прием) В этой структуре по- лезен и буфер данных для завершившейся операции При вызове функции
Winbock, принимающей в качестве параметра структуру OVERLAPPED, мож- но привести вашу структуру к указателю на OVERLAPPED или просто пере- д п ь ссылку на элемент OVERLAPPED вашей структуры
PER.I0_OPERATION_DATA PerloData,
// Функцию нужно вызывать так
WSARecv(socket, , (OVERLAPPED *)&PerIoData);
// или так
WSARecv(socket, , &(PerIoData Overlapped)),
Затем, когда в рабочем потоке функция GetQueuedCompletionStatus вернет чр>кгуру OVERLAPPED (и параметр СотрШюпКеу), можно определить тип запроса, который был отправлен на сокет, сняв имя с элемента структуры

2 4 0 ЧАСТЬ II Интерфейс прикладного программирования Wmsock
OperationType. (Для этого приведите структуру OVERLAPPED к вашему типу
PER_IO_OPERATION_DATA.) Данные об операции весьма полезны, так как по- зволяют управлять несколькими операциями ввода-вывода (чтение-запись,
множественное чтение, множественная запись и т. п.) на одном описателе.
Может возникнуть вопрос: зачем отправлять запросы на несколько опера- ций одновременно на один сокет? Для масштабируемости. Например, на многопроцессорной машине, где рабочие потоки используют все процессо- ры, несколько процессоров смогут отправлять и принимать данные через один сокет одновременно.
В завершение обсуждения простого эхо-сервера рассмотрим функцию
ServerWorkerThread. В листинге 8-10 показано, как разработать процедуру рабочего потока, использующую данные операции и данные описателя для обслуживания запросов ввода-вывода.
Листинг 8-10. Рабочий поток порта завершения
DWORD WINAPI ServerWorkerThread(
LPVOID CompletionPortID)
HANDLE CompletionPort = (HANDLE) CompletionPortID;
DWORD BytesTransferred;
LPOVERLAPPED Overlapped;
LPPER_HANDLE_DATA PerHandleData;
LPPER_IO_OPERATION_DATA PerloData;
DWORD SendBytes, RecvBytes;
DWORD Flags;
while(TRUE)
// Ожидание завершения ввода-вывода на любом
// из сокетов, связанных с портом завершения
GetQueuedCompletionStatus(CompletionPort,
SBytesTransferred,(LPDWORD)&PerHandleData,
(LPOVERLAPPED *) &PerIoData, INFINITE);
h
// Сначала проверим, не было ли ошибки
// на сокете; если была, закрываем сокет
// и очищаем данные описателя
// и данные операции if (BytesTransferred == 0 &&
(PerIoData->OperationType == RECV_POSTED ||
PerIoData->OperationType == SEND_POSTED))
// Отсутствие перемещенных байт (BytesTransferred)
// означает, что сокет закрыт партнером по соединению
1 / / и нам тоже нужно закрыть сокет. Примечание:
)i // для ссылки на сокет, связанный с операцией ввода-вывода,
// использовались данные описателя.
nub
''ОМ ,БЭ

1   ...   18   19   20   21   22   23   24   25   ...   50


написать администратору сайта