* При перепечатке материалов ссылка на www.SeoLiga.ru обязательна!
Многоадресная рассылка
19 марта 2009
В предыдущей статье мы упоминали протокол IGMP - дополнение к протоколу IP, позволяющее назначать нескольким узлам групповые адреса. С помощью этого протокола можно группе сокетов назначить один IP-адрес, и тогда все пакеты, отправленные на этот адрес, будут получать все сокеты, входящие в группу. Заметим, что не следует путать группы сокетов в терминах IGMP, и группы сокетов в терминах WinSock (поддержка групп сокетов в WinSock пока отсутствует, существуют только зарезервированные для этого параметры в некоторых функциях). В предыдущей статье мы говорили, что сетевая карта получает все IP-пакеты, которые проходят через её подсеть, но выбирает из них только те, которые соответствуют назначенному её MAC- и IP-адресу. Существуют два режима работы сетевых карт. В первом выборка пакетов осуществляется аппаратными средствами карты, во втором - программными средствами драйвера. Аппаратная выборка осуществляется быстрее и не загружает центральный процессор, но её возможности ограничены. В частности, у некоторых старых карт отсутствует аппаратная поддержка IGMP, поэтому они не могут получать пакеты, отправленные на групповой адрес, без переключения в режим программной выборки. Более современные сетевые карты способны запоминать несколько (обычно 16 или 32) групповых адресов, и, пока количество групповых адресов не превышает этот предел, могут осуществлять аппаратную выборку пакетов с учётом групповых адресов.
Windows 95 и NT 4 используют сетевые карты в режиме программной выборки пакетов. Windows 98 и 2000 по умолчанию используют сетевые карты в режиме аппаратной выборки пакетов. При этом Windows 2000 может переключать карту в режим программной выборки, если число групповых адресов, с которых компьютер должен принимать пакеты, превышает её аппаратные возможности. Windows 98 такой возможностью не обладает, поэтому программа, выполняемая в этой среде, может столкнуться с ситуацией, когда сокет не сможет присоединиться к групповому адресу из-за нехватки аппаратных ресурсов сетевой карты (программа при этом получит ошибку WSAENoBufs). WinSock предоставляет достаточно широкие возможности по управлению многоадресной рассылкой, но для их использования необходимо, чтобы выбранный сетевой протокол поддерживал все эти возможности. Поддержка многоадресной рассылки протоколом IP достаточно скудна по сравнению, например, с протоколами, применяющимися в сетях ATM. Здесь мы будем рассматривать только те возможности WinSock по поддержке многоадресной рассылки, которые могут быть использованы с протоколом IP. Протокол IP не поддерживает многоадресную рассылку при использовании TCP, поэтому всё, что будет сказано ниже, относится только к протоколу UDP. Отметим также, что при использовании многоадресной рассылки через границы подсетей роутеры должны поддерживать передачу многоадресных пакетов. Глава "Многоадресная рассылка" в рекомендованной выше книге Джонса и Оланда, к сожалению, содержит множество неточностей. Далее я буду обращать внимание на эти неточности, чтобы облегчить чтение этой книги. Многоадресная рассылка в IP является одноранговой и в плоскости управления, и в плоскости данных (в книге Джонса и Оланда вместо "одноранговая" используется слово "немаршрутизируемая" - видимо, переводчик просто перепутал слова nonrooted и nonrouted). Это значит, что все сокеты, участвующие в ней, равноправны. Каждый сокет без каких-либо ограничений может подключиться к многоадресной группе и получать все сообщения, отправленные на групповой адрес. При этом послать сообщение на групповой адрес может любой сокет, в т.ч. и не входящий в группу. Для групповых адресов протокол IP использует диапазон от 224.0.0.0 до 239.255.255.255. Часть из этих адресов зарезервирована для стандартных служб, поэтому своим группам лучше назначать адреса, начиная с 225.0.0.0. Кроме того, весь диапазон от 224.0.0.0 до 224.0.0.255 зарезервирован для групповых сообщений, управляющих роутерами, поэтому сообщения, отправленные на эти адреса, никогда не передаются в соседние подсети. Есть два варианта осуществления многоадресной рассылки с использованием IP средствами WinSock. Первый вариант связан с использованием WinSock 1 и жёстко привязан к протоколу IP. Второй вариант подразумевает использование WinSock 2 и осуществляется универсальными, не привязанными к конкретному протоколу средствами. Если рассылка будет осуществляться средствами WinSock 1, то сокет, участвующий в ней, создаётся обычным образом - с помощью функции WSASocket со стандартным набором флагов или с помощью функции Socket с обычными параметрами, задаваемыми при создании UDP-сокета. Если же используется WinSock 2, то сокет должен быть создан с указанием его роли в плоскостях управления и данных. Так как многоадресная рассылка в IP является одноранговой, все сокеты, участвующие в ней, могут быть только "листьями", поэтому сокет для рассылки должен создаваться функцией WSASocket с указанием флагов WSA_Flag_Multipoint_C_Leaf (4) и WSA_Flag_Multipoint_D_Leaf (16). В книге Джонса и Оланда на странице 313 написано, что для рассылки средствами WinSock 2 можно создавать сокет функцией Socket - это неверно. Впрочем, на странице 328 всё-таки написано, что указанные флаги использовать обязательно. Далее сокет, который планируется добавить в группу, привязывается к любому локальному порту обычным способом - с помощью функции Bind. Этот шаг ничем не отличается от привязки к адресу обычного сокета, не использующего групповой адрес. Затем выполняется собственно добавление сокета в группу. В WinSock 1 для этого надо использовать функцию SetSockOpt с параметром IP_Add_Membership, указав в качестве уровня IPProto_IP. При этом через параметр OptVal передаётся указатель на структуру ip_mreq, описанную следующим образом: struct ip_mreq { struct in_addr imr_multiaddr; struct in_addr imr_interface; }
type TIPMreq=packed record IMR_MultiAddr:TSockAddr; IMR_Interface:TSockAddr end;
Поле IMR_MultiAddr задаёт групповой адрес, к которому присоединяется сокет. У этой структуры должны быть заполнены поля sin_family (значением AF_Inet) и sin_addr. Номер порта здесь указывать не надо, значение этого поля игнорируется. Поле IMR_Interface определяет адрес сетевого интерфейса, через который будет вестись приём многоадресной рассылки. Если программу устраивает интерфейс, выбираемый системой по умолчанию, значение поля IMR_Interface.sin_addr должно быть InAddr_Any (на компьютерах с одним сетевым интерфейсом обычно используется именно это значение). Но если у компьютера несколько сетевых интерфейсов, которые связывают его с разными сетями, интерфейс для получения групповых пакетов, выбираемый системой по умолчанию, может быть связан не с той сетью, из которой они реально ожидаются. В этом случае программа может явно указать IP-адрес того интерфейса, через который данный сокет должен принимать групповые пакеты. Как и в поле IMR_MultiAddr, в поле IMR_Interface используются только поля sin_family и sin_addr, а остальные поля игнорируются. Для прекращения членства сокета в группе используется та же функция SetSockOpt, но с параметром IP_Drop_Membership. Через параметр OptVal при этом также передаётся структура ip_mreq, значимые поля которой должны быть заполнены так же, как они были заполнены при добавлении данного сокета в данную группу. Несмотря на то, что структура ip_mreq относится к WinSock 1, в модуле WinSock её описание отсутствует. Константы IP_Add_Membership и IP_Drop_Membership в этом модуле объявлены, но использовать их следует с осторожностью, потому что они должны иметь разные значения в WinSock 1 и WinSock 2. В WinSock 1 они должны иметь значения 5 и 6 соответственно, а в WinSock 2 - 12 и 13. Из-за этого нужно внимательно следить, чтобы использовались значения, соответствующие той библиотеке, из которой импортируется функция SetSockOpt: 5 и 6 при использовании WSock32.dll и 12 и 13 при использовании WS2_32.dll. В WinSock 2 для присоединения сокета к группе объявлена функция WSAJoinLeaf, имеющая следующий прототип: SOCKET WSAJoinLeaf( SOCKET s, const struct sockaddr FAR *name, int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, DWORD dwFlags);
function WSAJoinLeaf( S:TSocket; var Name:TSockAddr; NameLen:Integer; lpCallerData,lpCalleeData:PWSABuf; lpSQOS,lpGQOS:PQOS; dwFlags:DWORD):TSocket;
Параметры lpCallerData и lpCalleeData задают буферы, в которые помещаются данные, передаваемые и получаемые при присоединении к группе. Протокол IP не поддерживает передачу таких данных, поэтому при его использовании эти параметры должны быть равны nil. Параметры lpSQOS и lpGQOS относятся к качеству обслуживания, которое мы здесь не рассматриваем, поэтому их мы тоже полагаем равными nil. Параметр S определяет сокет, который присоединяется к группе, параметр Name - адрес группы, NameLen - размер буфера с адресом. Параметр dfFlags определяет, будет ли сокет использоваться для отправки данных (JL_Sender_Only, 1), для получения данных (JL_Receiver_Only, 2) или и для отправки, и для получения (JL_Both, 4). Функция возвращает сокет, который создан для взаимодействия с группой. В протоколах типа ATM подключение к группе похоже на установление связи в TCP, и функция WSAJoinLeaf, подобно функции Accept, создаёт новый сокет, подключенный к группе. При использовании UDP новый сокет не создаётся, и функция WSAJoinLeaf возвращает значение переданного ей параметра S. Номер порта в параметре NameLen игнорируется. Для получения групповых сообщений используется тот интерфейс, который система назначает для этого по умолчанию. Чтобы прекратить членство сокета в группе, в которую он был добавлен с помощью WSAJoinLeaf, нужно закрыть его с помощью функции CloseSocket. Если сокет, для которого вызывается функция WSAJoinLeaf, находится в асинхронном режиме, то при успешном присоединении сокета к группе возникнет событие FD_Connect (в книге Джонса и Оланда написано, что в одноранговых плоскостях управления FD_Connect не возникает - это не соответствует действительности). Но в случае использования ненадёжного протокола UDP возникновение этого события говорит лишь о том, что было отправлено IGMP-сообщение, извещающее о включении сокета в группу (это сообщение должны получить все роутеры сети, чтобы потом правильно передавать групповые сообщения в другие подсети). Однако FD_Connect не гарантирует, что это сообщение успешно принято всеми роутерами. UPD-сокет, присоединившийся к многоадресной группе, не должен "подключаться" к какому-либо адресу с помощью функции Connect или WSAConnect. Соответственно, для отправки данных такой сокет может использовать только SendTo и WSASendTo. Сокет, присоединившийся к группе, может отправлять данные на любой адрес, но если используется поддержка качества обслуживания, она работает только при отправке данных на групповой адрес сокета. Отправка данных на групповой адрес не требует присоединения к группе, причём для сокета, отправляющего данные, нет никакой разницы между отправкой данных на обычный адрес и на групповой. И в том, и в другом случае используется функция SendTo или WSASendTo (или Send/WSASend с предварительным вызовом Connect). Никаких дополнительных действий для отправки данных на групповой адрес выполнять не надо. Порт при этом также указывается. Как мы видели выше, номер порта при добавлении сокета в группу не указывается, но сам сокет перед этим должен быть привязан к какому-либо порту. При отправке группового сообщения его получат только те сокеты, входящие в группу, чей порт привязки совпадает с портом, указанным в адресе назначения сообщения. Если сокет, отправляющий сообщение на групповой адрес, сам является членом этой группы, он, в зависимости от настроек, может получать или не получать своё сообщение. Это определяется его параметром IP_Mulitcast_Loop, имеющим тип Bool. По умолчанию этот параметр равен True - это значит, что сокет будет получать свои собственные сообщения. С помощью функции SetSockOpt можно изменить значение этого параметра на False, и тогда сокет не будет принимать свои сообщения. Параметром IP_Multicast_Loop следует пользоваться осторожно, т.к. он не поддерживается в Windows NT 4 и требует Windows 2000 или выше. В Windows 9x/ME он тоже не поддерживается (хотя упоминания об этом в MSDN'е нет). В предыдущей статье мы говорили, что каждый IP-пакет в своём заголовке имеет целочисленный параметр TTL (Time To Live). Его значение определяет, сколько роутеров может пройти данный пакет. По умолчанию групповые пакеты имеют TTL, равный 1, т.е. могут распространятся только в пределах непосредственно примыкающих подсетей. Целочисленный параметр сокета IP_Milticast_TTL позволяет программе изменить это значение. У функции WSAJoinLeaf не предусмотрены параметры для задания адреса сетевого интерфейса, через который следует получать групповые сообщения, поэтому всегда используется интерфейс, выбираемый системой для этих целей по умолчанию. Выбрать интерфейс, который система будет использовать по умолчанию, можно с помощью параметра сокета IP_Multicast_If. Этот параметр имеет тип TSockAddr, причём значимыми полями структуры в данном случае являются Sin_Family и Sin_Addr, а значение поля Sin_Port игнорируется. Значения констант IP_Multicast_If, IP_Multicast_TTL и IP_Multicast_Loop также зависят от версии WinSock. В WinSock 1 они должны быть равны 2, 3 и 4, а в WinSock 2 - 9, 10 и 11 соответственно.