UDP组播时最好不要提客户端/服务端的概念,而是提发送端/接收端的概念,避免出现逻辑理解混乱。发送端也需要接收,实际使用的过程中还是得根据业务提服务端/客户端。组播时A端和B端,都可能收发,把它们都加入组播组就可以了,能够达到既能接收也能发送的要求。
注意:如果A端和B端,在同一台机器上,应当注意bind时的端口冲突。
大家在使用QT UDP时,可能会出现下面的情况:
1.接收不到数据
一般我们会这样写:
m_udpSocket = new QUdpSocket();
m_udpSocket->bind(QHostAddress::AnyIPv4,8083,QUdpSocket::ShareAddress);
m_udpSocket->setSocketOption(QAbstractSocket::MulticastLoopbackOption,0);
m_udpSocket->joinMulticastGroup(m_McastAddr);
connect(m_udpSocket,SIGNAL(readyRead()),this ,SLOT(processPendingDatagrams()));
在接收端加入语句
m_udpSocket->setSocketOption(QAbstractSocket::MulticastLoopbackOption,0);
代表禁止本机回环接收,自发自收是不能实现的,值应当改为1。
bind Ip时使用QHostAddress::AnyIPv4是必要的。
2.还是接收不到数据
此时应当考虑网卡是否选对,如果选择了类似loopback_1这样的网卡,就只能在本机(127.0.0.1)上收发了,如果A端和B端分别在两台机器上,除了两台机器都要在一个网段(如192.168.9.0)中,还要选择在这个网段的网卡,因为现在的机器一般都有有线和无线网卡。通过QNetworkInterface可以查看活跃的且支持组播的网卡。
QNetworkInterface(name = "wireless_0",flags = IsUp IsRunning CanBroadcast CanMulticast)
以上代表一个无线网卡可用。像下面这样设置
udp->setMulticastInterface(intf);
另外如果A端和B端分别处于2个不同的网段中,也就是说组播要跨越路由器,组播是可以跨网段的,需要IGMP协议支持,但是本次我的测试,没有成功,没有路由器的访问权限,也就放弃了。我想应当需要到路由器上去设置,查看启用IGMP协议的接口
命令show ip igmp interface //这个是来自网上有待验证
3.readyRead触发后,忘了读导致的问题
我在测试时,槽函数中只写了打印信息,没有调用readDatagram读取数据,A端连续发送数据,B端的readyRead信号只抛出了一次,而后无论A端发多少次,B端始终不抛readyRead信号了。
这是因为QT的socket在抛出readyRead之前,会将数据读取到自己的缓冲区中,如果A端一直发,而B端并不从槽函数中读取数据,那么Qt就会不断的从系统缓冲区中读出数据放置自己内部缓冲区,最后肯定会出现堆栈满的情况,系统异常退出。
同时,第一次数据到来,触发了readyread信号,如果readyRead的槽函数没有来得及执行,而新的数据又来了,也许来了很多次(在QTcpSocket缓存没有满的情况下,满的情况下系统不会再发数据给应用),那么,都将会只再触发一次readyRead信号。
4.关于readDatagram方法
该方法用于读取UDP数据报,第2个参数是读取多少数据(或者说叫buffer大小),如果这个值小于QUdpSocket缓冲区中可读数据的长度,那么剩下没有读取完的数据,将会被丢弃,需要格外注意。
5.Windows能接收,Linux接收不到
以中标麒麟举例,它的防火墙中默认情况下,我们设置的端口号,是没有权限的,所以要在防火墙中放开限制;那么其他Linux操作系统可能也是这种问题。
题外话:connectToHost方法在QUdpSocket中的问题
如果QUdpSocket的对象不调用connectToHost的方法,连接一个对等的用户(peer),将无法使用write/read等标准IO函数,只能使用readDatagram/writeDatagram方法。但是请注意connectToHost给一个无法ping通的IP地址,仍然能够抛出connected信号和hostFound信号,这跟Tcp很不一样。
题外话:编译错误,报socket在不同的线程使用
一般应用中我们会在线程中接收数据,然后用另一个线程解析数据,以达到防止主线程卡死的问题;无论是继承QThread重载run函数;还是将接收对象moveToThread,都需要注意是在主线程还是在子线程中创建的QUdpSocket;
因为QUdpSocket是不支持跨线程使用的,如果首先接收对象构造函数中创建QUdpSocket,然后再将接收对象moveToThread,就会出现这个问题,因为此时线程还没有启动,你就已经new了QUdpSocket了。所以要等到线程的started()信号发出后,再创建QUdpSocket。