Программирование в сетях Windows
.pdf448 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
/ |
ствующий сокетным входным параметрам вызовов socket и WSASocket (таким как семейство адреса, тип сокета и протокол) Как только подходящая запись найдена, Ws2_32 dll загружает соответствующую DLL-библиотеку поставщика службы, определенную в каталоге
По существу, для успешных установки и управления записью поставщика службы в БД поставщиков необходимы четыре функции конфигурации SPI Каждая функция начинается с префикса WSC WSCEnumProtocols, WSCInstallProvider, WSCWnteProviderOrder, WSCDelnstallProvider
Эти функции работают с базой данных, используя структуру WSAPROTOCOLJNFOW (см главу 5) При установке поставщика транспортной службы нас прежде всего интересуют поля Providerld, dwCatalogEntryld и ProtocolCham этой структуры Поле Providerld — GUID, позволяющий уникально определять и устанавливать поставщик в любой системе Поле dwCatalogEntryld просто определяет каждую структуру WSAPROTOCOLJNFOW записи каталога в БД по уникальному числовому значению Поле ProtocolChain определяет, является ли структура WSAPROTOCOLJNFOW записью каталога для базового, многоуровневого или цепочки протоколов поставщика Поле ProtocolChain — структура WSAPROTOCOLCHAIN
typedef struct { int ChainLen,
DWORDChainEntnes[MAX_PR0TOCOL_CHAIN], } WSAPROTOCOLCHAIN, FAR * LPWSAPROTOCOLCHAIN,
Поле ChainLen определяет, представляет ли запись каталога многоуровневый (ChamLen = 0), основной (ChainLen = 1) поставщик, или цепочку протоколов поставщика (ChamLen > 1) Цепочка протоколов (protocol chain) — специальная запись каталога, задающая, как поместить многоуровневый поставщик служб между Winsock и другими поставщиками службы (рис 14-2) Многоуровневые и основные поставщики имеют только по одной записи каталога в БД Последнее поле — ChamEntnes, представляет собой массив идентификаторов каталога, используемых для описания порядка загрузки поставщиков в цепочке протоколов Когда в ходе создания сокета Ws2_32 dll ищет в каталоге соответствующий поставщик, она просматривает только записи цепочек протоколов и базовых поставщиков Записи многоуровневых поставщиков (ChamLen - 0) игнорируются — они существуют только для сопоставления многоуровневого поставщика цепочке протоколов в записях этих цепочек.
Установка базового поставщика
Чтобы установить основной поставщик, создайте структуру WSAPROTOCOL_ INFOW записи каталога, которая представляет этот поставщик Заполните поля в этой структуре информацией об атрибутах протокола Не забудьте присвоить значение 1 полю ChainLen структуры ProtocolChain Когда структура определена, поместите ее в каталог, используя функцию WSClnstallProvider
int WSCInstallProvider(
const LPGUID lpProviderld,
const LPWSTR lpszProviderDllPath,
Г Л А В А 14 Интерфейс Winsock 2 SPI |
449 |
const LPWSAPROTOCOL_INFOW lpProtocolInfoList, DWORDdwNumberOfEntries,
LPINT lpErrno
).
Параметр ipProviderld — GUID, позволяющий идентифицировать поставщик для каталога Winsock Параметр ipszProviderDllPath — строка, содержащая загрузочный путь к DLL поставщика Строка может включать переменные окружения, такие как %SystemRoot% Параметр lpProtocolInfoList представляет массив структур данных WSAPROTOCOLJNFOWдля помещения в каталог Устанавливая основного поставщика, вы можете просто назначить структуре WSAPROTOCOLJNFOWпервый элемент массива Параметр dwNumberOfEntnes содержит количество записей в массиве lpProtocolInfoList, параметр IpErmo — код ошибки, если выполнение этой функции не удалось Тогда возвращается SOCKETERROR
Установка многоуровневого поставщика
Чтобы установить многоуровневый поставщик службы, создайте две структуры WSAPROTOCOLJNFOW записей в каталоге Одна будет представлять ваш многоуровневый поставщик (например, длина цепочки протоколов, равная 0), а другая — цепочку протоколов (например, длина цепочки протоколов, большая 1), соединяющую многоуровневый поставщик с основным Эти две структуры следует инициировать со свойствами структуры WSAPROTOCOL_ INFOW записи каталога существующего поставщика службы Запись вы можете найти с помощью функции WSCEnumProtocols
int WSCEnumProtocols( |
|
LPINT lpiProtocols, |
|
LPWSAPROTOCOL.INFOW |
lpProtocolBuffer, |
LPDWORD lpdwBufferLength, |
|
LPINT lpErrno |
r(>v |
),
Параметр lpiProtocols — необязательный массив значений Если lpiProtocols содержит NULL, возвращается информация по всем доступным протоколам, если нет — только по протоколам, перечисленным в массиве Параметр lpProtocolBuffer — предоставленный приложением буфер, заполненный структурами WSAPROTOCOLJNFOW из каталога Winsock 2 Входящий параметр lpdwBufferLength содержит количество байт в переданном функцией WSCEnumProtocols буфере lpProtocolBuffer При выходе в этот параметр заносится минимальный размер буфера, который можно передать WSCEnumProtocols для определения всей запрошенной информации Параметр lpErrno содержит информацию об ошибке, если выполнение функции неудачно и возвращено SOCKET_ERROR Располагая записью в каталоге для поставщика, который вы собираетесь поместить сверху, скопируйте свойства структуры
WSAPROTOCOLJNFOW поставщика, во вновь созданные структуры
После инициализации поместите в каталог запись многоуровневого поставщика, используя функцию WSCInstallProvider Затем найдите идентифи-
450 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
катор каталога, который присваивается этой структуре после установки, перебрав записи в каталоге с помощью WSCEnumProtocols. Эта запись может затем использоваться при помещении в каталог записи цепочки протоколов, связывающей ваш многоуровневый поставщик с другим поставщиком. Затем для установки включенного в цепочку поставщика вызывается функция WSCInstallProvider. Этот процесс иллюстрирует следующий псевдокод:
WSAPROTOCOL_NIFOW LayeredProtocolInfoBuff,
ProtocolChainProtoInfo,
BaseProtocolInfoBuff;
// Получение BaseProtooolInfoBuff с помощью WSCEnumProtocolsO
memcpy (&LayeredProtocolInfoBuff , &BaseProtocolInfoBuff, sizeof(WSAPROTOCOL_INFO));
LayeredProtocolInfoBuff.dwProviderFlags = PFL_HIDDEN; LayeredProtocolInfoBuff.Providerld = LayeredProviderGuid;
// Эта запись будет заполнена системой LayeredProtocolInfoBuff.dwCatalogEntryld = 0;
LayeredProtocolInfoBuff.ProtocolChain.ChainLen =
LAYERED_PROTOCOL;
WSCInstallProvider(&LayeredProviderGuid, |
L"lsp.dll", |
|
|
JLayeredProtocolInfoBuff, 1, & install error); |
tot |
||
|
|
|
|
// Определение |
идентификатора каталога многоуровневого поставщика с |
' ' |
|
// использованием функции WSCEnumProtocolsO |
|
||
for ( 1 = 0 ; 1 < TotalProtocols; 1++) |
|
|
|
if (memcmp |
(&ProtocolInfo[l].Providerld, &ProviderGuid, |
r- |
|
sizeof |
(GUID))==O) |
|
|
{ |
|
|
|
LayeredCatalogld = Protocollnfo[i].dwCatalogEntryld; break;
Memcpy(&protocolChainProtoInfo, &BaseProtocolInfoBuff,
sizeof(WSAPR0T0C0L_INF0));
ProtocolChainProtoInfo.ProtocolChain.ChainLen = 2;
ProtocolChainProtoInfo.ProtocolChain.ChainEntries[0] =
LayeredProvideProtocolInfo.dwCatalogEntryld;
ProtocolChalnProtoInfo.ProtocolChain.ChainEntries[1] =
BaseProtooolInfoBuff.dwCatalogEntryld;
WSCInstallProvider(
iChainedProviderGuid,
L"lsp.dll", // lpszProviderDHPath &ProtooolChainProtoInfo, // lpProtocolInfoList
|
|
Г Л А В А 14 Интерфейс Winsock 2 SPI |
451 |
1, |
// |
dwNumberOfEntries |
|
&install error |
// |
lpErrno |
|
Обратите внимание на флаг PFLHIDDENв структуре WSAPROTOCOLJNFOW. Благодаря ему функция WSAEnumProtocols (см. главу 5) не включает каталог для многоуровневого поставщика в возвращаемый ею буфер.
Другой важный флаг, которым должна управлять программа установки — XP1JFS_ HANDLES. Любой He-IFS-поставщик службы, использующий WPUCreateSocketHandle для создания своих описателей сокета, не должен задавать флаг XP1_IFS_HANDLES в структуре WSAPROTOCOLJNFOW. Для приложений Winsock отсутствие флага ХР1JFSJiANDLES — указание избегать использования функций ReadFile и WriteFile из-за потенциального снижения производительности.
Упорядочение поставщиков
Теперь вы должны решить, как Winsock 2 будет искать поставщиков служб в БД. Большинство приложений Winsock определяют нужный протокол с помощью параметров вызова функций socket и WSASocket. Например, если приложение создает сокет, используя семейство адресов AFJNET и тип SOCK_STREAM, Winsock 2 ищет заданный по умолчанию протокол TCP/IP, запись включенного в цепочку или базового поставщика в БД. Когда вы устанавливаете поставщик службы, используя WSCInstallProvider, запись в каталоге автоматически становится последней в БД. Чтобы сделать поставщик службы стандартным поставщиком TCP/IP, упорядочите записи о поставщиках в базе данных и поместите запись цепочки протоколов перед другими поставщиками TCP/ IP. Для этого вызовите функцию WSCWriteProviderOrder.
int WSCWriteProviderOrder( LPDWORD lpwdCatalogEntryld, DWORD dwNumberOfEntries
Параметр lpwdCatalogEntryld принимает массив идентификаторов каталога, определяющих порядок его сортировки. Вы можете задать идентификаторы каталога, вызвав WSCEnumProtocols, как описано ранее. Параметр dwNumberOfEntries — счетчик записей каталога в массиве. Эта функция возвращает ERROR_SUCCESS (0), если выполнена успешно, и код ошибки Winsock — если нет.
Функция WSCWriteProviderOrder не входит в библиотеку Ws2_32.dll. Чтобы использовать ее, приложение должно быть скомпоновано с библиотекой Sporder.lib. Модуль Sporder.dll не является частью ОС Windows, ищите DLL поддержки в библиотеке Microsoft Developer Network (MSDN). Если вы используете этот модуль, распространяйте его вместе с приложением.
Библиотека MSDN также предлагает удобную программную утилиту Sporder.exe, которая позволяет просматривать и переупорядочивать записи каталога в БД Winsock 2 (рис. 14-3)-
452 |
ЧАСТЬ II Интерфейс прикладного программирования Wmsock |
|
Senfleeptovtdws|tameResatutronj
LayeredTCP/IPovei|MSAFDТсрир[TCP/IP])
MSAFDTcpp[TCP/IP]
MSAFDTcpip[UDP/IP]
MSAFDTcpip[RAW/IP]
RSVPUDPServiceProvider
RSVPTCPServiceProvider
MSAFD NetBIOS [\Device\NetBT TcpipJ28O1SCC0DOAA11D28A538EE7542C0OOE}
MSAFDNetBIOS[\DevKe\NetBTlTcpipJ28D19Ci:0DOAA11D28A538EE7542C000E}
MSAFDNetBIOS[SDeviceSNetBT_Tcp«>J28O13CC2-D(»A11D2-8A53«E7542COOOE)
MSAFDNetBIOS[\DevrceSNetBT_TcppJ28O19CC2D0AA11D28AS38EE7542C000E)
MSAFDNetBIOS[\Device\NetBT_TcppJ49213832DOAB11D2I3A53-00105424E14A)]
MSAFONetBIOS|\Devce\NetBT TcpipJ49213832DOAB11D28А53-О01ОЕА24Е14АЦ
MSAFDNetBIOS[\Device\NefflTlTcr4> {49213831DOAB11D28A53OO1O5A24E14A(]
MSAFONetBIOS|\Device\NetBT_Tcpip~<49213831DOAB11D28A5300105A24E14A!]
MSAFDNetBIOS(\DevrceWetBTTcpipI(49213830DQAB11D28A530010SA24E14A)]
MSAFDNetBIOS[\Devce\NelBT~TcpipJ49213830DQAB11D2-8A5300105A24E14AI]
Рис. 14-3. Конфигурация Winsock2 послеустановки многоуровневого поставщика на компьютерсWindows2000
Удаление поставщика службы
Удалить поставщик службы из каталога Wmsock 2 не сложно Вызовите функцию WSCDeinstallProvider
int WSCDeinstallProvider( LPGUID lpProviderld, LPINT lpErrno
Параметр lpProviderld представляет GUID удаляемого поставщика службы Параметр lpErrno получает код ошибки Wmsock, если функция возвращаетSOCKET_ERROR
При удалении поставщика службы вы должны учитывать один важный момент Всегда есть возможность, что многоуровневый поставщик службы включил идентификатор каталога вашего поставщика службы в свою цепочку протокола Если это так, вы должны удалить идентификатор каталога из любых цепочек протоколов, которые ссылаются на ваш поставщик
Проблемы при установке многоуровневых поставщиков
Многоуровневые поставщики службы имеют огромный потенциал Однако нынешняя спецификация Winsock 2 не отвечает на важный вопрос как многоуровневый поставщик службы может узнать, в каком месте вставить себя в цепочку протоколов, если находит другой многоуровневый поставщик
Например, если вы хотите установить поставщик шифрования данных в системе, которая уже содержит поставщик фильтрации URL, оче-
4 54 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
пространство имен, уведомите о нем систему с помощью функции WSCInstallNameSpace. Установленный поставщик вы можете отключить или удалить из системного каталога с помощью функций WSAEnableNSProvider и WSAUnlnstallNameSpace.
ФункцияWSCInstallNameSpace |
j |
Эта функция используется для установки поставщика в системный катался]
Она определена так: > t
int WSCInstallNameSpace ( LPWSTRlpszldentifier, LPWSTRlpszPathName, DWORDdwNameSpace, DWORDdwVersion, LPGUID lpProviderld
);
Сразу бросается в глаза, что все строковые параметры представлены широкими символами. На самом деле, с использованием строк широких символов реализованы все поставщики пространства имен.
Параметр lpszldentifier — имя поставщика пространства имен, возвращаемое функцией WSAEnumNameSpaceProviders (см. главу 10). Параметр lpszPathName — расположение DLL Строка может включать системные переменные, такие как %SystemRoot%. Параметр dwNameSpace — числовой идентификатор пространства имен. Например, в заголовочном файле Nspapi.h определены константы других известных пространств имен, типа NS_SAP, для IPX SAP. Параметр dwVersion задает номер версии пространства имен. Наконец, параметр lpProviderld — GUID, идентифицирующий поставщик пространства имен.
В случае удачного выполнения WSCInstallNameSpace возвращает 0, иначе — SOCKET_ERROR. Чаще всего это WSAEINVAL (пространство имен с данным GUID уже существует) или WSAEACCESS (процесс вызова не имеет достаточных полномочий). Задавать пространство имен могут только члены группы Administrators.
ФункцияWSCEnableNSProvider
Эта функция используется для изменения состояния (включения или выключения) поставщика пространства имен:
int WSCEnableNSProvider ( LPGUID lpProviderld, BOOL fEnable
);
Параметр lpProviderld — идентификатор GUID пространства имен, которое вы хотите изменить. Параметр fEnable содержит булево значение, указывающее, следует ли включить или выключить поставщик. Выключенный поставщик не может обрабатывать запросы или регистрацию.
Г Л А ВА 14 Интерфейс Winsock 2 SPI |
455 |
При удачном выполнении WSCEnableNSProvider возвращает 0, иначе — SOCKET_ERROR. Если GUID поставщика не действителен, возвращается ошибкаWSAEINVAL.
ФункцияWSCUnlnstallNameSpace
г
Эта функция удаляет поставщик пространства имен из каталога: int WSCUnlnstallNameSpace ( LPGUID lpProviderld );
Параметр lpProviderld — GUID удаляемого пространства имен. Если GUID не действителен, выполнение возвращается ошибка WSAEINVAL.
Реализация пространства имен
Пространство имен должно реализовать все девять функций, соответствующих функциям RNR. Кроме того, следует разработать метод для постоянного сохранения данных, то есть поддерживать данные вне экземпляра DLL Каждый загружающий DLL процесс получает свой собственный сегмент данных — это означает, что сохраненные в рамках DLL данные не могут быть разделены между экземплярами (на самом деле, совместное использование информации загрузившими DLL приложениями возможно, но не одобряется).
Дополнительную информацию по библиотекам DLL можно найти в книге «Windows для профессионалов» Джеффри Рихтера (М.: «Русская Редакция». 2001). Как упоминалось в главе 10, существуют три типа пространства имен: динамическое, постоянное и статическое. Очевидно, реализовать статическое пространство не стоит, так как оно не допускает программную регистрацию служб. Далее мы рассмотрим некоторые аспекты поддержки данных, которые пространство имен должно сохранить.
Очень важно использовать строки широких символов во всех функциях поставщика пространства имен: не только строковых параметров функций, нотакжеистроквRNR-структурах,типаWSAQUERYSETиWSASERVICECLASS- INFO. Вы можете спросить, возможно ли это — ведь когда приложение регистрирует или разрешает имя, оно вправе использовать и обычную версию (ASCII), и версию с широкими символами (UNICODE) функций и RNR-струк- тур. На самом деле, работает любая версия, так как все запросы ASCII проходят промежуточный уровень, преобразующий все строки в строки широких символов.
Это верно и для вызова функции, и для ее возвращения. То есть если вызывающемуприложениювозвращаетсяWSAQUERYSET(спомощьюфункции WSALookupServiceNexf), то любые данные, возвращенные поставщиком пространства имен, изначально находятся в виде UNICODE и преобразуются в ASCII перед возвратом из функции. Так что если ваши приложения используют RNR-функции, вызов версий с широкими символами будет быстрее, так как не требует никаких преобразований.
Из девяти функций, которые должен реализовать поставщик пространства имен, только семь соответствуют RNR-функциям Winsock 2 (табл. 14-2).
456 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
|
|
Табл. 14-2. Соответствие функций регистрации и разрешения имен |
|
||
в Winsock 2 функциям поставщика пространства имен |
|
||
функция Winsock |
Эквивалентная функция поставщика пространства имен |
||
WSAInstallServiceClass |
NSPInstallServtceClass |
|
|
WSARemoveServiceClass |
NSPRemoveServiceClass |
|
|
WSAGetServiceClassInfo |
NSPGetServiceClassInfo |
! T*" |
|
WSASetService |
NSPSetService |
}$1 |
|
WSALookupServiceBegm |
NSPLookupServiceBegm |
|
|
WSALookupServiceNext |
NSPLookupServiceNext |
э г |
|
WSALookupServiceEnd |
NSPLookupServiceEnd |
|
Оставшиеся две функции применяются для инициализации и очистки. Когда пространство имен указано в системе, приложения могут использовать его, определяя соответствующий GUID или идентификатор пространства имен. Тогда приложение вызывает стандартную RNR-функцию Winsock 2, как описано в главе 10. При этом вызывается эквивалентная функция поставщика пространства имен Например, при вызове из приложения функции WSAInstallServiceClass, которая ссылается на GUID пользовательского пространства имен, вызывается функция NSPInstallServiceClass для этого поставщика
Далее мы рассмотрим все функции пространства имен.
ФункцияNSPStartup
NSPStartup вызывается каждый раз, когда загружается DLL поставщика пространства имен. Ваша реализация пространства имен должна включать эту функцию, экспортируемую из DLL. При этом можно выделить для каждой DLL все структуры данных, которые требуются для работы поставщика. Прототип NSPStartup выглядит так:
int NSPStartup (
LPGUID lpProviderld, LPNSP_ROUTINE lpnspRoutines
);
Первый параметр — lpProviderld, является GUID для этого поставщика пространства имен. Параметр lpnspRoutines — структура NSP_ROUTINE, которую должна заполнить ваша реализация этой функции. Эта структура обеспечивает указатели функции для восьми других функций пространства имен поставщика. Объект NSP_ROUTINE определен так:
typedef struct _NSP_ROUTINE |
|
{ |
|
DWORD |
cbSize; |
DWORD |
dwMajorVersion; |
DWORD |
dwMinorVersion; |
LPNSPCLEANUP |
NSPCleanup; |
LPNSPLOOKUPSERVICEBEGIN |
NSPLookupServiceBegin; |
LPNSPLOOKUPSERVICENEXT |
NSPLookupServiceNext; |
LPNSPLOOKUPSERVICEEND |
NSPLookupServiceEnd; |
Г Л А ВА 14 Интерфейс Winsock 2 SPI |
457 |
LPNSPSETSERVICE NSPSetService; LPNSPINSTALLSERVICECLASS NSPInstallServiceClass; LPNSPREMOVESERVICECLASS NSPRemoveServiceClass; LPNSPGETSERVICECLASSINFO NSPGetServiceClassInfo;
} NSP_ROUTINE, FAR * LPNSP_ROUTINE;
Первое поле структуры — cbSize, указывает размер структуры NSP_ROUTINE Следующие два поля — dwMajorVersion и dwMinorVersion, включены для управления версиями поставщика. Управление версиями произвольно и не служит никакой другой цели Поставщик присваивает остальным записям соответствующие указатели на функции Например, свой адрес функции NSPSetService (независимо от того, что скрывается под этим именем) — NSPSetService. Имена функций поставщика могут быть произвольными, но их параметры и возвращаемые типы должны соответствовать определению поставщика
Единственное действие, требуемое NSPStartup^ — заполнить структуру NSP_ROUTINE. Когда поставщик успешно завершает эту и любые другие процедуры инициализации, он возвращает NO_ERROR Если произошла ошибка, NSPStartup возвращает SOCKETJERROR и выясняет код ошибки Winsock Например, если ошибка связана с распределением памяти, поставщик вызывает WSASetLastError с параметром WSA_NOT_ENOUGH_MEMORYu затем возвращает SOCKET_ERROR
Сейчас, наверное, самое время обсудить обработку ошибок в DLL поставщика. Все функции, которые вы должны реализовать для поставщика, возвращают NOERROR в случае успеха и SOCKETERROR — при неудаче. Если вы видите, что вызов возвращает ошибку, назначьте соответствующий код ошибки Winsock Иначе любое приложение, пытающееся регистрировать или вызывать службы, используя ваш поставщик пространства имен, сообщит о неудаче функции RNR, но WSAGetLastError вернет 0 Это вызовет проблемы у приложений, которые пытаются корректно обрабатывать ошибки, 0 — безусловно не то значение, которое ожидается при ошибке
ФункцияNSPCIeanup
Эта процедура вызывается при выгрузке DLL поставщика. С ее помощью вы можете освобождать любую память, выделенную процедурой NSPStartup Функция NSPCIeanup определена так-
int NSPCIeanup ( LPGUID lpProviderld );
Единственный параметр — GUID вашего поставщика пространства имен От вас требуется лишь очистка всей динамически выделенной памяти
ФункцияNSPInstallServiceClass
Функция NSPInstallServiceClass соответствует WSAInstallServiceClass и отвечает за регистрацию класса служб
int NSPInstallServiceClass ( LPGUID lpProviderld,