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

10 篇文章 0 订阅
4 篇文章 0 订阅

背景

在一个跨平台的桌面项目中,由于涉及多线程中对象的创建、销毁等,基于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.csdn.net/rcfalcon/article/details/35221759

https://blog.csdn.net/xqligong/article/details/106124408

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

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

https://blog.csdn.net/qing310820/article/details/103408235

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值