Программирование в сетях Windows
.pdf320 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
Листинг 11-1. (продолжение)
DWORD i=0;
// Анализ командной строки и загрузка Winsock
ValidateArgs(argc, argv);
if (WSAStartup(MAKEW0RD(1, 1), &wsd) != 0)
printf("WSAStartup failed\n"); return - 1 ;
// |
Создание сокета. В |
Winsock 1 вам |
не потребуется никаких специальных |
// |
флагов для указания |
многоадресной |
рассылки |
if ((sockM = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID.SOCKET)
printf("socket failed with: Xd\n", WSAGetLastErrorO); WSACleanupO;
return - 1 ;
// Привязка сокета к локальному интерфейсу. |
Это нужно для приема данных |
{ |
|
|
||||
local.sin_family = AF_INET; |
|
|
|
|
|
|||
local.sin_port |
= htons(iPort); |
|
|
|
|
|||
local.sin_addr.s_addr = dwlnterface; |
|
|
|
|
||||
if |
(bind(sockM, |
(struct |
sockaddr *)&local, |
|
|
|
\\ |
|
|
sizeof(local)) == SOCKET.ERROR) |
|
|
«wnO |
v. |
|||
{ |
|
|
|
|
' |
|
,tk |
\\ |
|
printffbind |
failed with: Xd\n", WSAGetLastErrorO); |
.а |
V |
|
|||
|
closesocket(sockM); |
|
|
|
|
ct |
||
|
WSACleanupO; |
|
|
|
|
|
|
|
|
return-1; |
|
|
|
, ,„41 |
•(* |
||
|
|
|
|
|
|
In.1 |
|
|
// |
Настройка структуры |
im_req для указания |
группы, к которой мы |
|
|
} |
||
// хотим присоединиться, |
и интерфейса |
< |
|
ATAQAS |
||||
// |
|
|
|
|
u |
Mj\ttomk9Q& |
|
|
remote.sin_family |
= |
AF_INET; |
|
|
|
|
||
remote.sin_port |
|
= |
htons(iPort); |
|
|
|
|
|
remote.sin_addr.s_addr = |
dwMulticastGroup; |
f№%ajql |
Л*, |
|
|
|||
|
|
|
|
|
T? |
|
|
|
mcast.imr_multiaddr.s_addr = dwMulticastGroup; |
I |
|
|
|||||
mcast.imr_interface.s_addr |
= dwlnterface; |
|
|
|
|
if (setsockopt(sockM, IPPR0TO_IP, IP_ADD_MEMBERSHIP, (char *)&mcast, sizeof(mcast)) == SOCKET.ERROR)
printf("setsockopt(IP_ADD_MEMBERSHIP) failed: Xd\n",
322 ЧАСТЬ II Интерфейс прикладного программирования Winsock
Листинг 11-1. |
|
(продолжение) |
|
|
|
|
|
|
|
|
|
|
|
|
|||
|
return - 1 ; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
} |
|
|
|
|
|
|
|
|
|
|
|
|
и в ) : |
|
|
;Э |
|
r e c v b u f [ r e t ] |
= 0 ; |
|
|
|
|
|
g ^ |
|
|
|
'<(> |
|
|
-•*' |
|||
printf("RECV: |
- Xs' |
from |
<Xs>\n", |
recvbuf, |
|
|
|
|
|
|
i |
||||||
|
i n e t _ n t o a ( f r o m . s i n _ a d d r ) ) ; |
|
|
|
|
|
|
|
|
|
{ |
||||||
|
|
|
|
|
|
|
|
|
|
та) JTT RH««»*ine |
к |
|
itH \\ |
||||
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
else |
|
|
// Сервер |
|
|
|
|
|
|
|
|
|
;в. |
||||
// Отправка |
порции данных |
|
|
|
|
|
|
|
|
|
|
|
< V14}O<&(« isda) |
||||
// |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
J |
f o r ( i = |
0; |
i < dwCount; |
i++) |
|
|
|
|
|
|
|
|
|
|
ц |
|||
sprintf(sendbuf, |
"server 1: This |
is a test: Xd", |
i ) ; |
|
|
|
J |
||||||||||
if (sendto(sockM, |
(char |
*)sendbuf, |
strlen(sendbuf), |
0, |
|
|
M |
||||||||||
|
(struct |
sockaddr |
*)&remote, |
|
|
|
|
|
|
|
;f- |
niuJei |
|||||
|
sizeof(remote)) |
== |
SOCKET_ERROR) |
rV., t, |
, |
;; |
|
|
|
|
{ |
||||||
{ |
|
|
|
|
|
|
|
|
|
IW»CI |
oit |
« u i |
.wvren |
тщпв£ \S |
|||
|
printf("sendto failed with: Xd\n"ipbncW # f> ГО вноЬоШ a OTS< \\ |
||||||||||||||||
|
|
WSAGetLastErrorO); |
|
|
|
|
|
|
|
|
|
|
\\ |
||||
|
closesocket(sockM); |
t ( |
**.»<*} |
> |
wysw» аяя «р»#Й1#%*ВД4Лк) *'t |
||||||||||||
|
WSACleanupO; |
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
||
|
return - 1 ; |
|
|
|
|
|
|
|
|
|
|
;0 |
» |
lev.t^e |
|||
} |
|
|
|
|
|
>( |
' ч" |
ОТО"""" |
' |
' |
f qo^sf |
|
*fc) Iri |
||||
Sleep(500); |
|
|
|
|
|
'ta}i |
|
|
|
>Я(« |
|
|
|
||||
} |
|
|
|
|
|
|
|
|
JW |
|
|
|
^*)^ |
|
|
|
|
// Выход из |
группы |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (setsockopt(sockM, IPPR0T0_IP, IP_DROP_MEMBERSHIP, |
|
|
|
|
|
|
|||||||||||
(char *)&mcast, sizeof(mcast)) == S0CKET_ERR0R) |
|
|
|
|
|
|
|
||||||||||
< |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
printf("setsockopt(IP_DROP_MEMBERSHIP) failed: Xd\n", |
|
|
|
|
< |
||||||||||||
WSAGetLastErrorO); |
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
closesocket(sockM); |
|
|
|
|
it), |
|
|
|
|
|
|
& |
^ |
} |
|||
WSACleanupO; |
|
|
|
|
|
' |
|
|
|
|
|
|
|
|
|
\\ |
|
return 0; |
|
|
|
|
|
|
|
|
|
|
|
|
*b |
> |
i |
;0 |
|
Одно предостережение при организации рассылки в Winsock 1: используйте корректные заголовочный файл и библиотеки для компоновки. Если вы загружаете библиотеку Winsock 1.1, подключите Winsock.h и компонуйте его с Wsock32.1ib. Для версии 2 или выше подключайте Winsock2.h и Ws2tcpip.h и компонуйте их с Ws2_32.1ib. Это необходимо, поскольку существует два множества значений для констант IP'_ADD_MEMBERSHIP, IP_DROP_MEMBERSHIP, IPJWULTICASTJF и IP ^MULTICAST LOOP. Исходная спецификация значений, написанная Стивеном Дирингом (Stephen Deering), никогда офи-
" ' " Г Л А В А 11 Многоадресная рассылка |
323 |
'циально не включалась в спецификацию Winsock. В результате они изменились в спецификации Winsock 2. Конечно, если вы используете раннюю вер- С(ию Winsock, компоновка с wsock32.lib решит все проблемы и константы получат корректные значения даже при запуске на компьютере с Winsock 2.
РассылкасредствамиWinsock2
Многоадресная рассылка в Winsock 2 немного сложнее, чем в Winsock 1, но поддерживает разные протоколы, что дает дополнительные возможности, например, использовать Quality of Service (QoS). Кроме того, рассылка в Winsock 2 позволяет применять протоколы, поддерживающие маршрутизируемые схемы. Параметры сокета больше не нужны для инициализации членства в группе — им на смену пришла функция WSAJoinLeaf.
SOCKET WSAJoinLeaf( SOCKET s,
const struct sockaddr FAR * name, int namelen,
LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS,
LPQOS lpGQOS, DWORD dwFlags
);
Параметр 5 — описатель сокета, возвращенный WSASocket. Переданный сокет должен быть создан с соответствующими флагами, иначе WSAJoinLeaf вернет ошибку WSAEINVAL. Помните о необходимости задать два флага: один показывает, будет ли этот сокет маршрутизируемым в плоскости управления, другой — будет ли сокет маршрутизируемым в плоскости данных. Флаги для плоскости управления — WSA_FIAG_MULTIPOINT_C_ROOT и WSA_FLAG_MULTIPOINT_C_LEAF. Флаги для плоскости данных - WSA_FLAG_MULTIPOINT_
D_ROOTKWSA_FLAG_MULTIPOINT_D_LEAF.
Второй параметр — структура SOCKADDR, специфичная для используемого протокола. Для маршрутизируемых схем управления (например, ATM) этот адрес указывает клиента, которого нужно пригласить, а для немаршрутизируемых схем (например, IP) — это адрес группы, к которой узел присоединяется.
Параметр namelen — длина в байтах параметра пате. Параметр lpCallerData используется для передачи буфера данных партнеру в установленном сеансе, a lpCalleeData указывает буфер, который будет передан обратно. Эти два параметра пока не реализованы на платформах Windows и должны быть равны NULL. Параметр lpSQOS задает структуру FLОWSPEC, указывающую требуемую пропускную способность для приложения (подробнее о QoS — в главе 12.) Параметр lpGQOS игнорируется, поскольку ни одна из платформ Windows не поддерживает группы сокетов. Последний параметр — dwFlags, показывает роль узла: отправка данных, прием данных, или и то, и другое. Возможные значения —JL_SENDER_ONLYJL_RECEIVER_ONLYviJL_BOTH.
324 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
I |
|
|
7 |
Функция возвращает описатель SOCKET для сокета, связанного с многот адресной группой. Если вызов WSAJoinLeafвыполнен с асинхронным (неблокирующим) сокетом, возвращаемый описатель сокета не пригоден к использованию, пока не завершится операция присоединения. Например, в этом случае после вызова WSAAsyncSelect или WSAEventSelect описатель не будет действителен, пока исходный сокет 5 не вернет сообщение FD_CONNECT. Сообщение FD_CONNECT генерируется только в маршрутизируемой схеме управления, в которой параметр пате задает конкретный адрес конечной точки.
В табл. 11-1 перечислены обстоятельства, при которых приложение получает сообщение FD_CONNECT. Вы вправе аннулировать ожидающий выполнения запрос на присоединение для этих неблокирующих режимов, вызвав closesocket на исходном сокете. Корневой узел в многоточечном сеансе может вызывать WSAJoinLeaf один или несколько раз, чтобы добавить несколько листовых узлов; впрочем, в каждый момент времени ожидать выполнения может только один запрос на многоточечное соединение.
Табл.11-1.ДействияWSAJoinLeaf
Плоскость s |
Имя |
Действие |
Прием |
Возвращение |
|
|
управления |
|
|
уведомления |
описателя |
|
|
|
|
|
FDCONNECT сокета |
|
|
|
Маршрутиc_root Адрес Корень |
Да |
Используется для |
||||
зируемая |
листа |
приглашает |
|
уведомленияFDCLOSE |
||
|
|
лист |
|
и для отправки данных |
||
|
|
Листини- |
|
только этому листу |
||
cjeaf |
Адрес |
Да |
Дубликат* |
.тэнсп- |
||
|
корня |
циирует |
|
|
|
|
|
|
соединение |
|
|
'ыесж..- |
|
|
|
с корнем |
|
|
|
|
Немаршруc_root — |
Невозможная |
— |
_ |
оэроп: |
||
тизируемая |
|
комбинация |
|
|
А1ОЧ\ТV |
|
cjeaf |
Адрес |
Лист присое- |
Нет |
Дубликат 5 |
>ОШ <\ |
группы диняется к группе
Как мы уже упоминали, запрос на присоединение для неблокирующих сокетов не может завершиться немедленно. Если сокет переведен в неблокирующий режим посредством ioctlsocket и команды FIONBIO, вызов WSAJoinLeafне вернет ошибку WSAEWOULDBLOCK, поскольку эта функция фактически выдаст сообщение об успешном запуске Заметьте, что в асинхронной модели ввода-вывода единственный способ узнать об успешном запуске — сообщение FD_CONNECT. (Подробнее об асинхронных моделях WSAAsyncSelect и WSAEventSelect — в главе 8.) Блокирующие сокеты не могут уведомить приложение об удачном или ошибочном завершении WSAJoinLeaf. Другими словами, применять неблокирующие сокеты не стоит, поскольку нет однозначного способа определить успешно ли присоединение к группе, пока сокет не будет задействован в последующих вызовах Winsock (которые вернут ошибку, если присоединения не произошло).
Г Л А В А 11 Многоадресная рассылка |
325 |
Описатель сокета, возвращенный WSAJoinLeaf, зависит от того, является ли сокет маршрутизируемым или листовым узлом. Для маршрутизируемого узла параметр пате указывает адрес конкретного листа, который приглашается в многоточечный сеанс. Чтобы c_root поддерживал членство листьев, WSAJoinLeaf возвращает для листа новый описатель сокета.
Новый сокет имеет те же свойства, что и корневой описатель, использованный для приглашения, включая любые асинхронные события, зарегистрированные по асинхронным моделям ввода-вывода типа WSAEventSelect и WSAAsyncSelect. Впрочем, эти новые сокеты следует применять только для получения уведомления FD_CLOSE от листа. Любые данные, которые нужно отправить многоадресным группам, должны отправляться через сокет cjroot. Иногда вы можете отправить данные на сокет, возвращенный WSAJoinLeaf, но их получит только лист, соответствующий этому сокету (это позволяет протокол ATM). Наконец, чтобы удалить листовой узел из многоточечного сеанса, корень просто вызывает closesocket на сокете, соответствующем этому листу.
С другой стороны, когда WSAJoinLeafвызывается с листовым узлом, параметр пате задает адрес либо корневого узла, либо многоадресной группы. В первом случае присоединение инициируется листом, что в настоящее время не поддерживает ни один протокол (спецификация ATM UNI 4.0 будет делать это). Второй пример — рассылка в IP. В любом случае описатель сокета, возвращенный WSAJoinLeaf — это тот же описатель сокета, что был передан в 5. Когда вызов WSAJoinLeaf служит для присоединения со стороны листа, корневой узел слушает входящие соединения, используя методы bind, listen и accept/WSAAccept, как обычно на сервере. Когда приложение хочет удалить себя из многоадресного сеанса, вызывается closesocket на том сокете, который прекращает членство (при этом также освобождаются ресурсы сокета). Табл. 11-1 резюмирует действия, выполняемые в зависимости от типа плоскости управления, и параметры, которые передаются на сокет. С ее помощью также можно определить, возвращается ли новый описатель сокета после удачного вызова функции и получает ли приложение уведомление FD_CONNECT.
Допустим, приложение вызывает accept или WSAAccept, чтобы ожидать приглашения от корня, либо выполняет роль корня, чтобы ожидать запросы на присоединение от листьев. Тогда функция возвращает сокет, который является описателем сокета c_leaf (такой же возвращает WSAJoinLeaf). Для работы с протоколами, которые поддерживают соединения, инициированные как корнем, так и листом, в качестве входного параметра для WSAJoinLeaf Допустимо передавать слушающий сокет c_root.
После вызова WSAJoinLeaf возвращается новый описатель сокета. Этот описатель не применяется для отправки и приема данных: он просто показывает, что приложение — член многоадресной группы. Для операций отправки и приема используется исходный описатель сокета, полученный от
WSASocket и затем переданный в WSAJoinLeaf. Вызов closesocket для нового описателя прекратит членство приложения в группе. В результате вызова closesocket на сокете c_root все связанные с ним узлы c_leaf, использующие асинхронную модель ввода-вывода, получат уведомление FD CLOSE.
326 |
ЧАСТЬ II Интерфейс прикладного программирования Winsock |
Примермногоадреснойрассылки вIP-сетисредствамиWinsock2
Листинг 11-2 содержит программу Mcastws2.c, которая иллюстрирует присоединение и выход из многоадресной группы с помощью WSAJoinLeaf. Это измененный пример организации IP-рассылки средствами Winsock 1. Отличие в том, что вызовы присоединения (выхода) здесь переписаны под WSAJoinLeaf.
Листинг 11-2. Пример организации многоадресной рассылки средствами Winsock 2
// Модуль: Mcastws2.c |
|
|
// |
|
|
«include |
<winsock2.h> |
|
«include |
<ws2tcpip.h> |
|
«include |
<stdio.h> |
|
«include |
<stdlib.h> |
|
«define |
MCASTADDR |
"234.5.6.7" |
«define |
MCASTPORT |
25000 |
«define |
BUFSIZE |
1024 |
«define |
DEFAULT_COUNT |
500 |
BOOL |
bSender |
= FALSE, |
// Действовать как отправитель? |
|
|||||
|
bLoopBack = FALSE; |
// Запретить |
образование петли? |
|
|||||
DWORD |
dwlnterface, |
// Локальный интерфейс для привязки |
|
||||||
|
dwMulticastGroup, |
// Многоадресная |
группа, к которой присоединиться |
||||||
|
dwCount; |
|
// Количество |
сообщений |
для отправки/приема |
||||
short |
iPort; |
|
// Номер |
используемого |
порта |
^ |
|||
|
|
|
|
|
|
|
|
JK'I |
Н Оз |
II |
|
|
|
|
|
|
|
Я'' |
.ИНТ |
// Функция: usage |
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
( С 1 " • "> |
|
I/ Описание: |
|
|
|
|
|
|
|
|
|
// |
Вывод информации об |
использовании |
и |
выход |
|
|
|
||
// |
|
|
|
|
|
|
|
|
|
void |
usage(char •progname) |
|
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
|
|
printf("usage: Xs -s -m:str -p:int -i:str -1 -n:int\n", |
|
||||||||
|
progname); |
|
|
|
|
|
|
|
|
printfC |
-s |
Act as server |
(send data); otherwise\n"); |
|
|||||
printfC |
|
receive data.\n"); |
|
|
|
||||
printfC |
-m:str |
Dotted decimal |
multicast |
IP addres " |
|
||||
|
"to Join\n"); |
|
|
|
|
|
|
|
|
printfC |
|
The default |
group is: |
Xs\n", |
MCASTADDR); |
|
|||
printfC |
-p:lnt |
Port number to |
use\n"); |
|
|
|
|||
printfC |
|
The default |
port |
is: |
Xd\ri", |
MCASTPORT); |
|
||
printf(" |
-i:str |
Local interface to bind to; by default \n"); |
|