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

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Linux 平台下,使用 Qt 实现 UDP 组播需要以下步骤: 1. 创建 QUdpSocket 对象,并绑定到本地地址和端口号。 2. 加入一个组播组,使用 QUdpSocket 的 joinMulticastGroup() 函数,传入组播地址。 3. 使用 QUdpSocket 的 writeDatagram() 函数发送数据。 4. 在数据接收端,使用 QUdpSocket 的 bind() 函数绑定到组播地址和端口号,然后使用 QUdpSocket 的 readDatagram() 函数接收数据。 下面是一个简单的示例代码: ```cpp // 创建 QUdpSocket 对象 QUdpSocket *socket = new QUdpSocket(this); // 绑定到本地地址和端口号 socket->bind(QHostAddress::AnyIPv4, 1234); // 加入组播组 socket->joinMulticastGroup(QHostAddress("224.0.0.1")); // 发送数据 QByteArray data = "Hello, multicast!"; socket->writeDatagram(data, QHostAddress("224.0.0.1"), 1234); // 接收数据 connect(socket, &QUdpSocket::readyRead, [=]() { while (socket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(socket->pendingDatagramSize()); QHostAddress sender; quint16 senderPort; socket->readDatagram(datagram.data(), datagram.size(), &sender, &senderPort); qDebug() << "Received multicast data from" << sender.toString() << ":" << senderPort << ":" << datagram; } }); ``` 在这个示例中,我们创建了一个 QUdpSocket 对象,并绑定到本地地址和端口号 1234。然后加入了组播组 224.0.0.1,并使用 writeDatagram() 函数发送了一条数据。最后,在 readyRead 信号槽中接收数据,并使用 readDatagram() 函数读取数据和发送方地址信息。这个示例中使用了 Lambda 表达式来连接信号槽,你也可以使用常规的方式连接信号槽。 需要注意的是,组播地址是一个特殊的地址,不能被分配给单个主机。组播地址范围为 224.0.0.0 ~ 239.255.255.255,其中 224.0.0.0 ~ 224.0.0.255 是本地链路组播地址,只能在本地网络内使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值