?博客主页?:?https://blog.csdn.net/wkd_007?
?博客内容?:?嵌入式开发、Linux、C语言、C++、数据结构、音视频?
?本文内容?:?介绍广播概念、UDP实现广播的C语言例子 ?
?金句分享?:?你不能选择最好的,但最好的会来选择你——泰戈尔?
⏰发布时间⏰:2024-03-06 00:10:30
本文未经允许,不得转发!!!
目录
?一、广播概述?二、广播地址?三、UDP单播 和 UDP广播 的比较✨3.1 UDP单播过程✨3.2 UDP广播过程 ?四、UDP实现广播的例子?五、总结
?一、广播概述
在网络编程中,有三种常见的通信方式:单播、广播、多播(组播),这三种方式对比如下表:
类型 | IPv4 | IPv6 | TCP | UDP | 所标识接口数 | 递送到的接口数 |
---|---|---|---|---|---|---|
单播 | 支持 | 支持 | 支持 | 支持 | 一个 | 一个 |
广播 | 支持 | 支持 | 全体 | 全体 | ||
多播 | 可选 | 支持 | 支持 | 一组 | 整个组 |
广播的概念
广播是允许一台主机
向 本地子网内所有主机
发送消息的一种通信方式。发送的广播分组会去往子网上的所有主机,包括发送者自身。
广播的用途
资源发现:知道子网中有我们想找的资源,但不知道其具体IP地址,可以往该子网中发一个广播,该资源主机收到广播后处理并回复,非资源主机不处理。举例:NVR发送广播搜索局域网内的IPC。减少分组流通。广播的缺点
子网内未参与相应广播的所有主机也会沿着协议栈向上完整地处理
收取的广播数据报,直到该数据报在UDP层处理时才被丢弃。
?二、广播地址
广播的实现需要往广播地址
发送广播分组。任何子网中最后一个IP地址就是广播地址。如果以{子网ID,主机ID}表示一个IP地址,则广播地址分为下面两种,-1表示所有位都位1:
子网定向广播地址
。通常,路由器不转发目的地址为子网定向广播地址
的数据报。2、受限广播地址:{-1,-1} 或 255.255.255.255
。路由器从不转发目的地址为255.255.255.255
的IP数据报。当应用程序设置了SO_BROADCAST
套接字选项,且发送目的地址为255.255.255.255
的UDP数据报时,大多数主机会将该目的地址转换成外出接口的子网定向广播地址
并发送到路由器。255.255.255.255
用十六进制表示就是0xffffffff
。在Linux系统中,定义了一个宏INADDR_BROADCAST
来表示受限广播地址
,该宏定义在头文件<netinet/in.h>中,定义如下:/* Address to send to all hosts. */#define INADDR_BROADCAST ((in_addr_t) 0xffffffff)
?三、UDP单播 和 UDP广播 的比较
✨3.1 UDP单播过程
下图说明了一个UDP数据报在单播情况下,怎样到达目的地的:
发送主机(图中最左边):
①进程在一个UDP套接字上调用sendto往IP地址192.168.42.3端口7433发送一个数据报;②UDP层对它冠以一个UDP首部后把UDP数据报传递到IP层③IP层对它冠以一个IPv4首部,确定其外出接口,在以太网情况下还激活ARP把目的IP地址映射成相应的以太网地址:00:0a:95:79:bc:b4。④该分组然后作为一个目的以太网地址为这个48位地址的以太网帧发送出去。该以太网帧的帧类型字段值为表示IPv4分组的
0x0800
。 目的主机(图中最右边):
①目的主机的以太网接口看到该帧后,比较并确定该帧的目的地址和自己以太网地址相同,于是读入该帧。由于帧类型是0x0800
,该帧的分组被置于IP输入队列。②IP层处理该分组会先比较该分组的目的地址,确定是本机IP地址之一才接受该分组,接着查看IPv4首部的协议字段,值为表示UDP的17,于是将该分组承载的数据报传递到UDP层。③UDP层检查该数据报的目的端口,把该数据报置于相应套接字的接收队列,必要时会唤醒阻塞在该相应输入操作的进程,由进程读取这个新收取的数据报。 其他主机(图中中间):
①非目的主机的以太网口看到该帧后,比较该帧目的地址和自己以太网地址,比较结果不相同,于是忽略了这个帧。✨3.2 UDP广播过程
下图说明了一个UDP数据报在广播情况下,怎样到达目的地的:
发送主机(图中最左边):
①进程在一个UDP套接字上调用sendto往IP地址192.168.42.255端口520发送一个数据报;②UDP层对它冠以一个UDP首部后把UDP数据报传递到IP层;③IP层对它冠以一个IPv4首部,确定其外出接口,因为目的地址是子网定向广播地址
,所以映射为48位全为1的以太网地址:ff:ff:ff:ff:ff:ff
。④该分组作为一个目的以太网地址为ff:ff:ff:ff:ff:ff
的以太网帧发送出去,使得该子网的每个以太网接口(包括自身主机接口)都会接收到该帧。该以太网帧的帧类型字段值为表示IPv4分组的0x0800
。 子网内所有主机(包括自身主机):
①子网内的所有主机的以太网接口看到该帧后,因为目的地址是ff:ff:ff:ff:ff:ff
,都会接收该帧。由于帧类型是0x0800
,该帧的分组传递到IP层。②IP层确定该分组目的地址为广播地址后,会接受该分组,接着查看IPv4首部的协议字段,值为表示UDP的17,于是将该分组承载的数据报传递到UDP层。③UDP层检查该数据报的目的端口,如果接收到该数据报的主机没有任何进程绑定值为520的UDP端口,则该主机的UDP代码会丢弃已收取的数据报,如图中中间主机。如果接收到该数据报的主机存在进程绑定了值为520的UDP端口,那么该进程把该数据报置于相应套接字的接收队列,必要时会唤醒阻塞在该相应输入操作的进程,由进程读取这个新收取的数据报。 ?四、UDP实现广播的例子
下面给出一个使用UDP实现广播的例子,代码是之前文章的例子修改的,文章链接: 入门知识:UDP协议、一个最简单的UDP客户端、一个最简单的UDP服务端 。
只需要在原本客户端修改两个地方就可以发送UDP广播数据报了:一个是在sendto
之前设置套接字选项SO_BROADCAST
;另一个是将sendto的目的地址设置为广播地址,这里使用INADDR_BROADCAST
。
发送UDP广播客户端步骤:
1、创建UDP套接字socket;2、准备广播地址和端口;3、设置广播套接字选项 SO_BROADCAST;4、使用 sendto 发送广播数据报;5、处理应答6、关闭UDP广播客户端代码也很简单,如下:
// brocastCli.c#include <stdio.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>#define USE_BRORDCAST 1int 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 USE_BRORDCASTservaddr.sin_addr.s_addr = INADDR_BROADCAST;// 3、设置广播套接字选项 SO_BROADCASTint so_broadcast = 1;if(setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&so_broadcast,sizeof(so_broadcast)) < 0){perror("setsockopt");close(sockfd);return -1;}#elseif (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0) // 设置本机IP为服务端IPperror("inet_pton error");#endif// 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;}
UDP服务器不需要做任何改动,还是使用之前的例子,代码如下:
// brocastSer.c#include <stdio.h>#include <string.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <unistd.h>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、使用 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);}}// 5、关闭close(sockfd);return 0;}
分别在局域网内的几台机器运行UDP服务端brocastSer
,然后在其中一台机器运行广播客户端brocastCli
,下面是客户端的运行结果,收取到好几台运行着服务端主机对广播的响应:
?五、总结
?本文介绍了广播的概念、广播的用途、广播的缺点、广播地址,对比了单播和广播的流程,最后给出了UDP实现广播的C语言例子。
]对于目的地址是广播地址的IP数据报,如果其大小超过外出接口的MTU(一般是1500字节),发送它的系统调用将返回EMSGSIZE错误。
如果文章有帮助的话,点赞?、收藏⭐,支持一波,谢谢 ???
参考资料:
《Unix网络编程卷1》