当前位置: 首页 > news >正文

Linux C/C++ 学习日记(24)UDP协议的介绍:广播、多播的实现

注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、UDP 协议是什么

UDP 的全称是用户数据报协议(User Datagram Protocol),是 OSI 模型中传输层的重要协议,核心特性可概括为三点:

  1. 无连接:通信前不需要像 TCP 那样建立 “三次握手”,直接发送数据。
  2. 不可靠:不保证数据能送达,也不保证送达顺序,丢包后不会自动重传。
  3. 轻量:头部仅 8 字节(远少于 TCP 的 20 字节),协议本身几乎不占用额外资源。

二、为什么会有 UDP

UDP 的存在是为了弥补 TCP 的 “短板”,因为 TCP 的 “可靠性” 是有代价的,无法满足所有场景需求:

  1. TCP 需要建立连接、确认数据、重传丢包,会产生连接开销传输延迟
  2. 部分场景对 “实时性” 的需求远高于 “可靠性”,TCP 的延迟会直接影响体验。
  3. 网络中需要一种 “极简” 的传输方式,用于传递小数据量、对丢失不敏感的信息。

三、它解决了什么问题

UDP 的核心价值是 “放弃部分可靠性,换取速度和效率”,主要解决三类问题:

  1. 降低传输延迟:无连接、无重传机制,数据能以最快速度从发送方到接收方,适合实时场景。
  2. 减少资源开销:极简的头部设计和无连接管理,对发送端、接收端的 CPU 和内存占用极低。
  3. 支持特殊通信:原生支持广播(一对多) 和多播(一组多),而 TCP 仅支持点对点通信。

四、它的应用场景是什么

所有选择 UDP 的场景,核心诉求都是 “实时性优先” 或 “轻量优先”,典型场景包括:

  • 实时音视频:如视频通话、直播、语音聊天。偶尔丢包只会导致画面轻微卡顿,不会影响整体流畅度,若用 TCP 会因重传产生明显延迟。
  • 在线游戏:如 MOBA 类游戏、射击游戏。游戏数据(如位置、操作)需要毫秒级传输,延迟比丢 1-2 个数据包更影响体验。
  • DNS 查询:域名解析(如将 “baidu.com” 转成 IP)仅需传递几十字节的小数据,用 UDP 能瞬间完成,没必要建立 TCP 连接。
  • 广播 / 多播:如局域网内的设备发现(如打印机搜索)、IPTV 组播,TCP 无法实现这类 “一对多” 通信。

五、UDP协议头(8个字节)

1. 各参数的含义

字段名称长度(位)含义说明
16 位源端口号16标识发送方应用程序使用的端口。无需回复时可设为 0;需回复时,接收方通过该端口确定回复目标。
16 位目的端口号16标识接收方应用程序的目标端口,是区分不同应用层协议 / 程序的核心依据(如 DNS 用 53 端口,TFTP 用 69 端口),系统据此将数据交付给正确应用。
16 位 UDP 长度16表示 UDP 头部 + 数据的总长度(字节)。最小值为 8(仅头部),最大值 65535 字节(受 16 位限制),实际传输受 IP 层 MTU 约束。
16 位 UDP 检验和16

用于检测传输中的比特错误,计算时包含 UDP 头部、数据及含 IP 源 / 目的地址、协议类型的伪头部。校验不一致则丢弃数据报;

设为 0 表示不校验。

2. 16位UDP检验和的使用

环节 / 步骤具体说明
步骤 1:构造完整数据块

需拼接三部分组成临时数据块(仅用于计算):

- 伪首部(12 字节):源 IP 地址(32 位)、目的 IP 地址(32 位)、保留字段(8 位,0x00)、协议类型(8 位,UDP 为 0x11)、UDP 总长度(16 位,头部 + 数据总字节数);

- UDP 头部(8 字节):源端口、目的端口、UDP 长度、检验和(计算时临时设为 0x0000);

- UDP 数据:应用层载荷(可为空)。

步骤 2:数据块对齐校验和以 16 位(2 字节)为单位计算,需保证总字节数为偶数:若总长度为奇数,在数据末尾补充 1 个字节的 0x00(仅用于计算,不传输)。
步骤 3:拆分 16 位字序列

将对齐后的数据块按 “低地址到高地址” 拆分为连续 16 位 “字”:

例:5 字节数据块0x11 0x22 0x33 0x44 0x55补 0x00 后为 6 字节,拆分为0x11220x33440x5500

步骤 4:反码求和(核心)

1. 逐字相加:所有 16 位字按二进制逐位相加,保留进位;

2. 处理进位:若结果为 17 位,将最高位进位(1)循环加到最低位;

3. 累加所有字,最终得到 16 位 “总和”。

步骤 5:计算检验和

对步骤 4 的 16 位总和取反码(0 变 1、1 变 0),结果即为 16 位 UDP 检验和

例:总和 0x0269(0000 0010 0110 1001)的反码为 0xFD961111 1101 1001 0110)。

步骤 6:发送与验证

- 发送方:将检验和填入 UDP 头部 “检验和字段” 并发送;

- 接收方:重构数据块(含接收的检验和),

重复计算(步骤1-5,检验和取发送方传来的:0xFD96):

- 结果为 0xFFFF(16 位全 1)→ 校验通过;

- 结果非全 1 → 数据错误,丢弃数据报(上层处理错误)。

关键特性与注意事项

1. 反码求和优势:“进位循环” 特性更易检测单比特 / 多比特连续错误;

2. 协议差异:IPv4 中检验和可选(0x0000 表示不校验),IPv6 强制计算;

3. 伪首部作用:关联 IP 地址和协议类型,确保端到端数据一致性(避免 IP 错误但端口正确的误传)。

六、UDP报文传输模式

注意:在UDP报文传输模式中

  • sendto 函数一次调用发送的数据,会被封装为一个独立的 UDP 数据报(报文) 发送。UDP 是面向数据报的协议,不会对数据进行拆分或合并,每次 sendto 调用对应一个完整的 UDP 报文。

  • recvfrom 函数一次调用会接收 一个完整的 UDP 报文(即对应一次 sendto 发送的数据)。如果接收缓冲区大小足够,会完整读取该报文;如果缓冲区不足,可能会截断数据(需注意检查返回值确认实际接收长度)。

需要注意的是,这一特性仅适用于 UDP。如果使用 TCP 协议(基于字节流),send/sendto 和 recv/recvfrom 没有 “报文” 的概念,数据会被视为连续的字节流,多次发送的数据可能被合并,一次接收也可能获取到多次发送的部分数据(取决于底层缓冲区)。

因此,“一次 sendto 对应一个报文,一次 recvfrom 接收一个报文” 是 UDP 协议的典型行为。

七、广播和多播的概念与实现

对比维度UDP 广播(一对所有)UDP 多播(一对一组)
核心定义同一局域网内所有设备发送数据,无需提前建立连接预先加入 “多播组” 的设备发送数据,仅组内设备可接收
核心要素

1. 广播地址(

全局:255.255.255.255;

子网:如 192.168.1.255)

2. 套接字需开启SO_BROADCAST选项

1. D 类多播地址(范围:224.0.0.0~239.255.255.255,如 239.0.0.1)

2. 依赖 IGMP 协议管理组成员(加入 / 退出)

关键实现步骤

1. 创建 UDP 套接字

2. 开启广播权限(setsockoptSO_BROADCAST=1

3. 目标 IP 设为广播地址,指定端口发送

4. 接收方绑定对应端口监听

1. 创建 UDP 套接字

2. 接收方通过setsockopt加入多播组(指定组地址 + 本地网卡 IP)

3. 发送方目标 IP 设为多播组地址,指定端口发送

4. 路由器通过 IGMP 转发给组内设备

跨子网能力不支持,路由器默认丢弃广播包,仅局限于本地局域网支持,只要路由器支持 IGMP 协议,可转发多播包到其他子网的多播组设备
网络带宽占用高,所有局域网设备都会接收数据包,可能造成带宽浪费低,仅多播组内设备接收,无关设备不占用带宽
关键注意事项

1. 仅适用于小数据量传输,避免广播风暴

2. 套接字默认禁止广播,需手动开启权限

1. 多播组动态管理,设备可随时加入 / 退出

2. 发送方无需加入多播组,仅接收方需加入

典型应用场景1. 局域网设备发现(如打印机搜索)2. 本地游戏房间创建通知3. 局域网内简单数据同步

1. IPTV / 网络直播(仅订阅用户接收)

2. 实时数据推送(如股票行情、气象数据)

代码实现:

1.广播

发送端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define BROADCAST_PORT 8888
// 子网广播地址(需根据实际局域网子网修改,例如192.168.1.255)
#define BROADCAST_ADDR "192.168.1.255"int main() {int sockfd;struct sockaddr_in broadcast_addr;char msg[] = "This is a UDP broadcast message!";// 1. 创建UDP套接字(AF_INET:IPv4,SOCK_DGRAM:UDP)if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 开启广播权限(UDP默认禁止广播,需设置SO_BROADCAST选项)int broadcast_enable = 1;if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast_enable, sizeof(broadcast_enable)) < 0) {perror("setsockopt SO_BROADCAST failed");close(sockfd);exit(EXIT_FAILURE);}// 3. 配置广播目标地址(IP+端口)memset(&broadcast_addr, 0, sizeof(broadcast_addr));broadcast_addr.sin_family = AF_INET;                  // IPv4broadcast_addr.sin_port = htons(BROADCAST_PORT);      // 端口转换为网络字节序// 将广播地址字符串转换为网络字节序IPif (inet_pton(AF_INET, BROADCAST_ADDR, &broadcast_addr.sin_addr) <= 0) {perror("invalid broadcast address");close(sockfd);exit(EXIT_FAILURE);}// 4. 循环发送广播消息while (1) {// 发送数据到广播地址ssize_t len = sendto(sockfd, msg, strlen(msg), 0, (struct sockaddr*)&broadcast_addr, sizeof(broadcast_addr));if (len < 0) {perror("sendto failed");close(sockfd);exit(EXIT_FAILURE);}printf("Broadcast sent: %s\n", msg);sleep(2);  // 每2秒发送一次}close(sockfd);return 0;
}

接收端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define BROADCAST_PORT 8888
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in local_addr;struct sockaddr_in sender_addr;socklen_t sender_len = sizeof(sender_addr);char buffer[BUFFER_SIZE];// 1. 创建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 配置本地地址(绑定到广播端口,接收所有IP的广播)memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;  // 监听所有本地IPlocal_addr.sin_port = htons(BROADCAST_PORT);  // 绑定广播端口// 3. 绑定套接字到本地地址(必须绑定端口才能接收对应端口的广播)if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}printf("Waiting for UDP broadcast on port %d...\n", BROADCAST_PORT);// 4. 循环接收广播消息while (1) {// 接收数据(同时获取发送方地址)ssize_t len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(struct sockaddr*)&sender_addr, &sender_len);if (len < 0) {perror("recvfrom failed");close(sockfd);exit(EXIT_FAILURE);}buffer[len] = '\0';  // 手动添加字符串结束符// 打印接收的消息和发送方IPprintf("Received from %s:%d: %s\n",inet_ntoa(sender_addr.sin_addr),  // 网络字节序IP转字符串ntohs(sender_addr.sin_port),      // 网络字节序端口转主机序buffer);}close(sockfd);return 0;
}

2. 多播

发送端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define MULTICAST_PORT 9999
// 多播组地址(D类地址,例如239.0.0.1)
#define MULTICAST_GROUP "239.0.0.1"int main() {int sockfd;struct sockaddr_in multicast_addr;char msg[] = "This is a UDP multicast message!";// 1. 创建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 配置多播目标地址(IP+端口)memset(&multicast_addr, 0, sizeof(multicast_addr));multicast_addr.sin_family = AF_INET;multicast_addr.sin_port = htons(MULTICAST_PORT);// 多播组地址转换为网络字节序if (inet_pton(AF_INET, MULTICAST_GROUP, &multicast_addr.sin_addr) <= 0) {perror("invalid multicast group address");close(sockfd);exit(EXIT_FAILURE);}// 3. 循环发送多播消息(发送方无需加入多播组)while (1) {ssize_t len = sendto(sockfd, msg, strlen(msg), 0,(struct sockaddr*)&multicast_addr, sizeof(multicast_addr));if (len < 0) {perror("sendto failed");close(sockfd);exit(EXIT_FAILURE);}printf("Multicast sent: %s\n", msg);sleep(2);  // 每2秒发送一次}close(sockfd);return 0;
}

接收端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define MULTICAST_PORT 9999
#define MULTICAST_GROUP "239.0.0.1"
// 本地网卡IP(需根据实际环境修改,例如192.168.1.100)
#define LOCAL_IP "192.168.1.100"
#define BUFFER_SIZE 1024int main() {int sockfd;struct sockaddr_in local_addr;struct ip_mreq mreq;  // 多播组加入请求结构体struct sockaddr_in sender_addr;socklen_t sender_len = sizeof(sender_addr);char buffer[BUFFER_SIZE];// 1. 创建UDP套接字if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {perror("socket creation failed");exit(EXIT_FAILURE);}// 2. 配置本地地址(绑定到多播端口)memset(&local_addr, 0, sizeof(local_addr));local_addr.sin_family = AF_INET;local_addr.sin_addr.s_addr = INADDR_ANY;  // 监听所有本地IPlocal_addr.sin_port = htons(MULTICAST_PORT);// 3. 绑定套接字到本地端口if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {perror("bind failed");close(sockfd);exit(EXIT_FAILURE);}// 4. 配置多播组加入信息// 设置要加入的多播组地址if (inet_pton(AF_INET, MULTICAST_GROUP, &mreq.imr_multiaddr.s_addr) <= 0) {perror("invalid multicast group");close(sockfd);exit(EXIT_FAILURE);}// 设置本地接口IP(多网卡时需指定,单网卡可用INADDR_ANY)if (inet_pton(AF_INET, LOCAL_IP, &mreq.imr_interface.s_addr) <= 0) {perror("invalid local IP");close(sockfd);exit(EXIT_FAILURE);}// 5. 加入多播组(通过IP_ADD_MEMBERSHIP选项)if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) {perror("setsockopt IP_ADD_MEMBERSHIP failed");close(sockfd);exit(EXIT_FAILURE);}printf("Joined multicast group %s, waiting for messages on port %d...\n",MULTICAST_GROUP, MULTICAST_PORT);// 6. 循环接收多播消息while (1) {ssize_t len = recvfrom(sockfd, buffer, BUFFER_SIZE - 1, 0,(struct sockaddr*)&sender_addr, &sender_len);if (len < 0) {perror("recvfrom failed");close(sockfd);exit(EXIT_FAILURE);}buffer[len] = '\0';printf("Received from %s:%d: %s\n",inet_ntoa(sender_addr.sin_addr),ntohs(sender_addr.sin_port),buffer);}// 退出时可离开多播组(实际中程序退出会自动离开)// setsockopt(sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));close(sockfd);return 0;
}

http://www.dtcms.com/a/485956.html

相关文章:

  • OpenHarmony内核基础:LiteOS-M内核与POSIX/CMSIS接口
  • C语言实现Modbus TCP/IP协议客户端-服务器
  • ORACLE 19C ADG环境 如何快速删除1.8TB的分区表?有哪些注意事项?
  • 重庆黔江做防溺水的网站少儿编程十大培训机构
  • 浅谈中兴电子商务网站建设html考试界面设计
  • 工业三防平板背后的条码与RFID采集技术
  • pytorch框架GPU适配npu
  • 【散列函数】哈希函数简介
  • 学英语音标作用,能听出声音拼音组成,记忆效率提高
  • 学习日记day
  • Python爬虫数据可视化:深度分析贝壳成交价格趋势与分布
  • C++中的父继子承(2)多继承菱形继承问题,多继承指针偏移,继承组合分析+高质量习题扫尾继承多态
  • 做公司网站别人能看到吗6网站源码传到服务器上后怎么做
  • php多语言网站开发网站界面设计图片
  • 树形结构渲染 + 选择(Vue3 + ElementPlus)
  • Redis技术应用
  • hot100练习-8
  • 手机网站设置在哪里找房产信息平台
  • 算法入门:专题二---滑动窗口(长度最小的子数组)更新中
  • 2025年存储市场报告深度解读
  • HTTP 413 状态码详解与前端处理,请求体过大
  • 大数据背景下时序数据库选型指南:国产开源技术的突破与实践
  • asp网站优化云南网站制作需求
  • k8s(六)Pod的资源控制器
  • TypeScript前端架构与开发技巧深度解析:从工程化到性能优化的完整实践
  • 郴州做网站网站建设公司ejiew
  • LeetCode 将数组和减半的最少操作次数
  • OpenHarmony南向开发环境搭建 - 深入理解Ubuntu、DevEco Device Tool与HPM
  • QT-day1
  • Spec-Kit+Copilot打造AI规格驱动开发