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

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


Скачать 2.88 Mb.
НазваниеЭ. Джонс, Д. Оланд
АнкорПрограммирование в сетях Windows.pdf
Дата12.10.2017
Размер2.88 Mb.
Формат файлаpdf
Имя файлаПрограммирование в сетях Windows.pdf
ТипКнига
#9346
страница35 из 50
1   ...   31   32   33   34   35   36   37   38   ...   50

ГЛАВА 13 Простые сокеты
4 1 5
рольной суммы их нужно дополнить нулевым байтом (в конце) Это допол- нительное поле не передается вместе с остальными данными Поля, необхо- димые для вычисления контрольной суммы показаны на рис 13-5 Первые три
32-битных слова составляют псевдозаголовок UDP, затем следуют заголовок
UDP и данные Заметьте так как контрольная сумма рассчитана по 16-битным значениям, может потребоваться дополнить данные нулевым байтом
32-битный IP-адрес отправителя
32 битный IP адрес получателя
8 битный протокол
16-битныи аорт отправителя
1 б-бит«а» дп и на UDP
16 битная длина UDP
16 битный порт пигучателя
16-битная контрольная сумма UOP
Данишв {и, если нужно нулевой Сайт)
TROT.
7\\
\\
s
1
«
и <
w
41 a» txPflftf" ) li) \\
Рис. 13-5. Псевдозаголовок UDP
Программа, взятая нами в качестве примера (см листинг 13-2), просто отправляет пакет UDP любому адресату IP с любого исходного адреса IP и выбранного порта Во-первых, необходимо создать простой сокет с флагом
I P HDRINCL ^
и
SOCKET
BOOL
bOpt;
\\
s = WSASocket(AF_INET, S0CK_RAW, IPPROTOJJDP, NULL, 0, ц,
WSA_FLAG_OVERLAPPED), яс i_"> '
ret = setsockopt(s, IPPROTO_IP, IP_HDRINCL, (char O&bOpt, sizeof(bOpt)),
Заметьте, что мы создали простой сокет с использованием протокола IP-
PROTOJJDP Поскольку это требует поддержки параметра IP_HDRINCL, в каче- стве платформы подходит только Windows 2000 Далее на примере Iphdnnc с поясняется, как использовать простые сокеты и параметр IP_HDRINCL для ма- нипулирования заголовками IP и UDP на исходящих пакетах
Листинг 13-2. Пример простого UDP-сокета
l|t>
**
// Имя модуля Iphdrinc с ' > >)
ID
чаи
«pragma pack(T)
«define WIN32_LEAN_AND_MEAN
«include
«include
\\
см след cmp

416
ЧАСТЬ II Интерфейс прикладного программирования Winsock
Табл. 13-2. (продолжение)
((include
«include
((define MAX.MESSAGE 4068
«define MAX_PACKET 4096
//
// Настройка некоторых значений по умолчанию
«define DEFAULT.PORT
«define DEFAULT.IP
«define DEFAULT_COUNT
«define DEFAULT.MESSAGE
5150
"10.0.0.1"
5
"This is a test"
// Определение IP-заголовока. Поля версии и длины заданы одним
// символом, поскольку мы не можем объявить два 4-битных поля,
// чтобы компилятор не выровнял их хотя бы по границе одного байта.
typedef struct ip_hdr
}
unsigned char
unsigned char
unsigned short
unsigned short
unsigned short
unsigned char
unsigned char
unsigned short
unsigned int
unsigned int
IP.HDR, »PIP_HDR,
ip_verlen;
ip_tos;
ip_totallength;
ip_id;
ip_offset;
ip_ttl;
ip_protocol;
ip_checksum;
ip_srcaddr;
ip_destaddr;
FAR» LPIP HDR;
// Определение заголовка UDP
//
typedef struct udp_hdr
{
unsigned short src_portno;
unsigned short dst_portno;
unsigned short udp_length;
unsigned short udp_checksum;
} UDP_HDR, .PUDP.HOR;
// Версия и длина IP
// Тип службы IP
// Полная длина
// Уникальный идентификатор
// Поле смещения фрагмента
// Срок жизни
// Протокол (TCP, UDP и т.п.
// Контрольная сумма IP
// Исходный адрес
// Целевой адрес
// Номер порта отправителя
// Номер порта получателя
// Длина пакета UDP
// Контрольная сумма UDP (необяз.)
// Глобальные переменные
//
unsigned long dwToIP,
dwFromlP;
unsigned short iToPort,
iFromPort;
// IP-адрес получателя
// IP-адрес отправителя (фиктивный)
// Порт получателя
// Порт отправителя (фиктивный)
Ь%

ГЛАВА 13 Простые coKVto
417
Табл. 13-2.
DWORD
char
(продолжение)
dwCount; // Количество повторов отправки
strMessage[MAX_MESSAGE]; // Отправляемое сообщение
// Описание:
// Вывод информации об использовании и выход wb
// :*-
void usage(char «progname) •>«•>
{
printf("usage: Xs [-fp:int] [-fi:str] [-tp:int] [-ti:str]\
[-n:int] [-m:str]\n", progname); \
printf(" -fp:int From (sender) port number\n"); id printf(" -fi:IP From (sender) IP address\n");
printf(" -fp:int To (recipient) port number\n"); «8
printf(" '-' -fi:IP To (recipient) IP address\n"); }
printf(" -n:int Number of times to read messaged");
printf(" -m:str Size of buffer to read\n\n");
ExitProcess(i); •» • no
} -vi
// Функция: ValidateArgs
41
// Описание:
r r
II Анализирует параметры командной строки и задает некоторые
// глобальные флаги согласно планируемым действиям
void ValidateArgs(int argc, char «*argv)
{
int i;
IToPort = DEFAULT_PORT;
IFromPort = DEFAULT_PORT;
dwToIP = inet_addr(DEFAULT_IP); С
dwFromlP = inet_addr(DEFAULT_IP);
dwCount = DEFAULT_COUNT;
strcpy(strHessage, DEFAULT_MESSAGE); H
for(i = 1; i < argc; i++)
{
if ((argv[i][0] == •-') || (argv[i][O] == '/'))
{
switch (tolower(argv[i][1]))
<
case 'f: // Исходный адрес
switch (tolower(argv[i][2]))
{
case 'p':
; It
. *' oeea
-18) \t
<
"it
см. след. стр.

418 ЧАСТЬ II Интерфейс прикладного программирования Wineoek
Табл. 13-2. (продолжение) м'^\,
if (strlen(argv[i]) > 4)
М* iFromPort = atoi(&argv[i][4]);
break;
case ' i ' :
if (strlen(argv[l]) > 4) :eNMS»w dwFromlP = inet_addr(&argv[i][4]); ф*и аовчй break;
default:
usage(argv[O]);
break;
}
break;
case ' f : // Целевой адрес switch (tolower(argv[i][2]))
case 'p':
if (strlen(argv[i]) > 4)
iToPort = atoi(&argv[i][4]);
break; ' {
case ' i ' :
if (strlen(argv[i]) > 4) V
dwToIP = inet_addr(&argv[i][4]); uxwft \\
break; \\
default: &5чпО \
%
usage(argv[O]); ,д \\
break; jai \Л
> \\
break; *ettlJ»V bit-,
case 'n': // Число повторов отправки сообщения
;
if (strlen(argv[i]) > 3) tul
dwCount = atol(&argv[i][3]);
break;
case 'm':
if (strlen(argv[i]) > 3)
strcpy(strMessage, &argv[i][3]);
break;
default: - j t
usage(argv[O]);
break; , I * 2)->aT
i " . ( (0 n»)) It
T0tU ГПI ' Vtt*
i
'> Го' J 1 ш #
//
// Функция: checksum
//
// Описание:

Г Л А В А 13 ПрОСтЫЬ<#вЙ»ТЫ
419
Табл. 13-2. (продолжение)
л л ^ ) .£-Cf .adel
//
Вычисляет 16-битную компле1ЦМЩру» сумму .'ь С-
// для указанного буфера t
ЯС
Л
А>Н
USHORT checksum(USHORT .buffer,
unsigned long cksum=0;

•'ibf while (size > 1) • )*nt
{
cksum += *buffer++;
size -= sizeof(USHORT);
}
if (size)
{
cksum += •(UCHARObuffer;
} -itet cksum = (cksum >> 16) + (cksum & Oxffff); ;l - ,.iutei cksum += (cksum » 1 6 ) ; ,,,,
tit-q return (USHORT)("cksum);
}
II
// Функция: main
//
// Описание:
// Анализирует параметры командной строки и загружает Wmsock.
// Создает простой сокет и включает параметр IP_HDRINCL.
// Затем собирает заголовки пакетов IP и UDP путем
// задания правильных значений и вычисления контрольных сумм.
// После этого вписывает данные и высылает адресату. ,ЭЛ1ЬТ
Л
Ь
rqCW
int main(int argc, char **argv)
{ <*'
WSADATA wsd; :et
SOCKET s;


Г
ц
BOOL bOpt;
struct sockaddr_in remote;
IP_HDR
UDP_HDR
i n t
DWORD
unsigned short iUdpSize,
illdpChecksumSize,
llPVersion,
ilPSize,
cksum = 0;
char buf[MAX_PACKET],
IS <*»
// Структуры IP-адресации lpHdr;
udpHdr; + • •
ret;
i;
lTotalSize, // Для заполнения разных заголовков
// требуется множество размеров
(
\\
\\
r,ioTi см. след. стр.

420 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Табл. 13-2. {продолжение) * *
• ptr = NULL; ,шм • як - «., <*и:ичН*>в \\
IN_ADDR addr; (Щл япд \\
\\
// Анализ и вывод параметров командной строки -jd* rto ТЧ0Н8Ц
ValidateArgs(argc, argv); {#- ^«'W
addr.S_un.S_addr = dwFromlP;
printf("From IP: \n Port: Xd\n", inet_ntoa(addr), iFromPort); silri*
addr.S_un.S_addr = dwToIP; }
printfC'To IP: \n Port: Xd\n", inet_ntoa(addr), iToPort); •>
printf("Message: [Xs]\n", strMessage);
printf("Count: Xd\n", dwCount); {
U
if (WSAStartup(MAKEW0RD(2,2), &wsd) != 0) >
printf("WSAStartup() failed: Xd\n", GetLastErrorO); {
return - 1 ; о
// Создание простого сокета
// '1
s = WSASocket(AF_INET, SOCK_RAW, IPPROTOJJDP, NULL, 0,0); {
if (s == INVALID_SOCKET) \\
{ fUM i »<* \\
printf("WSASocket() failed: Xd\n", WSAGetLastErrorO); V
return - 1 ; . Ю \\
> V\
>coO \\
// Разрешение заголовку содержать параметр >тв

ч
\
bOpt = TRUE; \\
ret = setsockopt(s, IPPROTO.IP, IP.HDRINCL, (char *)&bOpt, sizeof(bOpt)); \\
if (ret == SOCKET.ERROR) ;. л t Jn printf("setsockopt(IP_HDRINCL) failed: Xd\n", WSAGetLastError(W; I
return -1; '•!
} I
// Инициализация заголовка IP < I jit*t>e*S©» . j
// " I
iTotalSize = sizeof(ipHdr) + sizeof(udpHdr) + strlen(strH*tS«e*)! Я0«_ч*1
tnJE
ilPVersion = 4; , 0Я0Л
llPSize = sizeof(ipHdr) / sizeof(unsigned long); Jiorie
// Версия IP заключена в старших 4 битах ip_verlen.
// Длина заголовка IP (в 32-битных словах) - в младших 4 битах.
ipHdr.ip_verlen = (ilPVersion << 4) | ilPSize;
ipHdr.ip_tos = 0; // Тип службы IP
ipHdr.ip_totallength = htons(iTotalSize); // Полная длина пакета

г
Г Л А В А 13 Простые сокеты 421
Табл. 13-2. (продолжение)
ipHdr.ip_id = 0; // Уникальный идентификатор: приравнять 0
ipHdr.ip_offset = 0; // Поле смещения фрагмента i i p H d r . i p _ t t l = 128; // Срок жизни ipHdr.ip_protocol = 0x11; // Протокол (UDP)
f
r l v ipHdr.ip_checksum = 0 ; // Контрольная сумма IP jo ipHdr.ip_srcaddr = dwFromlP; // Адрес отправителя к>, • •,
ipHdr.ip_destaddr = dwToIP; // Адрес получателя
// . ,., .
// Инициализация заголовка UDP ibs*
1
.
// V
iUdpSize = sizeof(udpHdr) + strlen(strMessage); \\
udpHdr.src.portno = htons(iFromPort) ;
udpHdr.dst_portno = htons(iToPort) ; - udpHdr.udp_length = htons(il)dpSize) ; д udpHdr.udp_checksum = 0 ; j-q
// b*
// Формирование псевдозаголовка UDP для вычисления ,&(••
// контрольной суммы UDP. Псевдо-заголовок состоит из 32-битного ючг
// IP-адреса отправителя, 32-битного IP-адреса получателя,
// нулевого байта, 8-битного поля IP-протокола,16-битной длины UDP \
ч
// и самого заголовка UDP вместе с данными ,\
// (дополненными нулем, если их длина нечетная). щ< \\
II \\
iUdpChecksumSize = 0; \,\
ptr = buf; •I'-ne'
ZeroMemory(buf, MAX.PACKET); ,f.-oais<
memcpy(ptr, &ipHdr.ip_srcaddr, sizeof(ipHdr.ip_srcaddr));
ptr += sizeof(ipHdr.ip_srcaddr); ,y » i ^ j *
iUdpChecksumSize += sizeof(ipHdr.ip_srcaddr); ,
memcpy(ptr, &ipHdr.ip_destaddr, sizeof(ipHdr.ip_destaddr)); >stt
ptr += sizeof(ipHdr.ip_destaddr); «*• j&tp'Ti"
iUdpChecksumSize += sizeof(ipHdr.ip_destaddr); }
ptr++;
iUdpChecksumSize += 1;
memcpy(ptr, &ipHdr.ip_protocol, sizeof(ipHdr.ip_protocol));
ptr += sizeof(ipHdr.ip_protocol);
iUdpChecksumSize += sizeof(ipHdr.ip_protocol); ^
memcpy(ptr, iudpHdr.udp_length, sizeof(udpHdr.udp_length)); !
ptr += sizeof(udpHdr.udp_length);
iUdpChecksumSize += sizeof(udpHdr.udp_length);
memcpy(ptr, iudpHdr, sizeof(udpHdr));
см. след. стр.

4 2 2 ЧАСТЬ II Интерфейс прикладного программирования Wmsock
Табл. 13-2. {продолжение)
ptr += sizeof(udpHdr); < К, *«Л'
illdpChecksumSize += sizeof(udpHdr); wft ;l U
t
f o r ( i = 0; i < strlen(strMessage); i++, ptr++) ;TTxO - ,t
*ptr = strMessage[i]; d
iUdpChecksumSize += strlen(strMessage); ;t cksum = checksum((USHORT *)buf, iUdpChecksumSize); Л
udpHdr.udp checksum = cksum; ИФНШ \\
II ' \\
II Теперь соберем заголовки IP и UDP с данными, i h j b u } ^
1
- ex£24t>U£
//
чтобы отправить их
f
rf
ш
Oft'
v
'j
// <* »
т
ч)
ZeroMemory(buf, MAX_PACKET); t ptr = buf; iu memcpy(ptr, &ipHdr, sizeof(ipHdr)); ptr += sizeof(ipHdr); \\
memcpy(ptr, &udpHdr, sizeof(udpHdr)); ptr += sizeof(udpHdr); члвоцммэд* \\
memcpy(ptr, strMessage, strlen(strMessage)); itsyt \\
"Я \\
II Очевидно, что эта структура SOCKADDR_IN не играет роли: h \\
// подходит любой IP-адрес, введенный в качестве адреса получателя м \\
// в заголовок IP. Конкретный адресат в поле remote будет т т т т щ ) \\
// проигнорирован. \\
// ;0
remote.sin_family = AF_INET;
remote.sin_port = htons(iToPort); ;{ТЗЖ)А
remote.sin_addr.s_addr = dwToIP; ?
for(i = 0; i < dwCount; i++)
ret = sendto(s, buf, iTotalSize, 0, (SOCKADDR *)&remote,
sizeof(remote));
if (ret == SOCKET_ERROR)
printf("sendto() failed: Xd\n", WSAGetLastErrorO);
break;
else '" ^
printf("sent Xd bytes\n", ret);
closesocket(s) ; i»®r<-
WSACleanupQ ;
return 0;
После создания сокета с параметром IPJtiDRINCL, код начинает заполнять заголовок IP, объявленный как структура IP_HDR. Заметьте: первые два четы-

Г Л А В А 13 Простые сокеты
423
рехбитных поля объединены в одно, потому что компилятор может вырав- нивать поля только на границе минимум в 1 байт. В связи с этим, версия IP
должна быть указана в старших четырех битах. Поле ipjprotocol равно 0x11,
что соответствует UDP. Код приравнивает поле ip_srcaddr IP-адресу отпра- вителя (или тому, который вы хотите выдать за исходный), а поле ip_desta-
ddr — IP-адресу получателя. Сетевой стек вычисляет контрольную сумму IP,
поэтому она не задается кодом.
Следующий шаг — инициализация заголовка UDP. Это просто, посколь- ку в нем мало полей. Номера порта отправителя и получателя задаются на- ряду с размером заголовка UDP Поле контрольной суммы изначально рав- но 0. Хотя от вашего кода не требуется вычислять контрольную сумму UDP,
в примере это сделано, чтобы показать, как такое вычисление производится для псевдозаголовка UDP. Для простоты все необходимые поля копируются во временный символьный буфер — buf, после чего он просто передается вмес- те с длиной буфера в функцию вычисления контрольной суммы.
Наконец, перед отправкой дейтаграммы следует объединить разные час- ти сообщения в непрерывный буфер. Чтобы скопировать заголовки IP и UDP,
а потом и данные в непрерывный буфер, примените функцию тетеру, пос- ле чего вызовите функцию sendto, чтобы отправить данные. Когда вы исполь- зуете параметр IP_HDRINCL, параметр to (кому) в sendto игнорируется: дан- ные всегда отправляются узлу, который указан в заголовке IP.
Чтобы увидеть Iphdrinc.c в действии, используйте программу получателя
UDP из главы 7 — Receiver.c. Например, введите при ее запуске параметры:
Receiver.exe -р:5150 -i:xxx.xxx.xxx.xxx -n:5 -Ь:1000
Можете отбросить параметр -i, если на компьютере только один сетевой интерфейс, в ином случае укажите IP-адрес одного из интерфейсов. Если на вашем компьютере установлена Windows 2000, запустите программу
Iphdrinc.exe:
Iphdrinc.exe -f i: 1.2.3.4 -f p: 10 -tuxxx.xxx.xxx.xxx -tp: 5150 -n: 5150
IP-адрес, введенный для параметра -ti, должен совпадать с тем, на кото- ром слушает получатель. Тогда программа получателя выдаст сообщение:
[1.2.3.4:10] sent me:
[1.2.3.4:10] sent me:
[1.2.3.4:10] sent me:
[1.2.3.4:10] sent me:
[1.2.3.4:10] sent me:
'This is a test'
'This is a test'
'This is a test'
'This is a test'
'This is a test'
По завершении вызова reevfrom, Receiver с выводит сообщение об этом вместе с адресной информацией, возвращенной из структуры SOCKADDRJN,
которая передана в reevfrom. Проверить, что пакет передается именно с ва- шим заголовком, можно с помощью Microsoft Network Monitor. Возможные параметры командной строки для примера Iphdnnc таковы:
• -fi:xxx.xxx.xxx.xxx — IP-адрес отправителя пакета;
• -fp:intномер порта отправителя пакета;

4 2 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Я -ti:xxx.xxx.xxx.xxx — IP-адрес пункта назначения пакета,
К -tp:int — порт получателя пакета,
М -n:int — количество отправляемых UDP-дейтаграмм,
Ш -m:string — сообщение для отправки
Таким образом, вы можете задать исходный и целевой IP-адрес и номер порта
Резюме
Простые сокеты — мощный механизм для манипулирования базовым про- токолом Мы рассмотрели использование простых сокетов лишь для созда- ния программ ICMP и IGMP средствами Winsock, хотя они могут пригодиться в самых разных приложениях Оценить все преимущества применения про- стых сокетов и параметра IPHDRINCL вы сможете, лишь тщательно изучив протокол IP и другие вложенные в него протоколы мт
П£
i
ЭК
){t
Wl
> I

? 'i es iflfibrtql
)(J
•]
M
r]

Г Л А В А
1 4
Интерфейс Winsock 2 SPI
Можно сказать, что по сравнению с Winsock 2 API, Winsock 2 Service Provider
Interface (SPI) представляет другую сторону программирования для Winsock
С одной стороны — у вас API, с другой — SPI В главах 6-13 рассматривался интерфейс Winsock 2 API Winsock 2 предназначен для архитектуры откры-
тых систем Windows (Windows Open System Architecture, WOSA), обладающей стандартными API-интерфейсами между Winsock и приложениями Winsock,
а также SPI-интерфейсами между Winsock и поставщиками служб Wmsock (та- кими как TCP/IP) На рис 14-1 показано, как библиотека Ws2_32 dll из соста- ва Winsock 2 посредничает между приложениями и поставщиками служб
Winsock
Winsock 2 API
Winsock 2
Транспортный SPI •
Приложение
Winsock 2
Приложение
Winsock 2
Транспортные функции
Функции пространства имен
Winsock 2 DLL
Ws2_32 dll (32-bit)
4a*
87< *
Winsock 2
SPI пространства имен
TSP
;
TSP
i
NSP
NSP
••> I I
Рис. 14-1. WOSA-архитектура Winsock 2
В этой главе мы подробно рассмотрим Winsock 2 SPI и то, как разрабо- тать поставщик службы, расширив тем самым возможности Winsock 2

4 2 6 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Основы SPI
Winsock 2 SPI позволяет разработать два типа поставщиков службы постав- щик транспорта и поставщик пространства имен Поставщики транспор-
та (transport service provider, TSP), обычно называемые наборами протоко- лов (типа TCP/IP) — это службы, предоставляющие функции установления связи, передачи данных, управления потоком, контроля ошибок и т п По-
ставщики пространства имен (name space provider, NSP) — это службы, ко- торые сопоставляют атрибуты адресации сетевого протокола понятным именам, и таким образом обеспечивают независимое от протокола разреше- ние имен В этой роли выступают поддерживаемые Win32 библиотеки DLL,
подключаемые к модулю Ws2_32 dll Они обеспечивают внутреннее функци- онирование многих вызовов, определенных в Winsock 2 API >
Соглашения SPI об именах
!
Для прототипов функций Winsock 2 SPI действуют соглашения об именах префиксов Они определяют функции
Ш WSP (поставщик Winsock) — поставщика транспортной службы,
NSP (поставщик пространства и м е н ) — поставщика пространства имен,
WPU (обратный вызов поставщика Winsock) — поддержки Ws2_32 dll,
вызываемые поставщиками служб,
Ш* WSC ( к о н ф и г у р а ц и я Winsock) — установки поставщиков служб в Winsock 2
Например, функция с именем WSCInstallProvider — это функция конфи- гурации SPI
Соответствие функций Winsock 2 API и SPI
В большинстве случаев, когда приложение вызывает функцию Winsock 2,
библиотека Ws2_32 dll вызывает соответствующую функцию Winsock 2 SPI
для выполнения необходимых операций с использованием определенного поставщика службы Например, select соответствует WSPSelect, WSAConnect
WSPConnect, a WSAAccept WSPAccept Однако не для всех функций Winsock есть соответствующая функция SPI
Ш Вспомогательные функции типа htonl, htons, ntohl и ntohs реализова- ны в рамках Ws2_32 dll и не передаются поставщику службы То же верно для WSA-версий этих функций
Функции преобразования IP типа metjxddr и inetjitoa реализованы только в рамках Ws2_32 dll
Все относящиеся к IP ф у н к ц и и п р е о б р а з о в а н и я и р а з р е ш е н и я
и м е н в Winsock I I getXbyY, WSAAsyncGetXByY и WSACancelAsyncRequest, a такжеgethostname,— реализованы в рамках Ws2_32 dll.

Г Л А В А 14 Интерфейс Winsock 2 SPI
427
Winsock-функции поставщика службы, ответственные за пере-
числение и блокирующее подключение, реализованы в библиотеке
Ws2_32 dll Таким образом, функции WSAEnumProtocols, WSAIsBlocking,
WSASetBlockmgHook и WSAUnhookBlockingHook не требуются в SPI
• Коды о ш и б о к Winsock обрабатываются в рамках Ws2_32 dll Функци- ям WSAGetLastError и WSASetLastError не требуются в SPI
• Функции манипуляции объектами событий и ожидания, включая
WSACreateEvent, WSACloseEvent, WSASetEvent, WSAResetEvent и WSAWaitFor-
MultipleEvents — напрямую проецируются в вызовы Win32 и не существу- ют в SPI
Теперь посмотрим, какие Winsock API соответствуют поставщикам служ- бы Winsock 2 Все определения прототипов функций SPI вы найдете в заго- ловочном файле Ws2spi h
Поставщики транспортной службы
В Winsock 2 используются поставщики транспортной службы двух типов базовые и многоуровневые Базовые (base) поставщики службы реализуют конкретные детали сетевого транспортного протокола (типа TCP/IP), вклю- чая базовые сетевые функции протокола, такие как отправка и получение данных по сети Многоуровневые (layered) поставщики службы реализуют только высокоуровневые пользовательские функции связи и нуждаются в базовом поставщике службы для фактического обмена данными по сети
Например, вы можете написать код диспетчера безопасности данных или пропускной способности поверх существующего базового поставщика TCP/
IP На рис 14-2 показано, как установить одноуровневые или многоуровне- вые поставщики между Ws2_32 dll и базовым поставщиком
API
SPI
SPI
SPI
M
Ws2_32 dll
3NM
Многоуровневый поставщик
Многоуровневый поставщик
Базовый поставщик
П
4
\
• Г
1
!
I
1
Рис. 14-2. Архитектура многоуровневого поставщика
Этот раздел посвящен основным принципам разработки поставщика транспортной службы Однако мы не будем излагать все подробности реа- лизации конкретных функций SPI например, записи SPI-функцией WSPSend

428 ЧАСТЬ II Интерфейс прикладного программирования Winsock данных в сетевой адаптер. Вместо этого рассмотрим, как функция WSPSend
многоуровневого поставщика вызывает функцию WSPSend нижестоящего поставщика, что является требованием большинства многоуровневых по- ставщиков службы. По существу, разработка многоуровневого поставщика во многом сводится к передаче SPI-вызовов вашего поставщика следующему нижестоящему поставщику.
Мы также обсудим сложности в обработке вызовов ввода-вывода, в рам- ках описанных в главе 8 моделей Winsock. На прилагаемом компакт-диске приведен пример под названием LSP, который демонстрирует, как реали- зовать многоуровневый поставщик службы, просто рассчитывающий,
сколько байтов передано через сокет с использованием транспортного про- токола IP. Microsoft Platform SDK предоставляет более сложный пример мно- гоуровневого поставщика службы, называемого layered. Его можно найти в примерах MSDN Platform SDK по адресу ftp://ftp.microsoft.com/bussys/WinSock/
winsock2/layeredzip.
ПРИМЕЧАНИЕ В этом разделе, посвященном поставщикам транс- портной службы, мы часто используем термины «клиент SPI» и «ниже- стоящий поставщик». Клиентом SPI может быть библиотека Winsock 2
Ws2_32.dll или другой многоуровневый поставщик службы, вышестоя- щий по отношению к вашему поставщику. Клиент SPI никогда не яв- ляется самим приложением Winsock, так как приложения Winsock должны использовать Winsock 2 API, экспортируемый из Ws2_32.dll.
Термин «нижестоящий поставщик» используется только при описании аспектов разработки многоуровневого поставщика службы. Нижестоя- щий поставщик может быть другим многоуровневым или основным поставщиком службы. На компьютере порой устанавливают множе- ство многоуровневых поставщиков службы, так что многоуровневый поставщик может быть ниже вашего. !ЯЛ
Функция WSPStartup
Поставщики транспортной службы Winsock 2 реализованы как стандартные модули динамически компонуемой библиотеки Windows, в которые вы должны экспортировать функцию DUMain. Дополнительно следует экспор- тировать одну запись функции с именем WSPStartup. Когда SPI-клиент вызы- вает WSPStartup, он предоставляет 30 дополнительных SPI-функций, которые составляют поставщик транспортной службы через переданную в качестве параметра диспетчерскую таблицу функций (табл. 14-1). Ваш поставщик службы должен обеспечить реализацию WSPStartup и всех 30 функций.
Как и когда вызывается функция WSPStartup? Может показаться, что ког- да приложение вызывает WSAStartup API. Но это не так, Winsock не знает тип поставщика службы, который нужно использовать в ходе выполнения WSAS-
tartup, а определяет, какой поставщик службы нужно загрузить, основываясь на семействе адреса, типе сокета и параметрах протокола из вызова WSA-
Socket. Поэтому Winsock вызывает поставщик службы, только когда прило- жение создает сокет через вызов функций socket или WSASocket. Например,

Г Л А В А 14 Интерфейс Wmsock 2 SPI
429
если приложение создает сокет с использованием семейства адресов AFJNET
и типа сокета SOCK_STREAM, Winsock ищет и загружает соответствующий поставщик транспорта, который обеспечивает функциональность TCP/IP.
Подробнее процесс загрузки будет описан далее в этой главе, в разделе, по- священном установке поставщиков транспортной службы.
Табл. 14-1. Функции поддержки поставщика транспорта
Функция API
WSAAccept (accept также косвенно соответствует WSPAccepf)
WSAAddressToString
WSAAsyncSelect
bind
WSACancelBlochingCall
WSACleanup
dosesocket
WSAConnect (connect также косвенно
соответствует WSPConnecf)
WSADuplicateSocket
WSAEnumNetworkEvents
WSAEventSelect
WSAGetOverlappedResult
getpeemame
getsockname
getsockopt
WSAGetQOSByName
WSAIoctl
WSAJoinLeaf
listen
WSARecv (recv также косвенно соответствует WSPRecv)
WSARecvDisconnect
WSARecvFrom (recvfrom также косвенно
соответствует WSPRecvFroni)
select
WSASend (send также косвенно соответствует WSPSend)
WSASendDisconnect
WSASendTo (sendto также косвенно соответствует WSPSendTd)
setsockopt
shutdown
WSASocket (socket также косвенно соответствует WSPSockef)
WSAStringToAddress
Соответствующая функция
WSPAccept
WSPAddressToString
WSPAsyncSelect
WSPBind
WSPCancelBlockingCall
WSPCleanup
WSPCloseSocket
WSPConnect
WSPDuplicateSocket
WSPEnumNetworkEvents
WSPEventSelect
WSPGetOverlappedResult
WSPGetPeerName
WSPGetSockName
WSPGetSockOpt
WSPGetQOSByName
WSPloctl
WSPJoinLeaf
WSPListen
WSPRecv
WSPReci 'Disconnect
WSPRecvFrom
WSPSelect
WSPSend
WSPSendDisconnect
WSPSendTo
WSPSetSockOpt
WSPShutdown
WSPSocket
WSPStnngToAddress
SPI

r i
r
\
)
!
1
!
! f
ШСЙ
I
' M
I
}
i t

4 3 0 ЧАСТЬ II Интерфейс прикладного программирования Wmsock
Параметры WSPStartup
WSPStcirtup — ключевая функция, используемая для инициализации постав- щика i ранспортной службы. Она определена так.
int WSPStartup(
WORD wVersionRequested,
LPWSPDATAW lpWSPData,
LPWSAPROTOCOL_INFOW IpProtocolInfo,
WSPUPCALLTABLE UpoallTable,
LPWSPPROC_TABLE lpProcTable
);
Параметр wVersionRequested получает номер последней версии Windows
Sockets SPI, которую может использовать вызывающая программа. Ваш по- ставщик службы должен проверить это значение, чтобы убедиться, что нуж- ная версия поддерживается. Он использует параметр lpWSPData для возвра- щения информации о своей версии через структуру WSPDATA-.
typedef struct WSPData
{
WORD wVersion;
WORD wHighVersion;
WCHAR szDescription[WSPDESCRIPTION_LEN + 1];
} WSPDATA, FAR * LPWSPDATA;
В поле wVersion поставщик должен вернуть версию Winsock, которую, как ожидается, будет использовать вызывающая программа. Параметр wHigh-
Version возвращает самую последнюю версию Winsock, поддерживаемую по- ставщиком. Это обычно то же значение, что и в поле wVersion (информация по версиям Winsock подробно рассмотрена в главе 7) Поле szDescription воз- вращает строку UNICODE, завершающуюся символом null и определяющую ваш поставщик для SPI-клиента. Это поле может содержать до 256 символов.
Параметр LpProtocolInfo функции WSPStartup — указатель на структуру
WSAPROTOCOL_INFOW, которая содержит информацию о поставщике (харак- теристики протокола и детали этой структуры описаны в главе 5). Инфор- мация в WSAPROTOCOLJNFOWизвлекается библиотекой Ws2_32.dll из ката- лога поставщика служб Winsock 2, содержащего сведения о свойствах по- ставщиков служб. Мы рассмотрим записи каталога Winsock 2 в разделе, по- священном установке поставщиков транспортных служб.
Разрабатывая многоуровневый поставщик службы, вы должны обрабаты- вать параметр IpProtocolInfo уникально, ведь он задает способ, которым ваш поставщик посредничает между Ws2_32.dll и основным поставщиком служ- бы. Параметр используется для определения следующего поставщика служ- бы, нижестоящего по отношению к вашему (это может быть другой много- уровневый или базовый поставщик). В какой-то момент ваш поставщик дол- жен загрузить следующий поставщик службы, загрузив его модуль DLL и выз- вав функцию поставщика WSPStartup. Структура WSAPROTOCOLJNFOW, на которую указывает IpProtocolInfo, содержит поле ProtocolChain, определяю- щее место вашего поставщика службы в ряду других.

Г Л А В А 14 Интерфейс Winsock 2 SPI
431
Поле ProtocolChain — фактически структура WSAPROTOCOLCHAIN, опре- деленная как typedef struct .WSAPROTOCOLCHAIN
<
int ChainLen;
DWORD ChainEntries[MAX_PROTOCOL_CHAIN];
} WSAPROTOCOLCHAIN, FAR * LPWSAPROTOCOLCHAIN;
Поле ChainLen определяет, сколько уровней между Ws2_32.dll и основным поставщиком службы (включительно). Если на компьютере только один многоуровневый поставщик службы поверх протокола (такого как TCP/IP),
это значение равно 2. Поле ChainEntries — массив идентифицирующих но- меров каталога поставщика службы, уникально определяющих многоуров- невых поставщиков службы, связанных вместе для конкретного протокола.
Мы опишем структуру WSAPROTOCOLCHAIN далее в этой главе, в разделе по установке поставщиков транспортной службы.
Многоуровневый поставщик службы требует, в частности, искать поле
ProtocolChain этой структуры, чтобы выяснить ее расположение в массиве поставщиков службы (с помощью поиска записи каталога вашего уровня), а также определить следующий поставщик в массиве. Если следующий постав- щик — другого уровня, передайте структуру ipProtocolInfo функции WSP-
Startup следующего уровня без изменений. Если следующий уровень — пос- ледний элемент в массиве (то есть базовый поставщик), ваш поставщик дол- жен использовать структуру WSAPROTOCOLJNFOW базового поставщика для подстановки в структуру IpProtocolInfo при вызове функции основного по- ставщика WSPStartup. Листинг 14-1 демонстрирует, как многоуровневый по- ставщик программно управляет структурой IpProtocolInfo.
Листинг 14-1. Поиск соответствующей структуры WSAPROTOCOLJNFOW
для функции WSPStartup
LPWSAPROTOCOL_INFOW Protocollnfo;
LPWSAPROTOCOL_INFOW Protolnfo = IpProtocolInfo;
DWORD ProtocolInfoSize = 0;
// Определение, как много записей нам нужно перечислить
if (WSCEnumProtocols(NULL, Protocollnfo, &ProtocolInfoSize,
&ErrorCode) == SOCKET.ERROR)
{
if (ErrorCode != WSAENOBUFS)
{
return WSAEPROVIDERFAILEDINIT; .>- „ ,
'si ,'
it к
if ((Protocollnfo = (LPWSAPROTOC0L_INF0W) GlobalAlloc(GPTR,
ProtocolInfoSize)) == NULL)
{
return WSAEPROVIDERFAILEDINIT;
см. след. стр.

4 3 2 Ч А С Т Ь II Интерфейс прикладного программирования Winsock
Листинг 14-1. (продолжение) >;•<
if ((TotalProtocols = WSCEnumProtocols(NULL, Protocollnfo,
&ProtocolInfoSize, &ErrorCode)) == SOCKET_ERROR)
{
return WSAEPROVIDERFAILEDINIT;
// Поиск идентификационной записи нашего многоуровневого поставщика в каталоге i for ( 1 = 0 ; i < TotalProtocols; i++)
s if (memcmp (&ProtocolInfo[i].ProviderId, &ProviderGuid,
sizeof (GUID))==O)
gLayerCatld = ProtocolInfo[i].dwCatalogEntryId;
break;
// Сохранение идентификационной записи нашего поставщика в каталоге gChainld = lpProtocolInfo->dwCatalogEntryId;
// Поиск нашей идентификационной записи в цепочке протоколов for(] = 0; 1 < lpProtocolInfo->ProtocolChain.Chainl_en; j++)
{
if (lpProtocolInfo->ProtocolChain.ChainEntnes|j] ==
gLayerCatld)
{
NextProviderCatld =
lpProtocolInfo->ProtocolChain.ChainEntries[j+1];
// Проверка, является ли следующий поставщик базовый if (lpProtocolInfo->ProtocolChain.ChainLen ==
О + 2))
{
for ( 1 = 0 , i < TotalProtocols; i++)
if (NextProviderCatld ==
ProtocolInfo[i].dwCatalogEntryId)
{
Protolnfo = &ProtocolInfo[i];
break;
break;
// В этот момент Protolnfo будет содержать соответствующую
// структуру WSAPR0T0C0L_INF0W

Г Л А В А 14 Интерфейс Winsock 2 SPI 433
Параметр UpcallTable функции WSPStartup получает диспетчерскую таб- лицу обратного вызова библиотеки SPI Ws2_32 dll, содержащую указатели на вспомогательные функции, которые ваш поставщик может использовать для управления операциями ввода-вывода между ним и Winsock 2 Мы опреде- лим большинство этих функций и опишем, как их использовать, далее в этой главе, в разделе, посвященном поддержке модели ввода-вывода Winsock
Заключительный параметр WSPStartup — ipProcTable, представляет собой таблицу из 30 указателей на SPI-функции, которые должен реализовать ваш поставщик службы (табл 14-1)- Каждая SPI-функция соответствует специфи- кации параметров его API-аналога, за исключением следующих изменений
Во-первых, в каждой функции есть заключительный параметр ipErrno, который поставщик должен использовать, чтобы сообщить код ошибки Winsock, если реализация неудачна. Например, если вы реализуете WSPSend и не можете выделить память, пусть вернется ошибка WSAENOBUFS
Кроме того, функции SPI WSPSend, WSPSendTo, WSPRecv, WSPRecvFrom и
WSPIOCTL имеют дополнительный параметр ipThreadld, который определя- ет поток приложения, вызывающий функцию SPI. Эта особенность полезна для поддержки процедур завершения.
И последнее несколько функций Winsock 1 1, такие как send и recv, прямо соответствуют функциям Winsock 2. Эти функции Winsock 1.1 не соответству- ют прямо SPI-функциям, так как фактически вызывают функцию Winsock 2,
обладающую подобными возможностями Например, функция send фактичес- ки вызывает функцию WSASend, которая соответствует WSPSend (табл 14-1).
Счетчик экземпляров
В спецификации Winsock приложения могут вызывать функции WSAStartup
и WSACleanup неограниченное число раз Функции WSAStartup и WSACleanup
вашего поставщика службы будут вызываться столько же раз, сколько их API- аналоги В результате, ваш поставщик службы должен поддерживать счетчик экземпляров, указывающий, сколько раз вызвана WSPStartup. Вам следует убавлять это значение на 1 для каждого вызова WSPCleanup Цель поддерж- ки счетчика экземпляров — упростить инициализацию и очистку поставщи- ка службы Например, если ваш поставщик выделяет память для управления внутренними структурами, вы можете удерживать эту память, пока счетчик экземпляров больше 0. Когда он опустится до 0, Ws2_32.dll выгрузит ваш поставщик из памяти
Описатели сокетов
Поставщик службы должен возвращать описатели сокетов при вызове SPI- клиентом функций WSPSocket, WSPAccept и WSPJoinLeaf Это могут быть IFS- описатели, тогда поставщик службы называется IFS-поставщиком. Все базо- вые поставщики транспорта Microsoft — IFS-поставщики.
Winsock разработан так, чтобы позволить приложениям Winsock исполь- зовать функции Win32 API ReadFile и WriteFile для получения и отправки дан- ных по описателю сокета. Поэтому вы должны учитывать, как в поставщике службы создаются описатели сокета. Для приложений, вызывающих ReadFile

4 3 4 ЧАСТЬ II Интерфейс прикладного программирования Winsock и WriteFile на описателе сокета, лучше разработать IFS-поставщик, но при этом не забудьте об ограничениях ввода-вывода.
IFS-поставщик
Как мы уже упоминали, поставщики транспорта могут быть многоуровневы- ми или базовыми. Если вы разрабатываете базовый IFS-поставщик, он будет иметь компонент ОС режима ядра, и это позволит поставщику Winsock со- здавать описатели, которые могут использоваться подобно описателям фай- ловой системы в вызовах ReadFile и WriteFile. Разработка ПО режима ядра —
за рамками этой книги. Если вы хотите узнать о ней больше, см. MSDN Device
Development Kit (DDK).
Многоуровневый поставщик службы также может стать IFS-поставщиком,
но только если расположен поверх существующего базового IFS-поставщи- ка. При этом описатель сокета нижестоящего IFS-поставщика, полученный в вашем многоуровневом поставщике, напрямую передается SPI-клиенту.
Передача описателей сокета напрямую от нижестоящего поставщика огра- ничивает функциональные возможности многоуровневого поставщика сле- дующим образом.
Прежде всего, функции WSPSend и WSPRecv многоуровневого поставщи- ка не будут вызываться, если функции ReadFile и WriteFile вызываются по сокету. Эти функции обойдут многоуровневого поставщика и напрямую вы- зовут реализацию базового IFS-поставщика.
Многоуровневый поставщик также не сможет выполнить заключитель- ную обработку запросов перекрытого ввода-вывода, поступивших на порт завершения. Заключительная обработка порта завершения полностью обхо- дит многоуровневого поставщика.
Если ваш многоуровневый поставщик должен контролировать весь ввод- вывод, придется разработать многоуровневый He-IFS-поставщик.
Всякий раз, когда IFS-поставщик (многоуровневый или базовый) создает новый описатель сокета, требуется поставщик для вызова WPUModifylFS-
Handle до предоставления нового описателя SPI-клиенту. Это позволяет Win- sock Ws2_32 dll существенно упростить процесс идентификации связанно- го с данным сокетом IFS-поставщика службы, когда Win32 API (такие как
ReadFile и WriteFile) выполняют ввод-вывод через сокет. WPUModifylFSHandle
определена таю
SOCKET WPUModifyIFSHandle(
DWORD dwCatalogEntryld,
4
^ ,,Q
SOCKET ProposedHandle,
LPINT lpErrno --,„ -, f,
Поле dtvCatalogEntiyld определяет идентификатор каталога вашего постав- щика службы Параметр ProposedHandle представляет IFS-описатель, выделен- ный этим поставщиком (если он базовый) Если вы разрабатываете много- уровневый IFS-поставщик, этот описатель будет передаваться от нижестояще- го поставщика Параметр lpErrno получает информацию о конкретном коде ошибки Winsock, если эти функции дают сбой со значением INVALID SOCKET.

1   ...   31   32   33   34   35   36   37   38   ...   50


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