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

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

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

iипрограммированияwinsock

long INetworkEvents

);

Здесь параметр 5 — интересующий нас сокет, а параметр hEventObject — объект «событие», полученный из вызова WSACreateEvent, который нужно связать с сокетом. Последний параметр INetworkEvents — битовая маска получаемая комбинацией масок типов сетевых событий, которые надо отслеживать. Подробно эти типы обсуждались при описании предыдущей модели — WSAAsyncSelect.

Усобытия, используемого в модели WSAEventSelect, два рабочих состояния

свободное (signaled) и занятое (nonsignaled), а также два оперативных режима —ручного (manual) и автоматического сброса (auto reset). Первоначально событие создается в занятом состоянии и режиме ручного сброса. Когда на сокете происходит сетевое событие, связанный с эти событием объект становится занятым. Так как объект события создается в режиме ручного сброса, приложение ответственно за его возврат в занятое состояние после обработки ввода-вывода. Это можно сделать, вызвав функцию WSAResetEvenP.

BOOL WSAResetEvent(WSAEVENT hEvent);

Она принимает описатель события в качестве единственного параметра и возвращает TRUE или FALSE, в зависимости от успешности вызова. Закончив работу с объектом события, приложение должно вызвать функцию WSACloseEvent для освобождения системных ресурсов, используемых объектом:

BOOL WSACloseEvent(WSAEVENT hEvent);

Эта функция также принимает описатель события в качестве единственного параметра и возвращает TRUE или FALSE, в зависимости от успешности вызова.

Сопоставив сокет описателю события, приложение может начать обработку ввода-вывода, ожидая изменения состояния описателя. Функция WSAWaitForMultipleEvents будет ожидать сетевое событие и вернет управление, когда один из заданных описателей объектов событий перейдет в свободное состояние или когда истечет заданный таймаут.

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:

struct _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", NetworkEvents.iErrorCode[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 failed 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, возможно, уже знаете, каждая из этих функций связана с приемом, и П е редачей данных, или установлением соединения на сокете Их выпол-

224

ЧАСТЬ II Интерфейс прикладного программирования Wmsock

нение потребует много времени, поэтому каждая функция может принимать структуру WSAOVERLAPPED в качестве параметра. Тогда они завершаются немедленно, даже если сокет работает в блокирующем режиме. В этом случае функция полагается на структуру WSAOVERLAPPED для завершения запроса ввода-вывода. Есть два способа завершения запросов перекрытого вво- да-вывода: приложение может ожидать уведомления от объекта «событие* (event object notification) или обрабатывать завершившиеся запросы процедурами завершения (completion routines). У всех перечисленных функций (кроме AcceptEx) есть еще один общий параметр — ipCompletionROUTINE. Это необязательный указатель на процедуру завершения, которая вызывается при завершении запроса перекрытого ввода-вывода. Сначала рассмотрим способ уведомления о событиях, а затем — использование процедур завершения.

Уведомление о событии

Для использования уведомления о событии в модели перекрытого ввода-вывода необходимо сопоставить объекты события со структурами WSAOVERLAPPED. Когда функции ввода-вывода, такие как WSASend и WSARecv, вызываются со структурой WSAOVERLAPPED в качестве параметра, они завершаются немедленно. В большинстве случаев эти вызовы возвращают ошибкуSOCKETr_ERROR При этомфункция WSAGetLastErrorвозвращаетзначение WSA_IO_PENDING. Этопросто означает, что операция ввода-вывода продолжается. Приложению требуется определить, когда завершится перекрытый ввод-вывод. Об этом сообщает событие, связанное со структурой WSAOVERLAPPED. Структура WSAOVERLAPPED осуществляет связь между началом запроса ввода-вывода и его завершением:

typedef struct WSAOVERLAPPED

DWORD Internal; DWORD InternalHigh;

DWORD Offset;

DWORD 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:

BOOLWSAGetOverlappedResult( SOCKET s,

LPWSAOVERLAPPED lpOverlapped, LPDWOROlpcbTransfer,

BOOL fWait, LPDWORDlpdwFlags

);

Параметры 5 — сокет, и lpOverlapped — указатель на структуру WSAOVERLAPPED, переданы в запросе перекрытого ввода-вывода. Параметр 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 в качестве параметра.

ПРИМЕЧАНИЕ Как правило, эта функция возвращает ошибку SOCKETJERROR со статусом WSA_1O_PENDING.

5.Вызов функции WSAWaitForMultipleEvents с использованием массива событий и ожидание освобождения события, связанного с запросом перекрытого ввода-вывода.

6.По завершении WSAWaitForMultipleEvents — сброс события функцией WSAResetEvent с массивом событий и обработка результатов запроса.

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;

ZeroMemory(&AcceptOverlapped,

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;

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