linux下UDP组播接收不到数据的说明

本文介绍了一种基于libuv的跨平台组播数据接收方法。针对Windows与Linux平台的差异,详细阐述了如何通过调整组播地址绑定策略来确保程序在不同操作系统上都能正常工作。

背景

在一个跨平台的桌面项目中,由于涉及多线程中对象的创建、销毁等,基于QT的对象绑定机制(QObject子类)来做实现时,需要相当心累的设计,才能避免跨线程的异常。由于QT的这个天然机制,在实现很多业务(非界面)模块时,都避免了基于QObject。网络模块中的UDP等功能,同样的也未基于QUdpSocket及其相关,而是包装了libuv(尽管,对桌面来说libuv也需要再折腾,但libuv作者已经明示了这个点了)

 

现象

基于libuv的程序示例,在windows下,可以正常工作(接收到组播数据),但是,同样的代码,在linux下无法接收到组播数据。

大概的实现流程是:按照libuv的官方说明(初始化sockaddr结构udp操作),使用uv_ip4_addr、uv_udp_init、uv_udp_bind、uv_udp_set_membership等接口,很快就实现了一个可接收组播的原型,大概如下:

uv_udp_t* udpHandle = new uv_udp_t;
sockaddr_in addr;
int iRet = -1;
std::string localIP = "192.168.2.102";	// 本机的某个网卡的IP
std::string multicastIP = "224.1.1.8";	// 加入的组播IP,以接收数据
int localPort = 15909;
iRet = ::uv_ip4_addr(localIP.c_str(), localPort, &addr);
iRet = ::uv_udp_init(objLoop, udpHandle);
iRet = ::uv_udp_bind(udpHandle, (const sockaddr*)&addr, UV_UDP_REUSEADDR);
iRet = ::uv_udp_set_membership(udpHandle, multicastIP.c_str(),
							   localIP.c_str(), UV_JOIN_GROUP);

分析

由于linux组播实操经历的缺失,先期主要是搜看各类文章:有些文章,直接采用了修改路由等各种配置解决;有些文章,也采用了linux下绑定0.0.0.0即any的方式来接所有;另外一些,也说明了linux下与windows中组播的区别(但是,并没有适当的关键字以方便大家的搜索)。

最终,还是又继续翻看了UNIX网络编程(中文 第3版)的第21章。其中直接有关的是:

21.9节开始的模糊不清的文字说明,以及图21-14的代码示例(2、12-17行),还有sap.mcast.net对应着(224.2.127.254);

21.10节,这段话“我们想要给接收套接字捆绑多播组和端口,譬如说239.255.1.2端口8888。(回顾一下,我们可以只捆绑通配IP地址和端口8888,不过还是捆绑多播地址以防止目的端口同为8888的其他数据报到达本套接字)”(无论允许与否,我都想吐槽一下的:这种不加标点的长句子,是在锻炼国码奴的堆栈能力吧)。下面是英文版书中的对应文字(反倒是直观了):

We want the receiving socket to bind the multicast group and port, say 239.255.1.2 port 8888. (Recall that we could just bind the wildcard IP address and port 8888, but binding the multicast address prevents the socket from receiving any other datagrams that might arrive destined for port 8888.)

 

与本问题,间接有关的是,第21.2、21.3小节的原理性说明。

由以上,在linux下接收组播数据时,终于可以舒适安心的采用先绑定组播IP的方式进行实现了。

 

方案

1、基于libuv的方案示例

uv_udp_t* udpHandle = new uv_udp_t;
sockaddr_in addr;
int iRet = -1;
std::string localIP = "192.168.2.102";	// 本机的某个网卡的IP
std::string multicastIP = "224.1.1.8";	// 加入的组播IP,以接收数据
int localPort = 15909;

#if defined(_WIN32)
iRet = ::uv_ip4_addr(localIP.c_str(), localPort, &addr);
#else
// 下面是linux下,与windows的不同点
iRet = ::uv_ip4_addr(multicastIP.c_str(), localPort, &addr);	
#endif

// 以下是通用的
iRet = ::uv_udp_init(libuvLoop, udpHandle);
iRet = ::uv_udp_bind(udpHandle, (const sockaddr*)&addr, UV_UDP_REUSEADDR);
iRet = ::uv_udp_set_membership(udpHandle, multicastIP.c_str(),
							   localIP.c_str(), UV_JOIN_GROUP);

2、使用原生实现的方案示例

可能你在项目中,并不使用libuv,因此,本文也给出一个大概的示例

int udpSocket = socket(AF_INET, SOCK_DGRAM, 0);

sockaddr_in addr;
int iRet = -1;
std::string localIP = "192.168.2.102";	// 本机的某个网卡的IP
std::string multicastIP = "224.1.1.8";	// 加入的组播IP,以接收数据
int localPort = 15909;

addr.sin_family = AF_INET;
addr.sin_port = htons(localRecvPort);				// 本地想接收的端口

#if defined(_WIN32)
addr.sin_addr.s_addr = inet_addr(localIP.c_str());	// 本机的某个网卡的IP
#else
// 下面是linux下,与windows的不同点
// linux加入组播时,本机需绑定0.0.0.0或者组播地址
addr.sin_addr.s_addr = inet_addr(multicastIP.c_str());	
#endif

bind(udpSocket, (sockaddr *)&saddr, sizeof(addr));
int reuseOn = 1;
setsockopt(udpSocket, SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn));
ip_mreq mreq;  
mreq.imr_multiaddr.s_addr = inet_addr(multicastIP.c_str());  
mreq.imr_interface.s_addr = inet_addr(localIP.c_str());  
setsockopt(udpSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

 

相关文章

https://blog.youkuaiyun.com/rcfalcon/article/details/35221759

https://blog.youkuaiyun.com/xqligong/article/details/106124408

https://stackoverflow.com/questions/32824029/upnp-bind-with-multicast

https://www.cnblogs.com/lifan3a/articles/6780760.html

https://blog.youkuaiyun.com/qing310820/article/details/103408235

<think>我们正在解决Linux系统无法接收UDP的问题。根据用户的问题,我们需要分析可能的原因并提供解决方案。 根据引用[1],我们注意到在UDP发送时,当内核内存不足(ENOBUFS)或发送队列已满(SOCK_NOSPACE)时,会增加SNDBUFERRORS统计。虽然这主要针对发送,但接收问题可能涉及不同的方面。 引用[2]提到了VF(虚拟功能)链接的设置,这可能与网络接口的虚拟化有关,但用户问题未明确涉及虚拟化环境,所以我们暂时不考虑。 引用[3]提到了IP协议的要求,特别是版本号必须为4,校验和必须正确,以及地址(Class D)的使用。这提示我们在接收时,需要确保地址的正确性,以及数据包符合IP协议。 常见原因和解决方案: 1. 网络接口未加入:使用`ip maddr`或`netstat -g`检查接口是否已加入。 2. 路由问题:确保路由表正确,流量被正确路由到接口。 3. 防火墙:检查防火墙设置,是否阻止了流量(UDP特定端口和地址)。 4. 内核参数:检查相关内核参数,如`rp_filter`(反向路径过滤)可能丢弃包。 5. 应用程序绑定:确保应用程序绑定了正确的接口和地址。 步骤: 1. 检查成员资格: 命令:`ip maddr show` 或 `netstat -g` 如果接口未加入,需要使用程序加入(如使用setsockopt的IP_ADD_MEMBERSHIP)或手动加入(但通常由应用程序完成)。 2. 检查路由: 命令:`ip route show` 或 `route -n` 路由通常由路由协议(如PIM)管理,但在简单网络中,可能只需要默认路由。确保有到地址的路由。 3. 检查防火墙: 命令:`iptables -L -v -n` 或 `firewall-cmd --list-all`(如果使用firewalld) 需要允许目标为地址(224.0.0.0/4)的UDP数据包通过。 4. 调整内核参数: - 检查反向路径过滤:`sysctl net.ipv4.conf.all.rp_filter` 和 `sysctl net.ipv4.conf.<interface>.rp_filter` 如果rp_filter为严格模式(1)或严格模式(2),可能会丢弃包。可以尝试将其设置为0(关闭)或2(宽松模式)。 设置方法:`sysctl -w net.ipv4.conf.all.rp_filter=0` 或针对特定接口设置。 - 确保启用:`sysctl net.ipv4.icmp_echo_ignore_broadcasts` 应该为0(默认),但这不是通常默认启用。 - 检查接收缓冲区大小:如果流量大,可能需要增加UDP接收缓冲区大小。 参数:`net.core.rmem_default`和`net.core.rmem_max`,以及`net.ipv4.udp_mem`。 5. 使用工具抓包分析: 使用`tcpdump`或`wireshark`抓包,确认数据包是否到达接口。 命令:`tcpdump -i <interface> -n host <multicast-ip> and udp port <port>` 6. 检查应用程序: 确保应用程序正确设置了套接字选项(IP_ADD_MEMBERSHIP)并绑定了正确的接口和端口。 根据引用[3],还要确保地址在224.0.0.0到239.255.255.255之间,并且应用程序使用的端口和地址与发送方匹配。 如果以上步骤都检查了,问题仍然存在,可能需要考虑网络设备(交换机、路由器)的配置,如IGMP snooping是否启用,以及是否允许流量通过。 生成相关问题: 1. 如何检查Linux系统中的成员资格? 2. 如何调整Linux内核参数以优化接收? 3. 在Linux中,防火墙如何设置允许流量? 4. 如何诊断网络路由问题导致的接收失败? 5. 应用程序开发中需要注意哪些套接字选项?</think>### Linux UDP接收问题解决方案 #### 1. **检查成员资格** - 使用命令验证接口是否已加入: ```bash netstat -g | grep <地址> # 或 ip maddr show dev <接口名> ``` - 若未加入,需在应用程序中调用 `setsockopt()` 加入: ```c struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = inet_addr("地址"); mreq.imr_interface.s_addr = INADDR_ANY; setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); ``` #### 2. **验证防火墙配置** - 检查防火墙是否允许流量(224.0.0.0/4): ```bash iptables -L -v | grep 224.0.0.0/4 ``` - 临时允许流量: ```bash iptables -A INPUT -d 224.0.0.0/4 -j ACCEPT ``` #### 3. **检查内核路由与过滤** - 关闭反向路径过滤(关键步骤): ```bash sysctl -w net.ipv4.conf.all.rp_filter=0 sysctl -w net.ipv4.conf.<接口名>.rp_filter=0 ``` - 永久生效:在 `/etc/sysctl.conf` 添加: ``` net.ipv4.conf.all.rp_filter=0 net.ipv4.conf.default.rp_filter=0 ``` #### 4. **确认网络接口状态** - 确保接口支持: ```bash ip link show <接口名> | grep MULTICAST ``` - 若显示 `MULTICAST` 表示支持。 #### 5. **验证路由** - 检查路由表: ```bash route -n | grep 224.0.0.0 ``` - 若无路由,添加默认路由: ```bash ip route add 224.0.0.0/4 dev <接口名> ``` #### 6. **诊断工具使用** - 抓包分析流量: ```bash tcpdump -i <接口名> -nn 'udp and dst 地址' ``` - 检查内核丢包统计: ```bash netstat -su | grep "packets received" ``` #### 7. **内核缓冲区调整** - 增大UDP接收缓冲区(适用于高流量场景): ```bash sysctl -w net.core.rmem_max=26214400 sysctl -w net.core.rmem_default=26214400 ``` ### 关键问题排查点 1. **地址范围**:确保使用有效的D类地址(224.0.0.0-239.255.255.255)[^3]。 2. **TTL值**:包的TTL需大于1(`setsockopt` 设置 `IP_MULTICAST_TTL`)。 3. **交换机配置**:若在局域网中,确认交换机启用了IGMP Snooping。 4. **内核兼容性**:如使用虚拟化环境,检查VF驱动兼容性(参考引用[2])。 > **典型错误场景**:当反向路径过滤(`rp_filter`)开启时,内核会丢弃源地址不符合路由表的数据包,这是接收失败的常见原因[^1]。
评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值