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

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

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

438

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

Листинг 14-2. {продолжение)

fd_set FAR • readfds, fd_set FAR * writefds, fd_set FAR * exceptfds,

const struct timeval FAR * timeout, LPINT lpErrno)

{

SOCK_INFO *SocketContext; u_int I;

u_int count; int Ret;

int HandleCount;

// Построение таблицы привязок сокетов для вышестоящего и нижестоящего

поставщиков struct

{

SOCKET ClientSocket;

SOCKET ProvSocket;

> Read[FD_SETSIZE], Wnte[FD_SETSIZE], Except[FD_SETSIZE]; fd_set ReadFds, WriteFds, ExceptFds;

// Построение набора ReadFds для нижестоящего поставщика if (readfds)

{

FD_ZERO(&ReadFds);

 

for

( i = 0 ; i < readfds->fd_count; i++)

^

' 7

 

 

if (MainUpCallTable.lpWPUQuerySocketHandleContext(

r* Й А '

 

 

 

(Read[i].ClientSocket = readfds->fd_array[i]),

 

 

 

 

(LPDWORD) &SocketContext, lpErrno) ==

 

,'

 

 

SOCKET_ERROR)

 

 

 

 

return SOCKET_ERROR;

 

 

 

 

FD_SET((Read[i].ProvSocket =

 

 

 

 

SocketContext->ProviderSocket), &ReadFds);

 

 

//

Построение набора WriteFds для нижестоящего поставщика.

 

 

//

Это в

точности подобно построению набора ReadFds выше.

 

 

//Построение набора ExceptFds для нижестоящего поставщика.

//Это также подобно построению набора ReadFds выше.

//Вызов функции WSPSelect нижестоящего поставщика

Ret = NextProcTable.lpWSPSelect(nfds,

ГЛАВА 14 Интерфейс Winsock 2 SPI

439

Листинг 14-2. (продолжение)

(readfds ? &ReadFds NULL),

(writefds ? iWnteFds : NULL),

(exceptfds ' &ExceptFds : NULL), timeout, lpErrno);

if (Ret != SOCKET_ERROR)

{

HandleCount = Ret;

// Настройка набора readfds вызывающего поставщика if (readfds)

{

count = readfds->fd_count; FD_ZERO(readfds);

for(i =0; (l < count) && HandleCount; i++)

<

if (MamUpCallTable.lpWPUFDIsSet( Read[i].ProvSocket, &ReadFds))

FD_SET(Read[i].ClientSocket, readfds);

HandleCount-;

//Настройка набора writefds вызывающего поставщика.

//Это в точности подобно настройке набора readfds выше.

//Настройка набора exceptfds вызывающего поставщика.

//Это также подобно настройке набора readfds выше.

return Ret;

МодельWSAAsyncSelect

Эта модель ввода-вывода описывает основанное на сообщениях Windows уведомление о сетевых событиях на сокете. SPI-клиенты используют ее, вызывая функцию WSPAsyncSelect:

int WSPAsyncSelect( SOCKET s,

HWND hWnd, unsigned int wMsg, long lEvent, LPINT lpErrno

4 40

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

Параметр s представляет сокет SPI-клиента, ожидающий сообщения Windows о сетевых событиях. Параметр hWnd задает описатель окна, которое должно получить сообщение, определенное параметром wMsg, когда на сокете 5 произойдет сетевое событие (определенное параметром lEvenf). Параметр ipErrno получает код ошибки Winsock, если реализация этой функции возвращает SOCKET_ERROR. Сетевые события, которые ваш поставщик должен поддерживать, заданы параметром lEvent (см. главу 8).

Когда клиент вызывает WSPAsyncSelect, ваш поставщик отвечает за уведомление клиента о происходящих на сокете сетевых событиях, используя предоставленные клиентом описатель окна и сообщение (переданы через вызов WSPAsyncSelect). Ваш поставщик может уведомлять клиента о сетевых событиях, вызывая функцию WPUPostMessage:

BOOL WPUPostMessage(

HWND hWnd,

UINT Msg, WPARAM wParam, LPARAM IParam

);

Поставщик использует параметр hWnd, чтобы пометить как занятый описатель окна клиента SPI. Параметр Msg определяет заданное пользователем сообщение, которое первоначально передано в параметр wMsg функции WSPAsyncSelect. Параметр WParam принимает описатель сокета, на котором произошло сетевое событие. Последний параметр — IParam, состоит из двух частей: нижняя содержит информацию о произошедшем сетевом событии, верхняя — код ошибки Winsock (если произошла ошибка, связанная с сетевым событием, указанным в нижней части).

Многоуровневый He-IFS-поставщик службы — клиент функции WSPAsyncSelect нижестоящего поставщика. Значит, ваш поставщик должен транслировать описатели сокета SPI-клиента в свои описатели сокета с использованием WPUQuerySocketHandleContext перед вызовом функции WSPAsyncSelect нижестоящего поставщика. Чтобы сообщить в окне SPI-клиента о сетевых событиях, поставщик должен использовать службы WPUPostMessage, и следовательно, перехватывать сообщения нижестоящего поставщика. Дело в том, что функция WPUPostMessage возвращает описатель сокета нижестоящего поставщика в верхнем слове параметра IParam, и ваш поставщик должен транслировать описатель сокета в верхнем слове IParam в описатель сокета SPI-клиента, который использовался в исходном вызове WSPAsyncSelect.

Лучше всего управлять перехватом оконных сообщений нижестоящего поставщика с помощью рабочего потока, который создает скрытое окно для управления оконными сообщениями о сетевых событиях. Когда ваш поставщик вызывает функцию WSPAsyncSelect нижестоящего поставщика, вы просто передаете описатель рабочего окна вызову функции WSPAsyncSelect нижестоящего поставщика. Теперь когда рабочее окно вашего поставщика получит сообщения о сетевом событии от нижестоящего поставщика, ваш поставщик сообщит о них SPI-клиенту, используя WPUPostMessage.

Г Л А ВА 14 Интерфейс Winsock 2 SPI

441

Модельввода-выводаWSAEventSelect

WSAEventSelect включает сигнальные объекты событий, когда на сокете происходят сетевые события. SPI-клиенты используют эту модель, вызывая функцию WSPEventSelect:

int WSPEventSelect( SOCKET s,

WSAEVENT hEventObject, long INetworkEvents, LPINT lpErrno

);

Параметр s — сокет SPI-клиента, который ожидает уведомления о сетевых событиях. Параметр hEventObject — описатель объекта WSAEVENT, о котором ваш поставщик сообщает, когда на сокете s происходят сетевые события, указанные в параметре INetworkEvents. Параметр lpErrno получает код ошибки Winsock, если эта функция возвращает SOCKET_ERROR. Сетевые события, которые ваш поставщик должен поддержать (определенные параметром

INetworkEvents) — те же, что и в модели ввода-вывода WSAAsyncSelect.

В многоуровневом He-IFS-поставщике WSPEventSelect реализуется тривиально. Когда SPI-клиент передает объект события, поставщик должен только транслировать описатель сокета SPI-клиента в описатель сокета нижестоящего поставщика, используя функцию WPUQuerySocketHandleContext. После трансляции вызовите функцию WSPEventSelect нижестоящего поставщика, используя объект события, взятый непосредственно от SPI-клиента. Когда операции ввода-вывода происходят на объекте события SPI-клиента, нижестоящий поставщик прямо сообщает об объекте события, и SPI-клиент уведомляется о сетевых событиях.

Когда нижестоящий поставщик сообщает об объекте события, никакой описатель сокета не возвращается SPI-клиенту после завершения этого события. Поэтому ваш поставщик не должен транслировать описатель сокета для SPI-клиента. Этим модель ввода-вывода WSAEventSelect отличается от описанной ранее модели WSAAsyncSelect.

При разработке базового поставщика, учтите, что он должен уведомлять SPIклиента, когда на сокете происходят определенные параметром INetworkEvents сетевые события. При этом ваш поставщик использует предоставленный клиентом объект события, переданный в вызове WSPEventSelect. Ваш поставщик может уведомлять SPI-клиента о сетевых событиях через перекрестный вызов функции WPUSetEvent:

BOOL WPUSetEvent ( WSAEVENT hEvent, LPINT lpErrno

);

Параметр hEvent представляет переданный функцией WSPEventSelect описатель объекта события, о котором ваш поставщик должен сообщить. Параметр lpErrno возвращает код ошибки Winsock, если функция возвращает

FALSE.

442

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

Перекрытый ввод-вывод

Модель перекрытого ввода-вывода требует, чтобы ваш поставщик реализовал диспетчер перекрытого ввода-вывода. Этот диспетчер будет обслуживать и события, основанные на объектах, и завершение основанных на процедурах запросов перекрытого ввода-вывода. Перекрытый ввод-вывод Win32 в Winsock используютSPI-функцииWSPSend,WSPSendTo,WSPRecv,WSPRecvFrom,WSPIoctl.

У каждой из этих функций есть три параметра: необязательный указатель на структуру WSAOVERLAPPED, необязательный указатель на функцию WSAOVERLAPPED_COMPLETION_ROUTINE и указатель на структуру WSATHREADID -

она определяет выполняющий вызов поток приложения.

Структура WSAOVERLAPPED — ключевая для связи поставщика службы с SPIклиентом в ходе операций перекрытого ввода-вывода. Она определена так:

typedef struct .WSAOVERLAPPED {

DWORD

Internal;

DWORD

InternalHigh;

DWORD

Offset;

DWORD

OffsetHigh;

WSAEVENT

hEvent;

} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;

Диспетчер перекрытого ввода-вывода отвечает за управление полем Internal структуры WSAOVERLAPPED для SPI-клиента. В начале перекрытой обработки ваш поставщик службы должен присвоить полю Internal значение WSS_OPERATION_IN_PROGRESS. Это важно, ведь если SPI-клиент вызывает функцию WSPGetOverlappedResult, в то время как ваш поставщик обслуживает ожидающую выполнения перекрытую операцию, значение WSSOPERATION_IN_PROGRESS может использоваться в WSPGetOverlappedResult для определения, идет ли все еще перекрытая операция.

Когда операция ввода-вывода закончена, ваш поставщик присваивает значения полям OffsetHigh и Offset. Полю OffsetHigh присваивается значение кода ошибки Winsock, полученной в результате операции, а полю Offset — значение флагов, полученных в результате операций ввода-вывода WSPRecv и WSPRecvFrom. После настройки этих полей ваш поставщик уведомит клиента SPI о завершении перекрытого запроса через объект события, либо через процедуру завершения (в зависимости от того, как функции ввода-вывода использовали необязательную структуру WSAOVERLAPPED').

События. При основанном на событиях перекрытом вводе-выводе SPIклиент вызывает одну из функций ввода-вывода со структурой WSAOVERLAPPED, содержащей объект события Структуре WSAOVERLAPPED J0OMPLETION_ROUTINE должно быть присвоено значение NULL. Ваш поставщик службы отвечает за управление запросом перекрытого ввода-вывода. Когда выполнение запроса завершается, поставщик уведомит поток вызывающей программы о завершении запроса ввода-вывода, используя перекрестный вызов функции WPUCompleteOverlappedRequest:

WSAEVENT WPUCompleteOverlappedRequest( SOCKET s,

ГЛАВА 14 Интерфейс Winsock 2 SPI

443

 

LPWSAOVERLAPPED lpOverlapped,

DWORD dwError,

DWORD cbTransferred,

LPINT lpErrno

Параметры s и lpOverlapped представляют исходный сокет и структуру WSAOVERLAPPED, которые были первоначально переданы клиентом. Ваш поставщик должен присвоить параметру dwError значение, характеризующее состояние завершения запроса перекрытого ввода-вывода, а параметру cbTransferred — число переданных в ходе перекрытой операции байт. Последний параметр — lpErrno, сообщает код ошибки Winsock, если обращение к этой функции вернуло значение SOCKETERROR. Когда выполнение WPUCompleteOverlappedRequest завершается, она присваивает значения двум полям в структуре WSAOVERLAPPED SPI-клиента: полю InternalHigh — количество переданных и принятых байт cbTransferred, а полю Internal — значение, отличное от WSS_OPERATION_IN PROGRESS.

В основанном на событиях перекрытом вводе-выводе для восстановления результата завершенного перекрытого запроса SPI-клиент в конечном счете вызывает функцию WSPGetOverlappedResult:

BOOL WSPGetOverlappedResult (

UK-

SOCKET s,

 

LPWSAOVERLAPPED lpOverlapped,

 

LPDWORD lpcbTransfer,

 

BOOL fWait,

 

LPDWORD lpdwFlags,

} 'f

LPINT lpErrno

При вызове этой функции ваш поставщик службы должен сообщить о текущем состоянии исходного перекрытого запроса. Как мы уже упоминали, ваш поставщик отвечает за управление полями Internal, InternalHigh, Offset и OffsetHigh в структуре WSAOVERLAPPED SPI-клиента. Когда вызывается функция WSPGetOverlappedResult, ваш поставщик должен сначала проверить поле Internal структуры WSAOVERLAPPED SPI-клиента. Если это поле содержит значение WSS_OPERATION_IN_ PROGRESS, поставщик все еще обрабатывает перекрытый запрос. Если значение параметраfWait в WSPGetOverlappedResult TRUE, поставщик должен ждать завершения перекрытой операции: передачи описателя события в структуру WSAOVERLAPPED SPI-клиента перед возвращением результатов. При значении FALSE поставщик вернет ошибку WSA_ LOJNCOMPLETE. Как только перекрытая операция завершится, поставщик должен присвоить значения параметрам WSPGetOverlappedResult:

Ш lpcbTransfer значение поля InternalHigh структуры WSAOVERLAPPED,

сообщающее о количестве переданных операцией отправки или получения байтов;

lpdwFlags — значение поля Offset структуры WSAOVERLAPPED, сообщающее

офлагах, являющихся результатом операций WSPRecv или WSPRecvFrom;

444

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

Я ipErrno — значение поля OffsetHigh структуры WSAOVERLAPPED, сообщающее о результирующем коде ошибки.

Листинг 14-3 демонстрирует один из способов реализации WSPGetOverlappedResult.

Листинг14-3.ПодробностиреализациифункцииWSPGetOverlappedResult

BOOL WSPAPI WSPGetOverlappedResult( SOCKET s,

LPWSAOVERLAPPED lpOverlapped, LPDWORD lpcbTransfer,

BOOL fWait, LPDWORD lpdwFlags, LPINT IpErrno)

{

DWORD Ret;

if (lpOverlapped->Internal != WSS_OPERATION_IN_PROGRESS)

{

•lpcbTransfer = lpOverlapped->InternalHigh; •lpdwFlags = lpOverlapped->Offset; •IpErrno = lpOverlapped->OffsetHigh;

return(lpOverlapped->OffsetHigh == 0 ? TRUE : FALSE);

}

else

{

if (fWait)

{

Ret = WaitForSingleObject(lpOverlapped->hEvent, INFINITE);

if ((Ret == WAIT_0BJECT_0) && (lpOverlapped->Internal != WSS_OPERATION_IN_PROGRESS))

{

•lpcbTransfer = lpOverlapped->InternalHigh; •lpdwFlags = lpOverlapped->Offset; •IpErrno = lpOverlapped->OffsetHigh;

return(lpOverlapped->OffsetHigh == 0 ? TRUE : FALSE);

}

else

•IpErrno = WSASYSCALLFAILURE;

>

else

•IpErrno = WSA_IO_INCOMPLETE;

}

return FALSE;

Г Л А ВА 14 Интерфейс Winsock 2 SPI

445

Процедуры завершения. Завершая основанный на процедурах перекрытый ввод-вывод, SPI-клиент вызывает одну из функций ввода-вывода со структуройWSAOVERIAPPEDиуказателемWSAOVERLAPPED COMPLETIONJROUTINE.

Ваш поставщик службы отвечает за управление запросом перекрытого вво- да-вывода. Когда запрос завершается, поставщик должен уведомить поток вызывающей программы о завершении перекрытого ввода-вывода, используя Win32-MexaHH3M асинхронного вызова процедур (asynchronous procedure call, APC). APC требует, чтобы поток вызывающей программы был в «тревожном» состоянии ожидания (см. главу 8). Заканчивая обслуживание завершения основанного на процедурах перекрытого запроса, поставщик должен уведомить SPI-клиента через перекрестный вызов функции WPUQueueApo.

int WPUQueueApc( LPWSATHREADID lpThreadld, LPWSAUSERAPC lpfnUserApc, DWORD dwContext,

LPINT lpErrno

);

Параметр lpThreadld представляет структуру WSATHREADID SPI-клиента, передаваемую исходным вызовом ввода-вывода и обеспечивающую завершение процедуры. Параметр lpfnUserApc — указатель на промежуточную функцию WSAUSERAPC, которую поставщик должен предоставить для обратного вызова SPI-клиенту. Промежуточная функция вызывает WSAOVERLAPPED_ COMPLETION_ROUTINE SPI-клиента, предоставленную исходным перекрытым вызовом. Прототип промежуточной функции WSAUSERAPC определен так-.

typedef void (CALLBACK FAR * LPWSAUSERAPC)(DWORD dwContext);

Заметьте, что это определение функции содержит только один параметр — dwContext. Когда SPI-клиент вызывает эту функцию, dwContext содержит информацию, первоначально переданную в параметре dwContext в WPUQueueАрс. По существу, dwContext позволяет передавать структуру данных, содержащую любые нужные элементы информации, при вызове WSAOVERLAPPED_COMPLETION_ROUTINE (см. главу 8):

void CALLBACK CompletionROUTINE( IN DWORD dwError,

IN DWORD cbTransferred,

IN LPWSAOVERLAPPED lpOverlapped, IN DWORD dwFlags

Ваш поставщик передает через параметр dwContext функции WPUQueueАрс следующую информацию:

Ж состояние перекрытой операции как код ошибки Winsock;

число переданных через перекрытую операцию байт;

структуру WSAOVERLAPPED вызывающей программы;

флаги вызывающей программы, переданные вызову ввода-вывода.

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

Теперь поставщик может успешно вызвать WSAOVERLAPPEDjCOMPLETION ROUTINE клиента SPI из промежуточной процедуры завершения.

Порты завершения. Модель ввода-вывода порта завершения Winsock 2 реализована в модуле Ws2_32.dll. Модель порта завершения основана на модели перекрытого ввода-вывода, организованной на процедурах. Поэтому, чтобы ваш поставщик службы управлял моделью порта завершения, не требуется никаких дополнительных указаний.

Управление перекрытым вводом-выводом. Когда SPI-клиент вызывает ваш многоуровневый поставщик, использующий любую из описанных моделей перекрытого ввода-вывода, администратор перекрытия поставщика в свою очередь вызывает нижестоящий поставщик, который использует метод перекрытого ввода-вывода, основанный на событиях или на порте завершения. Если ваш поставщик выполняется под управлением Windows NT или Windows 2000, мы рекомендуем использовать порты завершения для перекрытого ввода-вывода на нижестоящем поставщике. В Windows 95 и Windows 98 следует использовать только основанный на событиях перекрытый ввод-вывод. Для работы с перекрытыми событиями вашему поставщику доступны следующие три вызова:

WSAEVENT WPUCreateEvent(LPINT lpErrno);

BOOL WPUResetEvent(WSAEVENT hEvent, LPINT lpErrno);

BOOL WPUCloseEvent(WSAEVENT hEvent, LPINT lpErrno);

Функция WPUCreateEvent создает и возвращает объект события, которое находится в режиме сброса вручную — точное подобие функции WSACreateEvent (см. главу 8). Если WPUCreateEvent не сможет создать объект события, возвращается NULL, а параметр lpErrno будет содержать определенный код ошибки Winsock. Функция WSPResetEvent, точно так же, как WSAResetEvent, переводит объект события (параметр hEvent) из незанятого в занятое состояние. Функция WPUCloseEvent — аналог WSACloseEvent, она освобождает все рабочие ресурсы, связанные с описателем объекта события.

В примере LSP на прилагаемом компакт-диске основанный на событиях перекрытый ввод-вывод используется для управления всеми действиями перекрытого ввода-вывода для SPI-клиента. Это позволяет примеру функционировать под Windows 2000, Windows NT, Windows 98 и Windows 95Важ- но, что в этом примере операции перекрытого ввода-вывода обслуживает только один поток, что ограничивает возможности поставщика: он может обслуживать одновременно не более чем WSA_MAXIMUM_WAIT_EVENTS (64) объектов событий.

Чтобы преодолеть этот барьер, создайте несколько потоков обслуживания. А еще лучше (особенно если вы разрабатываете поставщик для Windows NT и Windows 2000) — используйте вместо основанного на событиях перекрытого ввода-вывода порты завершения.

Расширенные функции

Библиотека Winsock Mswsock.lib обеспечивает приложения расширенными функциями, увеличивающими возможности Winsock. В настоящее время поддерживаются следующие расширенные функции:

Г Л А ВА 14 Интерфейс Winsock 2 SPI

447

 

AcceptEx WSAID_ACCEPTEX;

ШGetAcceptExSockaddrs — WSAID_GETACCEPTEXSOCKADDRS\

TransmitFile WSAIDJTRANSMITFILE;

ШWSARecvEx не связана с GUID.

Когда приложение, связанное с Mswsock.lib, использует AcceptEx, GetAcceptExSockaddrs и TransmitFile, оно неявно вызывает функцию WSPIoctl вашего поставщика, используя SIOjGET_EXTENSION_FUNCTION_POINTER. Функция

WSPIoctl определена так:

int WSPIoctl( SOCKET s,

DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cblnBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer,

LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine, LPWSATHREADID lpThreadld,

LPINT lpErrno

Когда происходит вызов, параметру dwIoControlCode присваивается значение SIO_GET_EXTENSIONJUNCTION_POINTER. Параметр lpvInBuffer содержит указатель на GUID, который определяет расширенную функцию для нужд Mswsock.lib (значения GUID, перечисленные в списке, определяют поддерживаемые в настоящее время расширенные функции). Если это значение GUID соответствует любому из определенных значений, ваш поставщик должен вернуть через lpvOutBuffer указатель на реализацию этой расширенной функции. Остальные параметры для управления расширенными функциями в SPI прямо не используются.

Мы отметили, что функция WSARecvEx не имеет GUID. Дело в том, что WSARecvEx вызывает не WSPIoctl, а прямо WSARecv. В результате, поставщик не может напрямую контролировать расширенную функцию WSARecvEx.

Установка поставщиков транспортной службы

Установка поставщиков транспортной службы включает разработку простого приложения для вставки многоуровневого или базового поставщика в каталог поставщиков служб Winsock 2. Многоуровневый и базовый поставщики транспорта устанавливаются по-разному. Программа установки просто настраивает поставщик в базе данных системной конфигурации Winsock 2, которая является каталогом всех установленных поставщиков службы. База данных конфигурации дает Winsock 2 знать, что поставщик существует, и определяет тип его службы. Winsock 2 использует эту БД, чтобы определить, какие поставщики службы следует загрузить при создании сокета приложением Winsock. Ws2_32.dll ищет в базе данных первый поставщик, соответ-