27、UDP组播收发:多播发送端实现、多播接收端实现、TTL设置
组播这个东西,说实话,我第一次接触是在做一个视频监控项目的时候。那时候要在局域网内把摄像头画面分发给几十个客户端,如果用单播,服务器得开几十个socket,CPU直接飙到80%。后来改用组播,一个socket搞定所有客户端,CPU降到15%。嗯,这就是组播的魅力。
今天我们就来聊聊UDP组播。说白了,组播就是“一对多”的通信方式。发送端只发一份数据,网络设备会自动复制给所有加入组播组的接收端。这比单播省带宽,比广播更精准。
组播的基本概念
组播使用D类IP地址,范围是224.0.0.0到239.255.255.255。其中224.0.0.0到224.0.0.255是保留地址,不能用于用户程序。我一般用239.0.0.0到239.255.255.255这个范围,这是管理本地范围的组播地址,不会往外扩散。
组播通信需要两个角色:
- 发送端:只管往组播地址发数据,不需要加入组播组
- 接收端:必须加入组播组,才能收到数据
这里有个容易搞混的点:发送端不需要加入组播组,但接收端必须加入。我刚开始做的时候,以为发送端也要加入,结果代码写复杂了,其实完全没必要。
多播发送端实现
发送端的实现其实和普通UDP发送差不多,区别在于目标地址是组播地址。来看代码:
#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_IP "239.0.0.1"
#define MULTICAST_PORT 8888
#define TTL_VALUE 4
int main() {
int sockfd;
struct sockaddr_in addr;
char buf[] = "Hello, multicast!";
// 创建UDP socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 设置TTL
int ttl = TTL_VALUE;
if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL,
&ttl, sizeof(ttl)) < 0) {
perror("setsockopt TTL");
close(sockfd);
exit(1);
}
// 设置目标地址
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(MULTICAST_IP);
addr.sin_port = htons(MULTICAST_PORT);
// 发送数据
while (1) {
int ret = sendto(sockfd, buf, strlen(buf), 0,
(struct sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("sendto");
break;
}
printf("Sent: %s\n", buf);
sleep(1);
}
close(sockfd);
return 0;
}
这段代码里,关键点就两个:
- IP_MULTICAST_TTL:设置组播包的生存时间,默认是1,只能在本网段内传播
- sendto:目标地址填组播地址就行,和普通UDP发送一模一样
多播接收端实现
接收端比发送端复杂一点。你需要先创建一个UDP socket,然后绑定到本地端口,最后加入组播组。来看代码:
#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_IP "239.0.0.1"
#define MULTICAST_PORT 8888
int main() {
int sockfd;
struct sockaddr_in addr;
struct ip_mreq mreq;
char buf[1024];
// 创建UDP socket
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror("socket");
exit(1);
}
// 允许地址重用
int reuse = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof(reuse)) < 0) {
perror("setsockopt REUSEADDR");
close(sockfd);
exit(1);
}
// 绑定到本地端口
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(MULTICAST_PORT);
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
close(sockfd);
exit(1);
}
// 加入组播组
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) < 0) {
perror("setsockopt ADD_MEMBERSHIP");
close(sockfd);
exit(1);
}
printf("Joined multicast group %s:%d\n", MULTICAST_IP, MULTICAST_PORT);
// 接收数据
while (1) {
int len = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);
if (len < 0) {
perror("recvfrom");
break;
}
buf[len] = '\0';
printf("Received: %s\n", buf);
}
close(sockfd);
return 0;
}
接收端有几个关键点:
- SO_REUSEADDR:允许多个程序绑定到同一个端口。我遇到过一个问题,如果不加这个选项,程序崩溃重启后端口会被占用,得等几分钟才能重新绑定
- IP_ADD_MEMBERSHIP:加入组播组。这个操作告诉网卡,我要收这个组播地址的数据
- 绑定到INADDR_ANY:绑定到所有网卡,这样不管数据从哪个网卡进来都能收到
TTL设置详解
TTL(Time To Live)这个东西,很多人以为只是防环的。其实在组播里,TTL还有一个重要作用:控制组播包的传播范围。
TTL值和传播范围的关系:
| TTL值 | 传播范围 | 典型场景 |
|---|---|---|
| 0 | 本机 | 调试用,数据不出网卡 |
| 1 | 同一网段 | 局域网内组播 |
| 2 | 跨1个路由器 | 小范围跨网段 |
| 4 | 跨3个路由器 | 中等规模网络 |
| 32 | 站点本地范围 | 企业内网 |
| 64 | 区域范围 | 城域网 |
| 128 | 全球范围 | 互联网组播(很少用) |
设置TTL的代码很简单,就一行:
int ttl = 4;
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));
你想想看,如果TTL设成1,数据包只能在同一个交换机下传播。如果设成4,就能跨3个路由器。我一般建议局域网内用1,跨网段用4,别设太大,否则网络里到处是你的组播包,影响别人。
组播通信流程图
下面这张图展示了组播通信的完整流程,从发送端到接收端,数据包是怎么走的:
从图上可以看得很清楚:发送端把数据发到组播地址,网络设备(交换机/路由器)负责把数据复制给所有加入组播组的接收端。接收端3因为没有加入组播组,所以收不到数据。
避坑指南
做组播开发,有几个坑我踩过,分享给你:
- 防火墙拦截:Linux的iptables默认会拦截组播包。我建议先关掉防火墙测试,确认程序没问题再配置防火墙规则
- 多网卡问题:如果机器有多个网卡,需要指定用哪个网卡发送组播。用setsockopt设置IP_MULTICAST_IF可以指定出口网卡
- Windows和Linux差异:Windows上加入组播组用的是WSAJoinLeaf,Linux用setsockopt。跨平台开发时要注意
- 组播地址冲突:同一个网段内,不同应用不要用同一个组播地址,否则会互相干扰
核心要点总结:
- 发送端:创建UDP socket,设置TTL,往组播地址发数据
- 接收端:创建UDP socket,绑定端口,加入组播组,收数据
- TTL控制传播范围:1局域网,4跨网段,别设太大
- 接收端必须加入组播组才能收到数据
组播这个东西,说白了就是“一次发送,多点接收”。用好了能大幅降低服务器负载,用不好就是网络灾难。我建议你先在虚拟机里搭个环境测试,确认流程走通了再上生产环境。
公众号:蓝海资料掘金营,微信deep3321