Программирование в сетях Windows. Э. Джонс, Д. Оланд
Скачать 2.88 Mb.
|
ГЛАВА 14 Интерфейс Winsock 2 SPI 435 He-IFS-поставщик Если вы разрабатываете многоуровневый поставщик и намерены контроли- ровать каждую проходящую через сокет операцию чтения и записи, вам луч- ше выбрать He-IFS-поставщик. He-IFS-поставщики создают описатели соке- та, используя обратный вызов WPUCreateSocketHandle. Описатели сокета, со- зданные WPUCreateSocketHandle, подобны описателям IFS-поставщика, так как позволяют приложениям Winsock использовать функции ReadFile и WriteFile на сокете. Однако с этими двумя функциями связана существенная пробле- ма ввода-вывода, так как архитектура Winsock 2 должна выполнять переад- ресацию ввода-вывода функциям WSPRecv и WSPSend поставщика службы. WPUCreateSocketHandle определена таю SOCKET WPUCreateSocketHandle( DWORD dwCatalogEntryld, ^ DWORD dwContext, LPINT lpErrno * Поле dwCatalogEntryld определяет идентификатор каталога вашего по- ставщика службы. Параметр dwContext позволяет сопоставлять данные по- ставщика с описателем сокета. Заметьте: dwContext дает большую свободу в отношении информации, которую вы можете связать с описателем сокета. В примере LSP на прилагаемом компакт-диске это поле используется для сохранения количества переданных и полученных данных. Winsock обеспе- чивает обратный вызов функции WPUQuerySocketHandleContext, которая может применяться для извлечения связанных с сокетом данных поставщи- ка, сохраненных в поле dwContext. Этот обратный вызов определен так: : int WPUQuerySocketHandleContext( f )Г SOCKET s, ' ,] LPDWORD lpContext, ф j LPINT lpErrno Параметр 5 — описатель сокета, переданный SPI-клиентом (первоначаль- но созданный WPUCreateSocketHandle), для которого вы хотите восстановить сокетные данные поставщика. Параметр lpContext получает данные постав- щика, первоначально переданные WPUCreateSocketHandle. Параметр lpErrno в обеих функциях получает определенный код ошибки Winsock, если выпол- нение этих функций не удается. Если выполняется WPUCreateSocketHandle, возвращается значение INVALID_SOCKET, если WPUQuerySocketHandleContext — значение SOCKETERROR. Поддержка модели ввода-вывода Winsock Winsock предлагает несколько моделей, которые могут использоваться для управления вводом-выводом через сокет. С точки зрения поставщика служб, каждая модель требует обратного вызова какой-либо SPI-функции из биб- лиотеки Ws2_32.dll, доступной из уже упомянутого параметра UpcallTable в 436 ЧАСТЬ II Интерфейс прикладного программирования Winsock WSPStartup. Если вы разрабатываете простой многоуровневый IFS-постав- щик, принципы, описанные в следующих разделах, не применяются — вы просто полагаетесь на нижестоящий поставщик, чтобы управлять всем вво- дом-выводом для каждой модели. Рассматриваемые принципы относятся к любому другому типу поставщика. Мы сосредоточимся на аспектах разра- ботки многоуровневого He-IFS-поставщика службы. Блокирующий и неблокирующий ввод-вывод Блокирующий ввод-вывод — самая простая форма ввода-вывода в Winsock 2. Любая операция ввода-вывода с блокируемым сокетом не будет возвраще- на, пока не завершится. Поэтому любой поток может выполнять одновре- менно только одну операцию ввода-вывода. Например, когда SPI-клиент вы- зывает функцию WSPRecv в блокируемом режиме, ваш поставщик должен только передать запрос прямо вызову WSPRecv следующего поставщика. Функция WSPRecv вашего поставщика вернется, только когда вызов WSPRecv завершит следующий поставщик. Реализация блокируемого ввода-вывода не сложна, но все же обратите вни- мание на обратную совместимость с блокирующими вызовами Winsock 1.1. Вызовы WSASetBlockingCall и WSACancelBlocking API удалены из спецификации Winsock 2. Впрочем, WSPCancelBlockingHook все-таки может быть вызвана биб- лиотекой Ws2_32.dll, если приложение Winsock 1.1 вызывает функции WSASet- BlockingHook и WSACancelBlockingCall. В многоуровневом поставщике службы вы можете просто передать вызов WSPCancelBlockingHook запросу основно- го поставщика. Если вы реализуете основной поставщик, и происходит зап- рос блокирования, следует предусмотреть механизм периодического вызо- ва функции WPUQueryBlockingCallback: int WPUQueryBlockingCallback( ' DWORD dwCatalogEntryld, LPBLOCKINGCALLBACK FAR .lplpfnCallback, LPDWORD lpdwContext, LPIKT lpErrno ); Параметр dwCatalogEntryld получает идентификатор записи каталога ваше- го поставщика, как описано в функции WSPStartup. Параметр iplpfnCallback — указатель на функцию обработчика блокирующих прерываний приложения, которую вы должны периодически вызывать для предотвращения реально- го блокирования блокирующих вызовов приложения. Эта функция обратно- го вызова имеет следующую форму: typedef BOOL (CALLBACK FAR • LPBLOCKINGCALLBACK)( DWORD dwContext j ); Когда ваш поставщик периодически вызывает IplpfnCallback, значение lpdwContext передается в параметр функции обратного вызова dwContext. Заключительный параметр WPUQueryBlockingCallback — lpErrno, возвраща- ет код ошибки Winsock, если функции возвращают значение SOCKETJERROR. ГЛАВА 14 Интерфейс Winsock 2 SPI 437 Модель select Модель ввода-вывода select требует, чтобы поставщик управлял структурами fdset для параметров readfds, writefds и exceptfds функции WSPSelect: int WSPSelect( • mt nfds, fd_set FAR *readfds, v ) fd_set FAR •writefds, . 503 '• 0- 08 fd_set FAR «exceptfds, • -- u const struct timeval FAR «timeout, ; t m r -'- и LPINT lpErrno - n - ). Oei :nk По сути, тип данных fd_set представляет собой совокупность сокетов. Когда клиент вызывает ваш поставщик, используя WSPSelect, он передает опи- сатели сокета одному или нескольким этим наборам. Поставщик отвечает за определение сетевой активности на каждом из перечисленных сокетов. Для многоуровневых He-IFS-поставщиков это требует создания трех по- лей данных fdset и привязки описателей сокета клиента SPI к описателям сокета нижестоящего поставщика в каждом наборе. Как только все наборы определены, поставщик вызывает функцию WSPSelect нижестоящего постав- щика. Когда нижестоящий поставщик завершает работу, ваш поставщик дол- жен определить, на каких сокетах есть события, ожидающие очереди в каж- дом из полей fdset. Существует полезный обратный вызов WPUFDIsSet, ко- торый определяет установленные сокеты нижестоящего поставщика. Этот обратный вызов подобен макрокоманде FDJSSET (см. главу 8): int WPUFDIsSet( SOCKET s, ll 1 0 J FD_SET FAR «set . ' ); Параметр s — сокет, который вы ищете в наборе. Параметр set — факти- ческий набор описателей сокета. Проверив содержимое каждого набора, переданное нижестоящему поставщику, ваш поставщик должен сохранить соответствие сокета верхнего поставщика нижестоящему. Когда нижестоя- щий поставщик завершает вызов WSPSelect, легко определить, какие сокеты ожидают ввода-вывода для вышестоящего поставщика. Определив сокеты нижестоящего поставщика, у которых в очереди есть сетевые события, обновите исходные наборы fdjset, переданные исходным SPI-клиентом. Файл Ws2spi.h описывает три макрокоманды — FD_CLR, FD_SET и FD_ZERO, которые могут использоваться для управления исходными набо- рами (см. главу 8). Листинг 14-2 демонстрирует одну из возможных реали- заций WSPSelect. Листинг 14-2. Подробности реализации WSPSelect int WSPAPI WSPSelect( int nfds, см. след. стр. 4 3 8 ЧАСТЬ 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 4 0 ЧАСТЬ И Интерфейс прикладного программирования Winsock Параметр s представляет сокет SPI-клиента, ожидающий сообщения Win- dows о сетевых событиях. Параметр 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-поставщик службы — клиент функции WSPAsync- Select нижестоящего поставщика. Значит, ваш поставщик должен транслиро- вать описатели сокета 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, необязательный указатель на функцию WSAO- VERLAPPED_COMPLETION_ROUTINE и указатель на структуру WSATHREADID - она определяет выполняющий вызов поток приложения. Структура WSAOVERLAPPED — ключевая для связи поставщика службы с SPI- клиентом в ходе операций перекрытого ввода-вывода. Она определена так: typedef struct .WSAOVERLAPPED { DWORD Internal; DWORD InternalHigh; DWORD Offset; DWORD OffsetHigh; WSAEVENT hEvent; } WSAOVERLAPPED, FAR * LPWSAOVERLAPPED; Диспетчер перекрытого ввода-вывода отвечает за управление полем Inter- nal структуры WSAOVERLAPPED для SPI-клиента. В начале перекрытой обра- ботки ваш поставщик службы должен присвоить полю Internal значение WSS_OPERATION_IN_PROGRESS. Это важно, ведь если SPI-клиент вызывает функцию WSPGetOverlappedResult, в то время как ваш поставщик обслуживает ожидающую выполнения перекрытую операцию, значение WSSOPERATI- ON_IN_PROGRESS может использоваться в WSPGetOverlappedResult для опре- деления, идет ли все еще перекрытая операция. Когда операция ввода-вывода закончена, ваш поставщик присваивает зна- чения полям OffsetHigh и Offset. Полю OffsetHigh присваивается значение кода ошибки Winsock, полученной в результате операции, а полю Offset — значе- ние флагов, полученных в результате операций ввода-вывода WSPRecv и WSPRecvFrom. После настройки этих полей ваш поставщик уведомит клиен- та SPI о завершении перекрытого запроса через объект события, либо через процедуру завершения (в зависимости от того, как функции ввода-вывода использовали необязательную структуру WSAOVERLAPPED'). События. При основанном на событиях перекрытом вводе-выводе SPI- клиент вызывает одну из функций ввода-вывода со структурой WSAOVER- LAPPED, содержащей объект события Структуре WSAOVERLAPPED J0OMP- LETION_ROUTINE должно быть присвоено значение NULL. Ваш поставщик службы отвечает за управление запросом перекрытого ввода-вывода. Когда выполнение запроса завершается, поставщик уведомит поток вызывающей программы о завершении запроса ввода-вывода, используя перекрестный вызов функции WPUCompleteOverlappedRequest: WSAEVENT WPUCompleteOverlappedRequest( SOCKET s, |