刚入行做网络编程那会儿,我在一个视频传输项目里第一次接触到了组播这个概念。当时项目要求把一路实时画面同时分发给几十个客户端,用传统的单播方式去写,服务器要维护一堆socket连接,CPU和带宽压力一下就上去了。后来换成了C语言实现组播,一个数据包发出去,网内所有订阅了组播组的客户端都能收到,代码量少了一大截,服务器负载几乎可以忽略不计。这篇文章就把C语言组播的完整用法、关键参数和我在实际项目里踩过的坑一次说清楚,适合正在做音视频推流、行情分发、设备状态同步这类“一对多”场景的朋友参考。
1. 组播方案选型与整体设计思路
1.1 单播、广播、组播到底怎么选
先搞清楚为什么需要组播这玩意儿。网络数据分发本质上就三种模型:单播(Unicast)、广播(Broadcast)和组播(Multicast)。
单播是一个source对应一个destination,数据包从源地址发往单个目标地址。这种模式最直接,但在一对多的场景下,服务器得给每个客户端都发一份同样的数据,N个客户端就发N份拷贝,带宽消耗是线性增长的。如果传输的是视频流这种大流量数据,服务器很快就会被IO和带宽拖死。
广播则是一个source发给网段内所有主机。这个方案虽然简单粗暴,但问题也明显:广播包会被所有主机接收,哪怕人家根本不关心这个数据,CPU中断开销全被抢占了,而且广播无法穿过路由器,只能在同一个二层广播域内传播,无法跨子网工作。
组播是介于两者之间的模型。它把数据发到一个特定的组播组地址,只有加入了该组的主机才会收到这份数据。服务器只发送一份数据包,路由器负责沿着组播分发树把数据复制到各个有接收端的网络分支。这样不管有多少接收者,发送端的负载都是恒定的,而且接收方可以存在于不同的子网,只要中间路由器启用了组播路由协议(如PIM、IGMP Snooping)就行。
我在实际选型时是这样判断的:如果接收端数量少(几个到十几个),且彼此网络位置分散,单播依然可行;如果接收端数量大、且都在同一二层网络内,组播几乎是唯一合理的方案。广播一般不建议用,除非是做局域网内的服务发现这类对“打扰”不敏感的场景。
1.2 组播地址与端口规划:不能随便乱用
组播通信的第一步是选地址,这个有讲究。IPv4组播地址范围是224.0.0.0到239.255.255.255(D类地址),其中又分成几类用途:
| 地址段 | 用途 | 说明 |
|---|---|---|
| 224.0.0.0 ~ 224.0.0.255 | 本地网络控制块 | 协议专用,如OSPF用224.0.0.5/224.0.0.6,IGMP用224.0.0.22,通常不用于业务数据 |
| 224.0.1.0 ~ 238.255.255.255 | 全球范围组播 | 可跨域传播,需要路由器支持组播路由协议 |
| 239.0.0.0 ~ 239.255.255.255 | 本地管理组播地址 | 只在本地管理的域内有效,不向外传播,适合园区网、公司内部使用 |
我做项目时一般习惯在239.0.0.0/8这个段里选组播地址,因为实际部署中不太可能让组播流量跨运营商骨干网传播,而且本地管理段不会跟公共服务冲突。端口则和UDP一致,随便选一个大于1024的端口就行,比如代码示例里我用的是8888,实际项目里可以根据业务划分不同端口,注意别跟已有的服务端口冲突。
有一点要提醒:组播组的IP地址有一段会被映射到以太网MAC地址(后面说原理),所以如果两个组播组地址的低23位相同,在同一二层网络上会被网卡当成同一个组接收,导致串包。选地址时尽量避开这种巧合。
1.3 组播协议栈工作流程
想要真正用好组播,必须理解组播协议栈的分工。从OSI模型来看,这个链条是这样的:
- 应用层:发送端调用
sendto()把数据提交给内核UDP层。 - 传输层:UDP封装,目标IP就是组播地址,目标端口是我们选的业务端口。
- 网络层:主机内核根据路由表查询出口网卡,把数据包丢给物理链路。
- 数据链路层:组播IP地址会被映射为对应的组播MAC地址,这个映射规则是把IP地址的后23位直接搬到MAC地址的01:00:5e:00:00:00前缀后面。比如239.0.0.1映射出来就是01:00:5e:00:00:01。
接收端这边,网卡在硬件层就会做一次MAC地址过滤,只有匹配了组播MAC地址的帧才会被送到内核协议栈。此时内核还要继续做一次IP层的过滤,确认本机确实加入了该组播组(IGMP成员关系),才会把数据送上UDP层,最终交给socket缓冲区。
这个过程就解释了为什么接收端必须“先加组再收包”——组播不是单纯往一个IP发数据就能收到,接收端必须明确表达“我关注这个组”,这个表达动作就是通过IP_ADD_MEMBERSHIP这个setsockopt完成的。它一方面通知本机内核注册组成员关系,另一方面会通过IGMP协议报文告诉交换机/路由器这个端口需要这份数据,否则交换机会把组播帧当广播帧泛洪到所有端口(IPv4组播环境下),或者干脆丢弃。
1.4 C语言实现组播的整体技术方案
明确了组播的工作机制以后,实现方案很简单,本质上就是UDP socket的“扩展用法”。整体上分为两个角色:
发送端:
- 创建UDP socket(
SOCK_DGRAM)。 - 设置组播TTL(
IP_MULTICAST_TTL),控制组播包能跨几个网段。 - 设置组播发送的出口网卡(
IP_MULTICAST_IF),多网卡机器上尤其重要。 - 通过
sendto()往组播地址发送数据。
- 创建UDP socket(
接收端:
- 创建UDP socket。
- 设置
SO_REUSEADDR端口复用,让多个程序能同时绑定同一个组播端口。 - 绑定本机端口(
bind)。 - 通过
IP_ADD_MEMBERSHIP加入组播组。 - 循环
recvfrom()接收数据。
这段链路看似简单,但每一条设置背后都有对应的原理和坑。我在实操环节会一步步拆解。
2. C语言组播核心API与关键参数解析
2.1 发送端三个重要的setsockopt
发送端的代码逻辑比接收端少,但这不代表可以随便写。实际项目里最容易出问题的就是下面三个参数。
IP_MULTICAST_TTL的作用是设置组播包的生存时间。每经过一个路由器TTL减1,减到0就被丢弃。同一个子网内通信TTL设为1就够了;要跨越路由器就需要适当放大。我测试时习惯设一个32,既保证灵活又不会太大导致流量绕远。需要注意的是TTL参数类型是unsigned char,不能用int,否则会设置失败。
IP_MULTICAST_IF用来指定组播报文从哪块网卡发出去。这个在单网卡机器上不设置也可以,但多网卡服务器上如果不设置,系统会默认选择“主网卡”,而主网卡往往不一定是组播业务要走的那个网卡。设置方式是一个struct in_addr结构体,填上本机网卡的IP地址即可。比如下面这段代码就是把组播包从192.168.1.100这个网卡发出去。
IP_MULTICAST_LOOP控制组播包是否会回环发给本机。默认是开启的,也就是说本机也会收到自己发的组播包。如果发送端同时开启了接收逻辑,这个默认行为有时会导致数据重复处理,可以设置成0关闭。
注意:发送端不需要加入组播组就能往组播地址发包。很多新手会以为要加入组,其实不必。发送端只需要知道组播组地址,内核就会根据目标地址构造数据报。
2.2 接收端绑定地址的选择
接收端的第一步是创建socket并绑定端口。这个bind()的细节值得说道说道。
bind()的时候,IP地址建议填INADDR_ANY(0.0.0.0),而不是具体的网卡IP。原因很简单:组播数据可能从任意一张网卡到达,如果绑死了某个IP,其他网卡收到的组播包就递不到这个socket了。内核会按照路由表判断从哪接收,所以填INADDR_ANY是普适做法。
还有一点,如果多个程序需要同时接收同一组播组的数据(比如同一台机器上跑了两套监控服务),就必须设置SO_REUSEADDR。不设置这个选项的话,第二个要绑到同一端口的进程会直接bind()失败,报Address already in use。UDP场景下这个选项没有安全隐患,放心用。
2.3 加入与离开组播组:IP_ADD_MEMBERSHIP详解
接收端最关键的一步就是加入组播组,对应的结构体是struct ip_mreq:
struct ip_mreq { struct in_addr imr_multiaddr; // 组播组地址 struct in_addr imr_interface; // 本机网卡地址,INADDR_ANY表示任意网卡 };调用方式:
struct ip_mreq mreq; inet_pton(AF_INET, "239.0.0.1", &mreq.imr_multiaddr); mreq.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));这段代码干了三件事:通知本机内核把239.0.0.1这个组标记为“已加入”;通过IGMP向交换机上报组成员关系;让内核开始接收该组的数据并递送到这个socket。如果本机是多网卡机器,imr_interface最好明确指定网卡IP,否则系统在IGMP报文和组播数据接收上可能走上错误的物理链路,导致收不到包。
离开组播组的动作是对称的,用IP_DROP_MEMBERSHIP,参数结构体相同。程序退出后socket关闭,内核会自动清理组成员关系,IGMP也会发离开报文,所以业务里不主动调也没问题,但如果你设计了一个动态切换组的逻辑,比如从一个频道切到另一个频道,主动离开再加入是避免串包的必须动作。
2.4 数据收发用到的函数细节
组播数据收发跟普通UDP收发完全一样,发送用sendto(),接收用recvfrom()。
发送端构造目标地址时要填组播组IP和端口:
struct sockaddr_in group_addr; memset(&group_addr, 0, sizeof(group_addr)); group_addr.sin_family = AF_INET; group_addr.sin_port = htons(8888); inet_pton(AF_INET, "239.0.0.1", &group_addr.sin_addr); sendto(sockfd, buf, len, 0, (struct sockaddr *)&group_addr, sizeof(group_addr));接收端的recvfrom()则多了一个来源地址参数,可以通过它拿到发送者的IP和端口,这在做日志、调试、权限校验时非常有用。
还有一个心得:接收socket的接收缓冲区建议调大一点。组播是UDP,如果你处理得慢,内核缓冲区满了新来的包就直接丢掉,recvfrom()不会报错,只会静默丢包。setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size))这个操作我每次都会写,尤其是在音视频推流场景下,默认缓冲区根本扛不住码率。
3. 发送端与接收端完整可跑代码
3.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 MULTI_GROUP "239.0.0.1" #define MULTI_PORT 8888 int main(void) { int sockfd; struct sockaddr_in multi_addr; unsigned char ttl = 32; struct in_addr local_if; char buf[128]; int count = 0; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(EXIT_FAILURE); } // 设置TTL if (setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) { perror("setsockopt TTL"); close(sockfd); exit(EXIT_FAILURE); } // 指定发送网卡(按实际网卡IP修改,单网卡可注释) if (inet_pton(AF_INET, "192.168.1.100", &local_if) == 1) { setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &local_if, sizeof(local_if)); } // 组播目标地址 memset(&multi_addr, 0, sizeof(multi_addr)); multi_addr.sin_family = AF_INET; multi_addr.sin_port = htons(MULTI_PORT); inet_pton(AF_INET, MULTI_GROUP, &multi_addr.sin_addr); while (1) { snprintf(buf, sizeof(buf), "C multicast packet %d", count++); ssize_t n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&multi_addr, sizeof(multi_addr)); if (n < 0) { perror("sendto"); } else { printf("sent: %s\n", buf); } sleep(1); } close(sockfd); return 0; }注意代码里的inet_pton(AF_INET, "192.168.1.100", &local_if)这段,这是我给多网卡场景加的。如果机器是单网卡或者确定默认路由就是组播要走的网卡,这段可以注释掉,不影响功能。
3.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 MULTI_GROUP "239.0.0.1" #define MULTI_PORT 8888 int main(void) { int sockfd; struct sockaddr_in local_addr; struct ip_mreq mreq; char buf[1024]; ssize_t n; int reuse = 1; int rcvbuf_size = 1024 * 1024; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd < 0) { perror("socket"); exit(EXIT_FAILURE); } // 端口复用 if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { perror("SO_REUSEADDR"); close(sockfd); exit(EXIT_FAILURE); } // 增大接收缓冲区,减少突发丢包 setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &rcvbuf_size, sizeof(rcvbuf_size)); // 绑定端口,IP用INADDR_ANY memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_port = htons(MULTI_PORT); local_addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr *)&local_addr, sizeof(local_addr)) < 0) { perror("bind"); close(sockfd); exit(EXIT_FAILURE); } // 加入组播组 memset(&mreq, 0, sizeof(mreq)); inet_pton(AF_INET, MULTI_GROUP, &mreq.imr_multiaddr); mreq.imr_interface.s_addr = htonl(INADDR_ANY); if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { perror("IP_ADD_MEMBERSHIP"); close(sockfd); exit(EXIT_FAILURE); } printf("waiting for multicast packets on %s:%d ...\n", MULTI_GROUP, MULTI_PORT); while (1) { struct sockaddr_in from_addr; socklen_t addr_len = sizeof(from_addr); memset(buf, 0, sizeof(buf)); n = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&from_addr, &addr_len); if (n > 0) { printf("recv %zd bytes from %s:%d: %s\n", n, inet_ntoa(from_addr.sin_addr), ntohs(from_addr.sin_port), buf); } } close(sockfd); return 0; }3.3 编译运行与实际效果演示
编译很简单,不需要链接额外库,直接用gcc就行:
gcc -o multicast_sender multicast_sender.c gcc -o multicast_receiver multicast_receiver.c先把接收端跑起来:
./multicast_receiver再另开一个终端跑发送端:
./multicast_sender接收端终端就会每秒打印一条类似下面的信息:
waiting for multicast packets on 239.0.0.1:8888 ... recv 23 bytes from 192.168.1.100:12345: C multicast packet 0 recv 23 bytes from 192.168.1.100:12345: C multicast packet 1 recv 23 bytes from 192.168.1.100:12345: C multicast packet 2有一条要特别注意:如果接收端和发送端在同一台机器上测试,且发送端没关闭IP_MULTICAST_LOOP,接收端可能会收到重复数据。这在某些需要精确计数的场景(比如帧计数)下会引发问题,排查手段之一就是检查这个回环选项。
3.4 局域网内多台机器联调步骤
如果你准备在两台真机上联调,遵循下面这个顺序成功率最高:
- 确认两台机器在同一二层网络(同一个交换机/AP下),不在同一子网的话要确认路由器启用了组播路由。
- 发送端要是多网卡,先在发送端执行
ip addr找到正确的网卡IP,填进IP_MULTICAST_IF。 - 接收端先跑起来,执行
ip maddr add 239.0.0.1 dev eth0(或者用代码里的加组逻辑)验证接口能加组。 - 发送端跑起来,观察发送端有无“Network is unreachable”之类的报错。
- 接收端没收到包时,优先在发送端执行
tcpdump -i eth0 udp port 8888确认包是否真的发出去了;再在接收端执行同样的命令,确认包是否到达。
我多次调试经验表明,90%的“收不到包”问题出在网卡选择和交换机IGMP Snooping配置上,而不是代码本身。因为代码只要逻辑正确,在这上面反而很少出bug。
4. 组播调试要点、常见问题与避坑经验
4.1 本机回环无法收到数据
同一台机器上一边跑发送端一边跑接收端,结果接收端就是收不到。这个情况的排查思路是先确认IP_MULTICAST_LOOP是不是被意外设置成了0。默认情况下回环是开启的,如果代码里显式关闭了它,本机收不到属于正常现象,这不代表组播功能有问题,换到两台机器上就好了。
如果确认回环选项没问题,再检查是否加入了组播组。可以在本机执行:
netstat -g看输出里有没有239.0.0.1这个组的记录。如果没有,说明加组失败了,检查IP_ADD_MEMBERSHIP的返回值。
4.2 多网卡机器收不到组播
多网卡是组播故障的高发区。情况是这样的:机器上有两张网卡,一张连办公网(192.168.1.x),一张连业务网(10.10.x.x),组播数据从业务网进来,但imr_interface填的是INADDR_ANY,系统选择了一张错误的网卡去接收,或者IGMP报文从另一张网卡发出,交换机那边没把组播数据引到业务口。
解决办法是显式指定网卡IP,业务网卡是10.10.1.10,就这么写:
mreq.imr_interface.s_addr = inet_addr("10.10.1.10");发送端同理,IP_MULTICAST_IF设置成业务网卡IP。这个“显式指定”的思路在多网卡环境下能规避掉绝大多数组播异常。
4.3 交换机IGMP Snooping导致跨接口丢包
组播在交换机层面有个经典问题:如果交换机开启了IGMP Snooping,它只会把组播流量转发到已经通过IGMP报告报文注册过的端口。如果接收端主机的IGMP报告因为某种原因没有到达交换机,交换机会认为这个端口“不关心”该组播组,直接不转发数据。
排查方法不复杂:在接收端执行tcpdump -i eth0 igmp,看能不能抓到IGMP成员报告报文。抓不到的话,要么是防火墙把IGMP报文拦了(有些主机默认防火墙策略会丢弃IGMP),要么就是主机加组操作没成功。你可以临时关闭防火墙服务再测试确认。如果IGMP报文正常发出,交换机配置上也要允许该VLAN的组播转发。
4.4 内核参数与防火墙
Linux环境下手动调整一下防火墙可能也是必要的。有些发行版默认防火墙策略会丢弃目标地址为组播地址的UDP报文,到接收端后直接静默丢弃,recvfrom()永远返回阻塞。测试临时放行可以这样:
iptables -A INPUT -d 239.0.0.1 -j ACCEPT如果涉及跨网段组播,还需要确认内核是否开启了组播路由转发(虽然这是路由器该干的事,但如果你的机器本身就充当路由器,需要检查/proc/sys/net/ipv4/conf/all/mc_forwarding相关配置)。
4.5 常见问题速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 本机收不到自己发的组播 | IP_MULTICAST_LOOP被设置为0 | 设置回1或者用两台机器测试 |
| 绑定端口报Address already in use | 没有设置SO_REUSEADDR | 设置SO_REUSEADDR选项 |
| 局域网跨设备收不到 | 交换机IGMP Snooping没放行、防火墙丢包 | 检查IGMP报文、放行组播端口 |
| 多网卡收不到 | 没有指定网卡 | 显式设置imr_interface为正确网卡IP |
| 收到A组的数据(串包) | 组播MAC地址冲突 | 换一个低23位不同的组播地址 |
| 偶发大量丢包 | 接收缓冲区太小 | 调大SO_RCVBUF,或者使用更大socket缓冲区 |
4.6 IP地址转MAC地址冲突的细节
前面提到过组播IP到MAC地址的映射规则是取IP后23位,这意味着每32个组播IP地址会共享同一个组播MAC地址。比如239.0.0.1和239.128.0.1这两个IP的MAC映射就是一样的。在同一台机器上如果同时加入了这两个组,网卡硬件层无法区分,收到的数据会先全部送进内核,由内核再做一次IP层过滤。过滤后不该本组的数据会被丢弃,但会白白消耗CPU。上层业务如果对性能敏感,选组播地址时最好避开这种碰撞。
我在一个项目里就遇到过两个不同业务组都选了239.x.0.1这种低地址,导致其中一路老是“莫名收到丢包后的残余数据”。后来把一组地址改到239.100.0.1才能彻底区分开。这属于比较冷门的坑,不踩一次真的想不到。
5. 组播实战场景扩展与设计建议
5.1 IPTV与视频直播分发场景
组播最经典的应用就是IPTV。运营商IPTV系统里,频道直播流大多数走的就是组播协议:用户换台时,机顶盒向网络发送IGMP加入报文,加入对应频道的组播组,组播流才会从局端发往用户侧,真正做到了“按需占用带宽”。
C语言在这个场景下通常用在媒体服务器的流输出模块或者网络探针设备上。我在一个类似项目中做过一个流分发模块:发送端从编码器拿RTSP流,解复用后把封好的TS包通过组播地址239.10.0.10:1234发出去;接收端是多个解码盒子,各自加入同一个组。当时遇到的棘手问题就是交换机IGMP Snooping,因为园区网里不同楼栋的交换机配置不一致,有的端口没有配置组播VLAN,导致个别楼栋的黑屏。后来通过梳理交换机配置,统一开启IGMP Snooping并配置组播VLAN后才算彻底解决。
这类场景下对C语言代码的要求其实不高,反而是网络规划占了大部分工作量,代码侧只需保证发送端在切台时能及时发送IP_DROP_MEMBERSHIP和IP_ADD_MEMBERSHIP,并处理好小包突发对缓冲区带来的压力。
5.2 行情数据与物联状态同步场景
金融行情分发也是组播的重度用户。很多行情系统的实时数据推送就采用组播方式:行情服务器把最新价格、成交量等数据发布到组播组,所有订阅了该行情的客户端在同一网段内直接接收。这种模式的好处是链路稳定、时延低,而且无论订阅者数量多少,服务器压力不变。
物联网场景里,组播也有独特的用武之地。比如智能楼宇中几百个传感器节点需要同时收到“开启设备”指令,用组播一条消息就能搞定,比逐个单播效率高太多。在C语言实现上,这类设备端往往使用轻量级的socket封装层,配合定时状态上报,整体代码量和维护成本都很可控。
5.3 组播结合多进程的设计建议
如果你想用组播做负载均衡或多路分发,一个不错的设计是让多个接收端进程共享同一个组播组和端口,并借助SO_REUSEADDR实现“多个socket绑同一端口”。在Linux内核层面,组播数据会被均衡分发到每个绑定了该组播地址和端口的socket上。这天然就是一个负载均衡的架构。
举个例子:我做过一个日志收集器,四台机器同时接收组播日志,每包数据只被一个socket处理。设置方法就是把上面接收端代码里的加组逻辑照搬,然后起四个进程即可。不过要注意,这种均衡分发在内核不同版本上策略略有差异,实际工程中还是建议每个进程独立组存活再汇总。
5.4 基于组播的服务发现思路
组播还能用来做局域网内的服务发现。做法是服务端启动时向224.0.0.251:5353这类地址(mDNS用的是这个)或者自定义的组播组周期发送“我在这里”的公告,客户端启动时向同一组发送查询请求,服务端回包即可。
C语言实现这种方式比引第三方框架更轻量、可控。需要注意两点:查询和服务公告的包都要做去重和超时处理,避免多个服务端响应时客户端处理逻辑被刷爆;此外服务发现包的TTL设小一点,默认1就够了,没必要跨网段扩散。这种思路特别适合局域网网关设备管理、打印机发现、嵌入式设备状态展示这类场景。
6. 最后的实操心得
组播真正写起来代码量很少,核心函数也就那几个。但如果只停留在“能跑通”的层面,一上真实网络环境就可能被各种网络问题折磨。我在实际项目中踩过好几轮坑,最大的体会就是:组播调试的难点从来不是在代码里,而是在网络链路和设备配置里。
所以每次用组播做东西,我都建议先花点时间画一张网络拓扑图,标清楚发送端在哪个网段、接收端在哪个网段、中间经过几台交换机、是否跨路由,然后把上面提到的自测步骤老老实实走一遍。别急着甩锅给代码,先确认链路是通的。
代码上,发送端务必注意网卡选择,接收端务必注意SO_REUSEADDR和加组操作的成功返回值。有条件就在项目一开始就用tcpdump抓包验证通信,越早暴露问题越好解决。最后一个小技巧:开发调试阶段可以把组播包的负载里带上序号和时间戳,配合接收端打印,能一眼看出有没有乱序、重复、丢包,这对定位问题帮助非常大。