IP 多播(也称多址广播或组播)技术,是一种允许一台或多台主机(多播源)发送单一数据包到多台主机(一次的,同时的)的 TCP/IP 网络技术。多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特例。
多播作为一点对多点的通信,数据的收发仅仅在同一分组中进行,是节省网络带宽的有效方法之一。
IP 多播应用大致可以分为三类:点对多点应用,多点对点应用和多点对多点应用。
- 点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。
- 多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。
- 多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等。
多播地址
IP 多播通信必须依赖于 IP 多播地址,在 IPv4 中它是一个 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:
- 局部链接多播地址范围在 224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包
- 预留多播地址为 224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议
- 管理权限多播地址为 239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围
一些多播组地址被 IANA 确定为知名地址,它们也被当作永久主机组,这和 TCP 及 UDP 中的知名端口相似。同样,这些知名多播地址在 RFC 最新分配数字中列出,注意这些多播地址所代表的组是永久组,而它们的组成员却不是永久的。这些地址如下:
- 224.0.0.1 所有组播主机
- 224.0.0.2 所有组播路由器
- 224.0.0.4 DRMRP 路由器
- 224.0.0.5 所有 OSPF 的路由器
- 224.0.0.6 OSPF 指派路由器
- 224.0.0.9 RPIv2 路由器
- 224.0.0.10 EIGRP 路由器
- 224.0.0.13 PIM 路由器
- 224.0.0.22 IGMPv3
- 224.0.0.25 RGMP
- 224.0.1.1 NTP 网络时间协议
多播地址与 MAC 地址的映射
使用同一个 IP 多播地址接收多播数据包的所有主机构成了一个主机组,也称为多播组。一个多播组的成员是随时变动的,一台主机可以随时加入或离开多播组,多播组成员的数目和所在的地理位置也不受限制,一台主机也可以属于几个多播组。
这个我们可以这样理解,多播地址就类似于 QQ 群号,多播组相当于 QQ 群,一个个的主机就相当于群里面的成员。
IPv4 的 D 类地址是多播地址。IEEE 把一块以太网多播组地址分给 IANA 以支持IP多播。块的地址都以 01:00:5e 开头,第 25 位为 0,低 23 位为 IPv4 多播地址( D类地址 )的低 23 位。IPv4 多播地址与 MAC 地址的映射关系如图所示:
由于多播地址( D类地址 )中的最高 5bit 在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。32 个不同的多播组号被映射为一个以太网地址(32个多播地址会映射成单个以太网地址)。例如,多播地址 224.128.64.32(十六进制 e0.80.40.20)和 224.0.64.32(十六进制 e0.00.40.20)都映射为同一以太网地址 01:00:5e:00:40:20。
下面是几个特殊的IPv4多播地址:
224.0.0.1是所有主机组。子网上所有具有多播能力的节点(主机、路由器或打印机等)必须在所有具有多播能力的接口上加入该组。
224.0.0.2是所有路由器组。子网上所有多播路由器必须在所有具有多播能力的接口上加入该组。
既然地址映射是不唯一的,那么设备驱动程序或 IP 层就必须对数据报进行过滤。因为网卡可能接收到主机不想接收的多播数据帧,如下图,假如主机 1 加入的多播为 224.128.64.32, 主机 2 加入的多播为 224.0.64.32,我们想给 224.0.64.32 所在的多播组 ( 主机 2 ) 发送信息,数据经过网卡时,224.128.64.32 (主机 1 ) 和 224.0.64.32 (主机 2 ) 所在多播组的网卡都会收到数据,因为它们的 MAC 地址都是 01:00:5e:00:40:20。这时候,如果网卡不提供足够的多播数据帧过滤功能,设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤,这个过滤过程是网络驱动或IP层自动完成。
右侧主机上的接收应用进程启动,并创建一个UDP套接字,捆绑端口123到该套接字上,然后加入多播组224.0.1.1。这种“加入”操作是通过调用setsockopt完成。
上述操作完成之后,IPv4层内部保存这些信息,并告知合适的数据链路接收目的以太网地址为01:00:5e:00:01:01的以太网帧。该地址就是接收应用进程刚加入的多播地址对应的以太网地址。
下一个步骤是左侧主机上的发送应用进程创建一个UDP套接字,往IP地址224.0.1.1的123端口发送一个数据报。发送多播数据报无需任何特殊处理:发送应用进程不必为此加入多播组。
发送主机把该IP地址转换成相应的以太网目的地址,再发送承载该数据报的以太网帧。注意该帧中同时含有目的以太网地址和目的IP地址。
假设中间主机不只备IPv4多播能力。它将完全忽略该帧,因为:
- 该帧的目的以太网地址不匹配该主机的接口地址;
- 该帧的目的以太网地址不是以太网广播地址;
- 该主机的接口未被告知接收任何组地址(高序字节的低序位被置为1的以太网地址)
该帧基于我们所称的“不完备过滤”被右侧主机的数据链路接收,其中的过滤操作由相应接口使用该帧的以太网目的地址执行。我们之所以说这种过滤不完备是因为尽管告知该接口接收以某个特定以太网组地址为目的地址的帧,通常它也会接收以其他以太网组地址为目的地址的帧。
当我们告知一个以太网接口接收目的地址为某个特定以太网组地址的帧时。许多当前的以太网接口卡对这个地址应用某个散列(hash)函数,计算出一个介于0和511之间的值。当有一个目的地为某个组地址的帧在线缆上经过时,接口对其目的地址应用同样的散列函数,计算出一个介于0和511之间的值。如果该值与之前的值匹配,那就接收这个帧;否则忽略这个帧。
右侧主机的数据链路收取该帧后,把由该帧承载的分组传递到IP层,因为该以太网帧的类型为IPv4。既然收到的分组以某个多播IP地址作为目的地址,IP层于是比较该地址和本机的接收应用进程已经加入的所有多播地址,根据比较结果确定是接受还是丢弃该分组。我们称这个操作为完备过滤,因为它基于IPv4报头中完整的32位D类地址执行。在本例子中,IP层接受该分组并把承载在其中的UDP数据报传递到UDP等,UDP层再把承载在UDP数据报中的应用数据报传递到绑定了端口123的套接字。
该图没有展示以下三种情况:
- 运行所有加入多播地址为255.0.1.1的某个应用进程主机。既然多播地址组id高在到以太网地址的映射中被忽略,该主机的接口也将接收目的以太网地址为01:00:5e:00:01:01的帧,这种情况该帧承载的分组将由ip层中的完备过滤丢弃
- 运行所加入多播地址符合以下某个应用进程的一个主机:由这个多播地址映射成的以太网地址恰好和01:00:5e:00:01:01一样被该主机执行非完备过滤的接口散列到同一个结果,该接口也将接收目的的以太网地址为01:00:5e:00:01:01的帧,直到数据链路层或ip层丢弃
- 目的地为相同多播组(244.0.1.1)不同端口(如:4000)的一个数据报,右侧主机仍然接收该数据报,并由ip层接收传递给udp层,不过udp层将丢弃它(假设绑定4000端口的进程不存在)
多播的缺点
与单播相比,组播没有补包机制,因为组播采用的是UDP的传输方式,并且不是针对一个接受者,所以无法有针对的进行补包。所以直接用组播协议传输的数据是不可靠的。
根据接收者对组播源处理方式的不同,组播模型分为以下两大类:
ASM模型:即任意源组播模型。在ASM模型中,任一发送者都可作为组播源向某组播组地址发送组播信息,接收者通过加入由该组播组地址标识的组播组以获得发往该组播组的组播信息。在ASM模型中,接收者无法预先知道组播源的位置,但可以在任意时间加入或离开组播组。
SSM模型:即指定信源组播模型。在现实生活中,用户可能只对某些组播源发送的组播信息感兴趣,而不愿接收其它源发送的信息。SSM模型为用户提供了一种能够在客户端指定组播源的传输服务。
在ASM模型下,接收者无法选择组播源,只能被动地接收所有组播源的信息,而SSM模型的提出则为指定源组播提供了解决方案。
广域网上的多播
多播相对于广播的优势在于不会给对多播分组不感兴趣的主机增加额外负担。广域网上也可以使用多播,比如下图
假设其中的5台主机启动了某个程序,这5个进程都加入了一个给定多播组,另外假设每个多播路由器与其相邻多播路由器的通信使用某个多播路由协议,以MRP指称。
当某个主机上的一个进程加入一个多播组时,该主机向所有直接连接的多播路由器发送一个IGMP消息,告知它们本主机已加入了那个多播组,多播路由器随后使用MRP交换这些信息,这样每个多播路由器就知道在收到目的地为所加入多播地址的分组时该如何处理。
假设左上方主机的一个进程开始发送目的地为那个给定多播地址的分组。如下图:
跟踪这些多播分组从发送进程游走到所有接收进程所经历的步骤如下:
- 这些分组由发送进程多播发送,接收主机H1接收这些分组(因为它已经加入给定多播组),多播路由器MR1也接收这些分组(因为每个多播路由器都必须接收所有多播分组)。
- MR1把这些多播分组转发到MR2,因为MRP已经告知MR1:MR2需要接收目的地为给定多播组的分组。
- MR2在直接连接的局域网上多播发送这些分组,因为该局域网上的主机H2和H3属于该多播组。MR2还向MR3发送这些分组的一个副本。
- 像MR2那样对分组进行复制是多播转发所特有的。单播分组在被路由器转发时从不被复制。
- MR3把这些多播分组发送到MR4,但是不在直接连接的局域网上多播这此分组,因为我们假设该局域网上没有主机加入该多播组。
- MR4在直按连接的局域网上多播发送这些分组,因为该局域网上的主机H4和HS属于该多播组。它并不向MRS发送这些分组的一个副本,因为直接连接MR5的局域网上没有主 机属于该多播组,而MR4已经根据与MRS交换的多播路由信息知道这一点。
多播编程
- 建立一个socket;
- 设置多播的参数,例如超时时间TTL,本地回环许可LOOP等
- 加入多播组
- 发送和接收数据
- 从多播组离开
多播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。
getsockopt()/setsockopt()的选项 |
含 义 |
IP_MULTICAST_TTL |
设置多播组数据的TTL值 |
IP_ADD_MEMBERSHIP |
在指定接口上加入组播组 |
IP_DROP_MEMBERSHIP |
退出组播组 |
IP_MULTICAST_IF |
获取默认接口或设置接口 |
IP_MULTICAST_LOOP |
禁止组播数据回送 |
IP_MULTICASE_TTL
选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值,例如:
unsigned char ttl=255; setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
IP_MULTICAST_IF
选项IP_MULTICAST_IF用于设置组播的默认默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:
struct in_addr addr; setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。
默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。例如:
unsigned char loop; setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop)); //参数loop设置为0禁止回送,设置为1允许回送。
IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP(ASM模型)
加入或者退出一个多播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,对一个结构struct ip_mreq类型的变量进行控制,struct ip_mreq原型如下:
struct ip_mreq { struct in_addr imn_multiaddr; /*加入或者退出的广播组IP地址*/ struct in_addr imr_interface; /*加入或者退出的网络接口IP地址*/ };
选项IP_ADD_MEMBERSHIP用于加入某个多播组,之后就可以向这个多播组发送数据或者从多播组接收数据。此选项的值为mreq结构,成员imn_multiaddr是需要加入的多播组IP地址,成员imr_interface是本机需要加入广播组的网络接口IP地址。例如:
struct ip_mreq mreq; setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
带源地址的IP多播(SSM模型)
带源地址的IP多播允许加入组时指定要接收哪些成员的数据。有两种方式:
- “包含”方式,这种方式下,为套接字指定N个有效源地址,套接字仅接收来自这些源地址的数据;使用IP_ADD_SOURCE_MEMBERSHIP和IP_DROP_SOURCE_MEMBERSHIP。
- “排除”方式,这种方式下,为套接字指定N个源地址,套接字将接收来自这些源地址之外的数据;使用IP_BLOCK_SOURCE(排除某个源地址)和IP_UNBLOCK_SOURCE(从排除集合中移除此源地址)以上两种方式输入参数都是ip_mreq_source结构。
server
/* *broadcast_server.c - 多播服务程序 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100"/ /*一个局部连接多播地址,路由器不进行转发*/ #define MCAST_DATA "BROADCAST TEST DATA" /*多播发送的数据*/ #define MCAST_INTERVAL 5 /*发送间隔时间*/ int main(int argc, char*argv) { int s; struct sockaddr_in mcast_addr; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } memset(&mcast_addr, 0, sizeof(mcast_addr));/*初始化IP多播地址为0*/ mcast_addr.sin_family = AF_INET; /*设置协议族类行为AF*/ mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*设置多播IP地址*/ mcast_addr.sin_port = htons(MCAST_PORT); /*设置多播端口*/ /*向多播地址发送数据*/ while(1) { int n = sendto(s, /*套接字描述符*/ MCAST_DATA, /*数据*/ sizeof(MCAST_DATA), /*长度*/ 0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr)) ; if( n < 0) { perror("sendto()"); return -2; } sleep(MCAST_INTERVAL); /*等待一段时间*/ } return 0; }
client
/* *broadcast_client.c - 多播的客户端 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100" /*一个局部连接多播地址,路由器不进行转发*/ #define MCAST_INTERVAL 5 /*发送间隔时间*/ #define BUFF_SIZE 256 /*接收缓冲区大小*/ int main(int argc, char*argv[]) { int s; /*套接字文件描述符*/ struct sockaddr_in local_addr; /*本地地址*/ int err = -1; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } /*初始化地址*/ memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(INADDR_ANY); local_addr.sin_port = htons(MCAST_PORT); /*绑定socket*/ err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ; if(err < 0) { perror("bind()"); return -2; } /*设置回环许可*/ int loop = 1; err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop)); if(err < 0) { perror("setsockopt():IP_MULTICAST_LOOP"); return -3; } struct ip_mreq mreq; /*加入多播组*/ mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*多播地址*/ mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*网络接口为默认*/ /*将本机加入多播组*/ err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));//ASM模型 if (err < 0) { perror("setsockopt():IP_ADD_MEMBERSHIP"); return -4; } int times = 0; int addr_len = 0; char buff[BUFF_SIZE]; int n = 0; /*循环接收多播组的消息,5次后退出*/ for(times = 0;times<5;times++) { addr_len = sizeof(local_addr); memset(buff, 0, BUFF_SIZE); /*清空接收缓冲区*/ /*接收数据*/ n = recvfrom(s, buff, BUFF_SIZE, 0,(struct sockaddr*)&local_addr,&addr_len); if( n== -1) { perror("recvfrom()"); } /*打印信息*/ printf("Recv %dst message from server:%s\n", times, buff); sleep(MCAST_INTERVAL); } /*退出多播组*/ err = setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); close(s); return 0; }