IP多播

使用广播服务,封包可以被发送到网络中的每个节点,而使用本节将介绍的多播服务,封包仅被发送到网络节点的一个集合。
IGMP 是IPv4 引入的管理多播客户和它们之间关系的协议。

一、多播地址

为了发送IP 多播数据,发送者需要确定一个合适的多播地址,这个地址代表一个组。IP多播地址采用D 类IP 地址确定多播的组,地址的范围是224.0.0.0~239.255.255.255。不过,有许多多播地址保留为特殊目的使用,下表列出了一些比较重要的地址(保留的IP 多播地址):
224.0.0.0    基地址(保留)
224.0.0.1     本子网上的所有节点
224.0.0.2     本子网上的所有路由器
224.0.0.4     网段中所有的 DVMRP 路由器
224.0.0.5     所有的 OSPF 路由器
224.0.0.6    所有的 OSPF 指派路由器
224.0.0.9     所有的 RIPv2 路由器
224.0.0.13     所有的 PIM 路由器

二、使用IP多播

1. 加入和离开组

加入和离开多播组可以使用setsockopt 函数,也可以使用WSAJoinLeaf 函数。使用setsockopt 函数更方便一点
有两 个套接字选项控制组的加入和离开: IP_ADD_MEMBERSHIP 和 IP_DROP_MEMBERSHIP,套接字选项级别是 IPPROTO_IP,输入参数是一个 ip_mreq 结构,定义如下:
typedef struct {
    struct in_addr imr_multiaddr; // 多播组的IP 地址
    struct in_addr imr_interface; // 将要加入或者离开多播组的本地地址
} ip_mreq;
下面的代码示例了如何加入组,其中s 是已经创建好的数据报套接字:

ip_mreq mcast;
mcast.imr_interface.S_un.S_addr = INADDR_ANY;
mcast.imr_multiaddr.S_un.S_addr = inet_addr("234.5.6.7");
int nRet = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
加入一个或者多个多播组之后,可以使用IP_DROP_MEMBERSHIP 选项离开特定的组:
ip_mreq mcast;
mcast.imr_interface.S_un.S_addr = dwInterFace;
mcast.imr_multiaddr.S_un.S_addr = dwMultiAddr;
int nRet = ::setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mcast, sizeof(mcast));

2. 接收多播数据

主机在接收IP 多播数据之前,必须成为IP 多播组的成员。为了接收发送到特定端口的多播封包,有必要绑定到那个本地端口,而不显式地指定本地地址。如果绑定套接字设置了 SO_REUSEADDR 选项,就有不止一个进程可以绑定到UDP 端口,如下代码所示。
BOOL bReuse = TRUE;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
如此一来,每个来到这个共享端口的多播或广播UDP 封包都会被发送给所有绑定到此端口的套接字。由于向前兼容的原因,这并不包括单播封包-单播封包永远不会被发送到多个套接字。
下面是接收多播封包的程序,它绑定到本地端口 4567 之后,便加入多播组234.5.6.7,循环调用recvfrom 函数接收发送到多播组中的数据:

void main()
{ 
	// 创建UDP套接字
	SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
	// 允许其他进程使用绑定的地址(端口复用)
	BOOL bReuse = TRUE;
	::setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
	// 绑定到4567 端口
	sockaddr_in si;
	si.sin_family = AF_INET;
	si.sin_port = ::ntohs(4567);
	si.sin_addr.S_un.S_addr = INADDR_ANY;
	::bind(s, (sockaddr*)&si, sizeof(si));
	// 加入多播组
	ip_mreq mcast;
	mcast.imr_interface.S_un.S_addr = INADDR_ANY;
	mcast.imr_multiaddr.S_un.S_addr = ::inet_addr("234.5.6.7"); // 多播地址为234.5.6.7
	::setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
	// 接收多播组数据
	char buf[1280];
	int nAddrLen = sizeof(si);
	while(TRUE)
	{ 
		int nRet = ::recvfrom(s, buf, strlen(buf), 0, (sockaddr*)&si, &nAddrLen);
		if(nRet != SOCKET_ERROR)
		{ 
			buf[nRet] = '\0';
			printf(buf);
		}
		else
		{ 
			int n = ::WSAGetLastError();
			break;
		}
	}
}

3. 发送多播数据

要向组发送数据,没有必要非加入那个组。在前面的例子中,以234.5.6.7 为目的地址,4567 为目的端口调用sendto 函数,即可向多播组234.5.6.7 发送数据。默认情况下,发送的IP 多播数据报的TTL 等于1,这使得它们不能被发出子网。套接字选项 IP_MULTICAST_TTL 用来设置多播数据报的TTL 值(范围为0~255),如下代码所示。

BOOL SetTTL(SOCKET s, int nTTL) // 自定义设置多播数据TTL 的函数
{ 
    int nRet = ::setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)&nTTL, sizeof(nTTL));
    return nRet != SOCKET_ERROR;
}

为了提供有意义的范围控制,多播路由器支持TTL"极限"的概念,它阻止TTL小于特定值的数据报在特定子网上传输。极限执行如下约定:
初始TTL为0         多播封包被限制在同一个主机
初始TTL为1         多播封包被限制在同一个子网
初始TTL为32     多播封包被限制在同一个站点
初始TTL为64     多播封包被限制在同一个地区
初始TTL为128     多播封包被限制在同一个大陆
初始TTL为255     多播封包没有范围限制
许多多播路由器拒绝转发目的地址在224.0.0.0~224.0.0.255 之间的任何多播数据报,不管它的TTL 是多少。这个地址范围是为路由协议和其他底层拓扑发现协议或者维护协议预留的,如网关发现和组成员报告等。每个多播传输仅从一个网络接口发出,即便是主机有多个多播接口。系统管理者在安装
过程中就指定了多播使用的默认接口。可以使用套接字选项 IP_MULTICAST_IF 改变默认的发送数据接口,如下代码所示。
struct in_addr addr;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr));
其中,addr 是本地机器上对外的接口。设置为地址 INADDR_ANY 可以恢复使用默认接口。选项 IP_MULTICAST_LOOP 可以设置多播回环是否打开。如果值为真,发送到多播地址的数据会回显到套接字的接收缓冲区。默认情况下,当发送IP 多播数据时,如果发送方也是多播组的一个成员,数据将回到发送套接字。如果设置此选项为FALSE,任何发送的数据都不会被发送回来。

4.带源地址的IP 多播

带源地址的IP 多播允许加入组时指定要接收哪些成员的数据。在这种情况下有两种方式加入组。第一种是"包含"方式,在这种方式下,为套接字指定N 个有效的源地址,套接字仅接收来自这些源地址的数据。另外一种是"排除"方式,在这种方式下,为套接字指定N个源地址,套接字将接收来自这些源地址之外的数据,也就是来自其他成员的数据。要使用"包含"方式加入多播组,应使用套接字选项 IP_ADD_SOURCE_MEMBERSHIP 和 IP_DROP_SOURCE_MEMBERSHIP。第一步是添加一个或者多个源地址。这两个套接字选项的输入参数都是一个ip_mreq_source 结构。

struct ip_mreq_source {
	struct in_addr imr_multiaddr; // 多播组的IP 地址
	struct in_addr imr_sourceaddr; // 指定的源IP 地址
	struct in_addr imr_interface; // 本地IP 地址接口
};

mr_sourceaddr 域指定了源IP 地址,套接字将接收来自此IP 地址的数据。如果有多个有效的源地址,IP_ADD_SOURCE_MEMBERSHIP 就应该被调用多次。下面的例子在本地接口上加入了多播组234.5.6.7,同时指定仅接收来自218.12.255.113 和218.12.174.222 的数据:

SOCKET s = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// 本地接口
SOCKADDR_IN localif;
localif.sin_family = AF_INET;
localif.sin_port = htons(5150);
localif.sin_addr.s_addr = htonl(INADDR_ANY);
::bind(s, (SOCKADDR *)&localif, sizeof(localif));
// 设置ip_mreq_source 结构
struct ip_mreq_source mreqsrc;
mreqsrc.imr_interface.s_addr = inet_addr("192.168.0.46");
mreqsrc.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
// 添加源地址218.12.255.113
mreqsrc.imr_sourceaddr.s_addr = inet_addr("218.12.255.113");
::setsockopt(s, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&mreqsrc, sizeof(mreqsrc));
// 添加源地址218.12.174.222
mreqsrc.imr_sourceaddr.s_addr = inet_addr("218.12.174.222");
::setsockopt(s, IPPROTO_IP, IP_ADD_SOURCE_MEMBERSHIP, (char *)&mreqsrc, sizeof(mreqsrc));

排除方式,为了加入多播组,同时排除一个或者多个源地址,加入组时使用 IP_ADD_MEMBERSHIP 选项。加入组后,便可以使用 IP_BLOCK_SOURCE 选项来指定要排除的源地址了。这里,输入参数也是 ip_mreq_source 结构。如果应用程序想从以前排除的地址接收数据,它可以通过使用IP_UNBLOCK_SOURCE选项从排除集合中移除此源地址,输入参数仍是ip_mreq_source 结构。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值