博客主页:https://blog.csdn.net/wkd_007
博客内容:嵌入式开发、Linux、C语言、C++、数据结构、音视频
本文内容:介绍多播的概念、多播地址、UDP实现广播的C语言例子
金句分享:你不能选择最好的,但最好的会来选择你——泰戈尔
⏰发布时间⏰:2024-03-07 20:31:23
本文未经允许,不得转发!!!
目录
- 一、多播概述
- 二、多播地址
- ✨2.1、IPv4 多播地址
- ✨2.2、IPv6 多播地址
- ✨2.3、多播地址映射为MAC地址
- 三、多播的过程
- 四、源特定多播
- ✨4.1 广域网的多播
- ✨4.2 源特定多播(Source-Specific Multicast)
- 五、UDP实现多播的例子
- ✨5.1 多播接收端代码
- ✨5.2 多播发送端代码
- 六、总结
一、多播概述
在网络编程中,有三种常见的通信方式:单播、广播、多播(组播),这三种方式对比如下表:
类型 | IPv4 | IPv6 | TCP | UDP | 所标识接口数 | 递送到的接口数 |
---|---|---|---|---|---|---|
单播 | 支持 | 支持 | 支持 | 支持 | 一个 | 一个 |
广播 | 支持 | 支持 | 全体 | 全体 | ||
多播 | 可选 | 支持 | 支持 | 一组 | 整个组 |
多播的概念
IP 多播(也称多址广播
或组播
)技术,是允许一台主机
向多台主机
发送消息的一种通信方式。单播只向单个IP接口发送数据,广播是向子网内所有IP接口发送数据,多播则介于两者之间,向一组IP接口发送数据。
多播支持IPv4,也支持IPv6。在IPv6中没有广播,认为广播只是特殊(把整个子网视为多播组)的多播。
多播支持UDP,不支持TCP。
多播既可用于局域网,也可用于广域网,而广播一般在局域网使用。
多播的优点
比起广播,多播数据报只会发送到加入多播组的主机,不会像广播那样发给所有主机。
二、多播地址
多播地址用来标识多播组,IPv4使用D类地址的某一个来表示一个多播组地址,IPv6多播地址的高序字节值为ff
。
✨2.1、IPv4 多播地址
IPv4的D类地址(从224.0.0.0到239.255.255.255)是IPv4多播地址,见下图:
D类地址的低序28位构成多播组ID(group ID),整个32位地址则称为组地址(group address)。
IPv4的多播地址可分为三类:
- 链路局部多播地址:
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,可限制多播范围。
下面是若干个IPv4特殊多播地址,这些地址是由IANA确定的,作为永久主机组:224.0.0.1所有组播主机224.0.0.2所有组播路由器224.0.0.4DRMRP 路由器224.0.0.5所有 OSPF 的路由器224.0.0.6OSPF 指派路由器224.0.0.9RPIv2 路由器224.0.0.10 EIGRP 路由器224.0.0.13 PIM 路由器224.0.0.22 IGMPv3224.0.0.25 RGMP224.0.1.1NTP 网络时间协议
✨2.2、IPv6 多播地址
IPv6多播地址的结构如下图,分成4个部分:
- 高位8比特:全部为1,这是固定的,表示这是一个多播地址;
- 标志4比特:分以下几种取值
0000
:众所周知的多播组;
0001
:临时的多播组;
0010
:表示多播地址是基于某个单播前缀赋予的;
0011
:表示基于单播的多播地址总是临时的。 - 范围4比特:可能有以下取值
0:保留。
1:接口本地范围(Interface-Local scope )。
2:链路本地范围(Link-Local scope )。
3:基于单播前缀的地址(Unicast-Prefix-based address )。
4:管理本地范围(Admin-Local scope )。
5:站点本地范围(Site-Local scope)。
6:未分配。
7:汇聚点标记(Rendezvous Point flag )。
8:组织本地范围(Organization-Local scope )。
9-D:未分配。
E:全局范围(Global scope。
F:保留。 - 组ID112比特:低序32位复制到以太网地址的低序32位
下面是若干特殊的IPv6多播地址。
ff01::1
和ff02::1
是所有节点(all-nodes)组。子网上所有具有多播能力的节点(主机、路由器和打印机等)必须在所有具有多播能力的接口上加入该组,类似于IPv4的224.0.0.1
多播地址。但多播是IPv6的一个组成部分,这与IPv4是不同的。
尽管对应的IPv4组称为所有主机组,而IPv6组称为所有节点组,它们的含义是一致的。IPv6重新命名意在更为清晰地指出本组包括了子网上的主机、路由器、打印机,以及任何IP设备。ff01::2
、ff02::2
和ff05::2
是所有路由器(all-routers)组。子网上所有多播路由器必须在所有具有多播能力的接口上加入该组,类似于IPv4的224.0.0.2
多播地址。
✨2.3、多播地址映射为MAC地址
将多播地址映射到以太网多播地址的过程是重要的,因为以太网是数据链路层的协议,而IPv4、IPv6的多播地址是网络层的协议。这种映射使得多播数据能够在以太网上正确传输到目标设备,确保只有加入了相应多播组的设备才会接收到相应的多播数据包。
以太网地址,通常称为MAC地址(Media Access Control address),是网络设备在数据链路层使用的唯一标识符。MAC地址由48位二进制数组成,通常以十六进制表示,每6位之间用冒号或连字符分隔,如 00:1A:2B:3C:4D:5E
。通常,MAC地址是网络设备(如网卡)出厂设置好的,一般不会改变。
当一个IPv4多播数据包需要发送到多个接收者时,IPv4多播地址会被映射成对应的以太网多播地址。这种映射不会改变设备的硬件MAC地址,而是针对特定的多播传输而生成的临时多播MAC地址
。多播MAC地址是一个虚拟的地址,源主机将数据发送到多播组对应的以太网多播地址,而不会影响到设备的真实MAC地址。设备仍然使用其固定的硬件MAC地址来标识自己,只是在多播通信过程中会使用临时的多播MAC地址来传输数据。
IPv4多播地址是通过特定规则映射到以太网多播地址的,映射规则如下:
- 前24位以固定的前缀
01:00:5E
开头,第25位总是0。- 后23位直接由IPv4多播地址的低23位组成。IPv4多播地址高序4位固定为
e
,映射过程中,中间的5位被忽略,因此这个映射关系不是一对一的。
IPv6多播地址到以太网地址的映射是通过多播组里的成员关系进行的。下面是IPv6多播地址到以太网地址的映射过程:
- 首先确定IPv6多播地址的范围,IPv6多播地址通常在ff00::/8的范围内。
- 多播组的最低24位用于构建对应的以太网地址。这个过程使用了特殊的前缀:33:33。
- 取IPv6多播地址的最低32位,将其转换为16进制,并插入到33:33之后,形成完整的以太网地址。
- 以太网地址的第一个字节设置为01-00-5E。这个步骤可以保证以太网地址不会与单播地址冲突。
总的来说,IPv6多播地址到以太网地址的映射是通过将IPv6多播地址的一部分直接复制到以太网地址中,并添加特定的前缀来确保唯一性。
三、多播的过程
下图是可以展示多播的过程:
多播的过程:
- 首先,接收主机(图中右侧)需要在接收进程创建一个UDP套接字,绑定端口123,然后加入多播组
224.0.1.1
。该主机的IPv4层会保存这些信息,并告知合适的数据链路接收目的以太网地址为01:00:5e:00:01:01
的以太网帧。 - 然后,发送主机(图中左侧)的发送应用进程创建一个UDP套接字,往IP地址
224.0.1.1
的123端口发送一个数据报,并最后转换成以太网帧发到网络。发送多播数据报无需任何特殊处理,发送应用进程不必为此加入多播组。 - 接着,不具备多播能力的主机(图中中间主机)会完全忽略该帧。因为:
1、该帧的目的以太网地址不匹配该主机的接口地址;
2、该帧的目的以太网地址不是以太网广播地址;
3、该主机的接口未被告知接收任何组地址(高序字节的低序位被置为1的以太网地址,如图21-1所示)。 - 最后,接收主机(图中右侧)的数据链路收取该帧后将承载的分组传递到IP层。IP层比较该地址和本机的接收应用进程已经加入的所有多播地址,根据比较结果确定是接受还是丢弃该分组。确定接受之后,再把承载的UDP数据报传递到UDP层。UDP层再把承载在数据报中的数据传递到绑定了端口123的套接字。
图中没有展示的还有以下三种情形:
- (1)运行所加入多播地址为225.0.1.1的某个应用进程的一个主机。既然多播地址组ID的高5位在到以太网地址的映射中被忽略,该主机的接口也将接收目的以太网地址为
01:00:5e:00:01:01
的帧。这种情况下,由该帧承载的分组将由IP层中的完备过滤丢弃。 - (2)运行所加入多播地址符合以下条件的某个应用进程的一个主机:由这个多播地址映射成的以太网地址恰好和
01:00:5e:00:01:01
一样被该主机执行非完备过滤的接口散列到同一个结果。该接口也将接收目的以太网地址为01:00:5e:00:01:01
的帧,直到由数据链路层或IP层丢弃。 - (3)目的地为相同多播组(
224.0.1.1
)不同端口(譬如4000)的一个数据报。图21-4中右侧主机仍然接收该数据报,并由P层接受并传递给UDP层,不过UDP层将丢弃它(假设绑定端口4000的套接字不存在)。
四、源特定多播
✨4.1 广域网的多播
多播是不仅支持局域网,也支持广域网。
当某个主机上的一个进程加入一个多播组时,该主机向所有直接连接的多播路由器发送一个IGMP
消息,告知它们本主机已加入了那个多播组。多播路由器随后使用多播路由协议(MRP)交换这些信息,这样每个多播路由器就知道在收到目的地为所加入多播地址的分组时该如何处理。
✨4.2 源特定多播(Source-Specific Multicast)
源特定多播的英文是Source-Specific Multicast,简称SSM,是基于源地址和目的地址的组合来定义多播组,接收者只能从单个源(或称为发送者)接收数据
,而不是从任意源接收,这使得网络更加安全可靠。
- 接收进程向多播路由器提供发送进程的
源地址
作为多播组加入操作的一部分。这种方式可以减少网络中的不必要流量,提高网络效率。 - 把多播组的标识从单纯多播组地址细化为单播源地址和多播目的地址之组合(SSM称之为通道)。
五、UDP实现多播的例子
下面给出一个使用UDP实现多播的例子,代码是之前文章的例子修改的,文章链接: 入门知识:UDP协议、一个最简单的UDP客户端、一个最简单的UDP服务端 。
✨5.1 多播接收端代码
接收端是使用UDP服务端代码修改,需要在交互数据之前,将套接字加入多播组239.0.1.1
,让链路层接口接收该多播组的数据报,使用完需要离开多播组。
UDP多播接收端步骤:
- 1、创建UDP套接字socket;
- 2、准备本地ip接口和多播组端口;
- 3、绑定多播组端口 bind;
- 4、加入多播组 239.0.1.1;
- 5、使用 sendto、recvfrom 交互数据;
- 6、离开多播组
- 7、关闭套接字
// multicastSer.c#include #include #include #include #include #include int main(){// 1、创建UDP套接字socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd<0)perror("socket error" );// 2、准备本地ip接口和多播组端口struct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons (10086);servaddr.sin_addr.s_addr = INADDR_ANY; // 指定ip地址为 INADDR_ANY,这样要是服务器主机有多个网络接口,服务器进程就可以在任一网络接口上接受客户端的连接// 3、绑定多播组端口 bindif (bind(sockfd,(struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)perror("bind error" );// 4、加入多播组 239.0.1.1struct ip_mreq mreq;mreq.imr_multiaddr.s_addr = inet_addr("239.0.1.1");// 多播组的IP地址mreq.imr_interface.s_addr = htonl(INADDR_ANY);// 加入的客服端主机IP地址if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1){perror("setsockopt");return -1;}// 5、使用 sendto、recvfrom 交互数据printf("UdpSer sockfd=%d, start \n",sockfd);char recvline[256];while(1){struct sockaddr_in cliaddr;bzero(&cliaddr, sizeof(cliaddr));socklen_t addrLen=sizeof(cliaddr);int n = recvfrom(sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&cliaddr, &addrLen);if(n>0){recvline[n] = 0 ;/*null terminate */printf("recv sockfd=%d %d byte, [%s] addrLen=%d, cliIp=%s, cliPort=%d\n",sockfd, n, recvline, addrLen, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);sendto(sockfd, "Hello,I am udp server", strlen("Hello,I am udp server"), 0, (struct sockaddr*)&cliaddr, addrLen);}}// 6、离开多播组setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); // 7、关闭close(sockfd);return 0;}
✨5.2 多播发送端代码
发送端是使用UDP客户端代码修改,整个流程都不需改动,只需要把要发送的目的地址改为多播组地址239.0.1.1
。修改后代码如下:
// multicastCli.c#include #include #include #include #include #include int main(){// 1、创建UDP套接字socketint sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd<0)perror("socket error" );// 2、准备多播组地址和端口struct sockaddr_in servaddr;bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons (10086);if (inet_pton(AF_INET, "239.0.1.1", &servaddr.sin_addr) <= 0)perror("inet_pton error");// 4、使用 sendto 发送多播组数据报if(sendto(sockfd, "Hello,I am udp client", strlen("Hello,I am udp client"), 0, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)perror("sendto error" );// 5、处理应答char recvline[256];int n = 0;struct sockaddr_in tmpAddr;bzero(&tmpAddr, sizeof(tmpAddr));socklen_t addrLen=sizeof(tmpAddr);while ( (n = recvfrom (sockfd, recvline, sizeof(recvline), 0, (struct sockaddr*)&tmpAddr, &addrLen)) > 0){recvline[n] = 0 ;/*null terminate */printf("recvfrom ip=[%s], [%s]\n",inet_ntoa(tmpAddr.sin_addr), recvline);bzero(&tmpAddr, sizeof(tmpAddr));}if (n < 0)perror("read error" );// 6、关闭close(sockfd);return 0;}
在4台机器运行多播接收端,下面是发送端的运行结果,发出多播数据报后,收到了各个接收端的回复:
六、总结
本文介绍多播的概念,多播地址,多播数据报发送过程,最后给出C语言实现多播的例子。
如果文章有帮助的话,点赞、收藏⭐,支持一波,谢谢