当前位置:   article > 正文

Qt实现UDP单播、组播和广播功能_qt udp组播

qt udp组播

一、UDP单播、广播和组播的说明

UDP是不可靠、无连接的,所以划分为发送方和接收方更好理解 

1、单播

UDP是无连接的,进行单播通信时,必须要绑定接收方端口,发送方直接通过接收方的ip和绑定的端口进行通信。发送方可以绑定端口也可以不用绑定端口,不绑定端口的话,系统会随机分配端口。

对于多网卡来说,需要指定网卡,绑定一个网卡的ip。如果不进行显式的绑定操作QUdpSocket 对象将会使用默认的绑定方式,自动选择一个可用的 ip 地址进行绑定。

2、广播

对于只有1个网卡的主机来说,可以不用显示绑定ip,发送方直接发送广播就行,接收方绑定广播端口就行,这样才能看到收到的消息。 

对于多网卡来说,要指定唯一的网卡ip并且在广播前要绑定广播端口。ip可以不用绑定,这样系统会随机分配一个网卡ip

3、组播 

对于只有1个网卡的主机来说,可以不用绑定ip,直接绑定端口后加入组播就行。系统分配任意一个ip ,相当于 QHostAddress::AnyIPv4。

对于多网卡来说,要指定唯一的网卡并且在加入组播前要绑定组播端口,ip可以不用绑定,系统分配任意一个ip

注意:指定网卡不等于指定ip!!! 

1、使用 setMulticastInterface 方法可以指定一个明确的网卡,但并不意味着只有一个 IP 地址。一个网卡可以绑定多个 IP 地址,例如在同一台主机上同时存在有线网卡和无线网卡,它们可能都连接到同一局域网,并分别配置了不同的 IP 地址。此时,通过 setMulticastInterface 方法指定了一个明确的网卡后,并不确定使用哪个 IP 地址来进行组播通信。

如果需要确保使用特定的 IP 地址进行组播通信,则需要使用 bind 方法来将 QUdpSocket 对象绑定到具体的 IP 地址和端口上,这样每次进行组播通信时,都会使用该 IP 地址来发送和接收数据报文。 

2、使用 QHostAddress::AnyIPv4 参数可以将 QUdpSocket 对象绑定到本机的所有 IPv4 地址。这意味着,该 QUdpSocket 对象可以接收通过本机的任意一个 IPv4 地址发送到指定端口的数据包。

然而,需要注意的是,绑定到多个 IPv4 地址并不意味着可以同时从多个地址接收数据包。在任何给定的时刻,QUdpSocket 对象只能通过一个 IP 地址接收数据包。

当有多个 IPv4 地址可用时,QUdpSocket 对象会选择其中一个地址来接收数据包。这个选择通常由操作系统或网络栈决定,并且可能会受到各种因素的影响,例如网络接口的优先级、路由表等。

因此,使用 QHostAddress::AnyIPv4 参数可以让 QUdpSocket 对象绑定到本机的所有 IPv4 地址,但实际上它只能通过其中一个地址接收数据包。具体使用哪个地址取决于操作系统和网络环境。

二、遇到的UDP通信的问题参考

      关于QT UDP组播的几个问题icon-default.png?t=N7T8https://blog.csdn.net/tom06/article/details/52163665?spm=1001.2014.3001.5506

UDP多播/组播通信,同一局域网下的两台机器通信接收不到数据icon-default.png?t=N7T8https://blog.csdn.net/qq_43290013/article/details/117288296?spm=1001.2014.3001.5506

QT读取网卡列表多网卡绑定组播网卡icon-default.png?t=N7T8https://blog.csdn.net/qq_30727593/article/details/127441711?spm=1001.2014.3001.5506

三、效果与代码

1、在.pro文件中添加如下内容:

QT       += network

2、在.h文件中添加串口所用的头文件

  1. #include <QUdpSocket>
  2. #include <QNetworkInterface>

 3、添加一个QUdpSocket* socket的类成员,并在.cpp中实例化对象:

socket = new QUdpSocket;

 4、扫描可用网口

  1. QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces();
  2. foreach (QNetworkInterface nif, interfaceList) {
  3. // 检查网卡是否有效并已经启用
  4. if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) {
  5. // 将已经启用的网卡名称添加到列表中
  6. enabledInterfaceList.append(nif);
  7. QList<QNetworkAddressEntry> entries = nif.addressEntries();
  8. foreach (QNetworkAddressEntry entry, entries) {
  9. if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
  10. ui->nif_config->addItem(entry.ip().toString());
  11. }
  12. }
  13. }
  14. }

5、对组播进行设置(可以忽略)

  1. //组播的数据的生存期,数据报没跨1个路由就会减1.表示多播数据报只能在同一路由下的局域网内传播
  2. socket->setSocketOption(QAbstractSocket::MulticastTtlOption,1);
  3. //1是允许loopback模式(自发自收),0是阻止。
  4. socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, true);

6、初始化组播设置

  1. int MainWindow::init_group(QUdpSocket *socket, QNetworkInterface &currentInterface, QHostAddress &localAddress, QHostAddress &targetAddress, int localPort)
  2. {
  3. if (targetAddress.isMulticast()) {//isMulticast()判断是否是组播地址
  4. if (localPort == 0) {//判断是否指定本地端口
  5. //bind(localAddress, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)可以换成bind(QHostAddress::AnyIPv4, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)
  6. if (socket->bind(localAddress, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
  7. socket->setMulticastInterface(currentInterface);
  8. if (socket->joinMulticastGroup(targetAddress, currentInterface)) {
  9. qDebug() << "未指定本地端口,加入组播成功";
  10. return 1;
  11. } else {
  12. qDebug() << "未指定本地端口,加入组播失败";
  13. return -1;
  14. }
  15. } else {
  16. qDebug() << "未指定本地端口,绑定失败";
  17. return -1;
  18. }
  19. } else {
  20. if (socket->bind(localAddress, localPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) {
  21. socket->setMulticastInterface(currentInterface);
  22. if (socket->joinMulticastGroup(targetAddress, currentInterface)) {
  23. qDebug() << "指定本地端口,加入组播成功";
  24. return 1;
  25. } else {
  26. qDebug() << "指定本地端口,加入组播失败";
  27. return -1;
  28. }
  29. } else {
  30. qDebug() << "指定本地端口,绑定失败";
  31. return -1;
  32. }
  33. }
  34. } else {
  35. qDebug() << "目标地址不是组播地址";
  36. return -1;
  37. }
  38. // 如果执行到这里,说明没有通过任何返回语句,应该是一个错误
  39. qDebug() << "init_group 函数执行路径错误";
  40. return -1; // 或者抛出异常,取决于您希望如何处理这种情况
  41. }

1、QUdpSocket::ShareAddress: 

  • 这个选项告诉操作系统允许多个 QUdpSocket 对象绑定到同一个地址和端口上。在默认情况下,操作系统可能会阻止多个 socket 绑定到相同的地址和端口,但是如果设置了 ShareAddress 选项,Qt 会尝试在可能的情况下共享这个地址和端口。
  • 在实际应用中,如果需要多个 QUdpSocket 对象同时监听相同的地址和端口,可以使用 ShareAddress 选项来避免绑定失败的问题。
  • 想象一下你和朋友们想在同一个电话号码上收发短信。默认情况下,操作系统可能会阻止多个程序或者多个 QUdpSocket 对象同时使用同一个网络地址(IP 地址)和端口号。但是如果你打开了 ShareAddress 选项,就像是你和朋友们一起共享一个电话号码,大家可以同时收发信息。
  • 这个选项让多个 QUdpSocket 对象可以在同一个网络地址和端口上工作,而不会相互干扰或者造成绑定失败的问题。

 2、QUdpSocket::ReuseAddressHint

  • 这个选项告诉操作系统允许在 UDP Socket 关闭之后,立即重新使用相同的地址和端口。如果没有设置这个选项,操作系统会在 QUdpSocket 关闭后一段时间内保持端口的占用状态,这可能会导致稍后尝试重新绑定失败。
  • 设置 ReuseAddressHint 可以避免在关闭一个 QUdpSocket 后立即重新绑定时遇到 Address already in use 的错误。
  • 想象一下你用一个电话号码打电话,然后挂了电话。在一段时间内,电话号码可能会被暂时保留,不允许其他人再用。这就好比默认情况下,当一个 QUdpSocket 关闭后,操作系统可能会暂时保留使用的网络地址和端口号,不让其他程序立即使用。
  • 如果你设置了 ReuseAddressHint,就像是告诉操作系统,“我关掉电话后,如果其他人想用这个电话号码,可以立刻用,不用等。” 这个选项允许在一个 QUdpSocket 关闭后,立即重新使用相同的网络地址和端口号,而不会遇到“地址已经被占用”的错误。

总结:使用后可以重复ip和端口

7、发送消息

socket->writeDatagram(str,targetAddress,targetPort);

8、接收消息

  1. //接收消息
  2. connect(udpSocket,&QUdpSocket::readyRead,this,[&](){
  3. QByteArray datagram;
  4. datagram.resize(udpSocket->pendingDatagramSize());
  5. udpSocket->readDatagram(datagram.data(), datagram.size());
  6. ui->recvTextEdit->append(datagram);
  7. });

9、退出组播

  1. int MainWindow::exit_group(QUdpSocket *socket ,QNetworkInterface &currentInterface, QHostAddress &targetAddress)
  2. {
  3. if(socket->leaveMulticastGroup(targetAddress,currentInterface)){
  4. socket->abort();
  5. qDebug() << "退出组播成功";
  6. return 1;
  7. }else{
  8. qDebug() << "退出组播失败";
  9. return -1;
  10. }
  11. }

四、自定义UDP工具类

MyUdpTools.h 

  1. #ifndef MYUDPTOOLS_H
  2. #define MYUDPTOOLS_H
  3. #include <QObject>
  4. #include <QThread>
  5. #include <QComboBox>
  6. #include <QUdpSocket>
  7. #include <QMessageBox>
  8. #include <QApplication>
  9. #include <QNetworkDatagram>
  10. #include <QNetworkInterface>
  11. class MyUdpTools : public QObject
  12. {
  13. Q_OBJECT
  14. public:
  15. explicit MyUdpTools(QObject *parent = nullptr);
  16. ~MyUdpTools();
  17. // 单播相关方法
  18. int setupUnicast(QString currentNetInterfacrAddress, quint16 port = 0);
  19. void sendUnicastData(QString targetAddress,quint16 port,const QByteArray &data);
  20. int exitUnicast();
  21. // 组播相关方法
  22. int setupMulticast(QNetworkInterface *netInterface,QString currentNetInterfacrAddress,QString groupAddress, quint16 port = 0);
  23. void sendMulticastData(QString targetAddress,quint16 port,const QByteArray &data);
  24. int exitMulticast();
  25. // 广播相关方法
  26. int setupBroadcast(QString currentNetInterfacrAddress,QString targetAddress,quint16 port = 0);
  27. void sendBroadcastData(QString targetAddress,quint16 port,const QByteArray &data);
  28. int exitBroadcast();
  29. //添加或刷新combox的网卡信息
  30. void init_or_flash_NetInterfaceInfoToCombox(QList<QNetworkInterface> &list,QComboBox *combox);
  31. signals:
  32. void sig_RecvData(QByteArray data);
  33. private slots:
  34. void processPendingDatagrams();//接收数据槽函数
  35. void handleSocketError(QAbstractSocket::SocketError socketError);//Udp错误日志
  36. private:
  37. QThread *thread;
  38. QHostAddress localAddress;//本机当前网卡ip
  39. QUdpSocket *unicastSocket;//单播
  40. quint16 unicastPort_Local;//单播本地端口
  41. quint16 unicastPort_Target;//单播目标端口
  42. QHostAddress targetAddress_Unicast;//单播目标地址
  43. QUdpSocket *multicastSocket;//组播
  44. QHostAddress multicastGroupAddress;//组播地址
  45. quint16 multicastPort_Local;//组播本地端口
  46. quint16 multicastPort_Target;//组播目标端口
  47. QUdpSocket *broadcastSocket;//广播
  48. QHostAddress broadcastGroupAddress;//广播地址
  49. quint16 broadcastPort_Local;//广播本地端口
  50. quint16 broadcastPort_Target;//广播目标端口
  51. };
  52. #endif // MYUDPTOOLS_H

MyUdpTools.cpp

  1. #include "myudptools.h"
  2. MyUdpTools::MyUdpTools(QObject *parent)
  3. : QObject(parent),unicastSocket(nullptr),multicastSocket(nullptr),broadcastSocket(nullptr)
  4. {
  5. thread = new QThread();
  6. moveToThread(thread);
  7. //应用程序关闭后触发
  8. connect(qApp,&QApplication::aboutToQuit,thread, &QThread::quit);
  9. //线程退出后触发
  10. connect(thread, &QThread::finished, thread, &QThread::deleteLater);
  11. connect(thread,&QThread::finished,this,&MyUdpTools::deleteLater);
  12. // 在子线程中直接调用函数
  13. QMetaObject::invokeMethod(this, "init_or_flash_NetInterfaceInfoToCombox", Qt::QueuedConnection);
  14. //启动线程
  15. thread->start();
  16. }
  17. MyUdpTools::~MyUdpTools()
  18. {
  19. if(unicastSocket){
  20. delete unicastSocket;
  21. }
  22. if(multicastSocket){
  23. delete multicastSocket;
  24. }
  25. if(broadcastSocket){
  26. delete broadcastSocket;
  27. }
  28. }
  29. //===========================单播相关方法实现===========================
  30. int MyUdpTools::setupUnicast(QString currentNetInterfacrAddress, quint16 port)
  31. {
  32. if (!unicastSocket) {
  33. unicastSocket = new QUdpSocket(this);//将父对象设置为当前对象
  34. connect(unicastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams);
  35. connect(unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
  36. qInfo() << "创建单播socket";
  37. }
  38. localAddress = QHostAddress(currentNetInterfacrAddress);
  39. unicastPort_Local = port;
  40. if(port == 0){
  41. if (!unicastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) {
  42. qCritical() << "单播绑定失败:" << unicastSocket->errorString();
  43. return -1;
  44. }
  45. }else{
  46. if (!unicastSocket->bind(localAddress, unicastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) {
  47. qCritical() << "单播绑定失败:" << unicastSocket->errorString();
  48. return -1;
  49. }
  50. }
  51. qInfo() << "单播连接成功";
  52. return 1;
  53. }
  54. void MyUdpTools::sendUnicastData(QString targetAddress, quint16 port, const QByteArray &data)
  55. {
  56. if(unicastSocket){
  57. targetAddress_Unicast = QHostAddress(targetAddress);
  58. unicastPort_Target = port;
  59. if(unicastSocket->writeDatagram(data,targetAddress_Unicast,unicastPort_Target) == -1){
  60. qCritical() << "发送单播数据失败:" << unicastSocket->errorString();
  61. }
  62. }
  63. }
  64. int MyUdpTools::exitUnicast()
  65. {
  66. if (unicastSocket) {
  67. unicastSocket->close();
  68. qCritical() << "退出成功";
  69. return 1;
  70. }else{
  71. qCritical() << "已退出单播,无需再退出";
  72. return -1;
  73. }
  74. }
  75. //===========================组播相关方法实现===========================
  76. int MyUdpTools::setupMulticast(QNetworkInterface *netInterface,QString currentNetInterfacrAddress, QString groupAddress, quint16 port)
  77. {
  78. if(!multicastSocket){
  79. multicastSocket = new QUdpSocket(this);
  80. connect(multicastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams);
  81. connect(multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
  82. qInfo() << "创建组播socket";
  83. }
  84. localAddress = QHostAddress(currentNetInterfacrAddress);
  85. multicastGroupAddress = QHostAddress(groupAddress);
  86. multicastPort_Local = port;
  87. if(!multicastGroupAddress.isMulticast()){
  88. qWarning() << "不是组播地址";
  89. return -1;
  90. }
  91. if(port == 0){
  92. if(!multicastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)){
  93. qCritical() << "组播绑定失败:" << multicastSocket->errorString();
  94. return -1;
  95. }
  96. }else{
  97. if(!multicastSocket->bind(localAddress,multicastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)){
  98. qCritical() << "组播绑定失败:" << multicastSocket->errorString();
  99. return -1;
  100. }
  101. }
  102. multicastSocket->setMulticastInterface(*netInterface);
  103. if(!multicastSocket->joinMulticastGroup(multicastGroupAddress,*netInterface)){
  104. qCritical() << "加入组播失败:" << multicastSocket->errorString();
  105. return -1;
  106. }
  107. qInfo() << "组播连接成功";
  108. return 1;
  109. }
  110. void MyUdpTools::sendMulticastData(QString targetAddress, quint16 port, const QByteArray &data)
  111. {
  112. if(multicastSocket){
  113. multicastGroupAddress = QHostAddress(targetAddress);
  114. multicastPort_Target = port;
  115. if(multicastSocket->writeDatagram(data,multicastGroupAddress,multicastPort_Target) == -1){
  116. qCritical() << "发送组播数据失败:" << multicastSocket->errorString();
  117. }
  118. }
  119. }
  120. int MyUdpTools::exitMulticast()
  121. {
  122. if (multicastSocket) {
  123. multicastSocket->leaveMulticastGroup(multicastGroupAddress);
  124. multicastSocket->close();
  125. qInfo() << "退出组播成功";
  126. return 1;
  127. }else{
  128. qCritical() << "已退出组播,无需再退出";
  129. return -1;
  130. }
  131. }
  132. //===========================广播相关方法实现===========================
  133. int MyUdpTools::setupBroadcast(QString currentNetInterfaceAddress,QString targetAddress, quint16 port)
  134. {
  135. if (!broadcastSocket) {
  136. broadcastSocket = new QUdpSocket(this);//将父对象设置为当前对象
  137. connect(broadcastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams);
  138. connect(broadcastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
  139. qInfo() << "创建广播socket";
  140. }
  141. localAddress = QHostAddress(currentNetInterfaceAddress);
  142. broadcastPort_Local = port;
  143. broadcastGroupAddress = QHostAddress(targetAddress);
  144. if(!broadcastGroupAddress.isBroadcast()){
  145. qCritical() << "不是广播地址";
  146. return -1;
  147. }
  148. if(port == 0){
  149. if (!broadcastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) {
  150. qCritical() << "广播绑定失败:" << broadcastSocket->errorString();
  151. return -1;
  152. }
  153. }else{
  154. if (!broadcastSocket->bind(localAddress, broadcastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) {
  155. qCritical() << "广播绑定失败:" << broadcastSocket->errorString();
  156. return -1;
  157. }
  158. }
  159. qInfo() << "连接成功";
  160. return 1;
  161. }
  162. void MyUdpTools::sendBroadcastData(QString targetAddress, quint16 port, const QByteArray &data)
  163. {
  164. if(broadcastSocket){
  165. broadcastGroupAddress = QHostAddress(targetAddress);
  166. broadcastPort_Target = port;
  167. if(broadcastSocket->writeDatagram(data,broadcastGroupAddress,broadcastPort_Target) == -1){
  168. qCritical() << "发送广播数据失败:" << broadcastSocket->errorString();
  169. }
  170. }
  171. }
  172. int MyUdpTools::exitBroadcast()
  173. {
  174. if (broadcastSocket) {
  175. broadcastSocket->close();
  176. qInfo() << "退出成功";
  177. return 1;
  178. }else{
  179. qDebug() << "已退出广播,无需再退出";
  180. return -1;
  181. }
  182. }
  183. //===========================添加或刷新combox的网卡信息函数实现===========================
  184. void MyUdpTools::init_or_flash_NetInterfaceInfoToCombox(QList<QNetworkInterface> &list, QComboBox *combox)
  185. {
  186. list.clear();
  187. combox->clear();
  188. QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces();
  189. foreach (QNetworkInterface nif, interfaceList) {
  190. // 检查网卡是否有效并已经启用
  191. if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) {
  192. // 将已经启用的网卡名称添加到列表中
  193. list.append(nif);
  194. QList<QNetworkAddressEntry> entries = nif.addressEntries();
  195. foreach (QNetworkAddressEntry entry, entries) {
  196. if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) {
  197. combox->addItem(entry.ip().toString());
  198. }
  199. }
  200. }
  201. }
  202. }
  203. //===========================接收数据槽函数实现===========================
  204. void MyUdpTools::processPendingDatagrams()
  205. {
  206. QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());//sender()发送信号的对象的指针
  207. if (!socket) return;
  208. while (socket->hasPendingDatagrams()) {
  209. #if 1
  210. QByteArray datagram;
  211. datagram.resize(socket->pendingDatagramSize());
  212. socket->readDatagram(datagram.data(), datagram.size());
  213. qDebug() << "Received Data:" << datagram;
  214. emit sig_RecvData(datagram);
  215. #else
  216. QNetworkDatagram datagram = socket->receiveDatagram();
  217. QByteArray data = datagram.data();
  218. emit sig_RecvData(data);
  219. #endif
  220. }
  221. }
  222. //===========================Udp错误日志信息===========================
  223. void MyUdpTools::handleSocketError(QAbstractSocket::SocketError socketError)
  224. {
  225. QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());
  226. if (!socket) return;
  227. switch (socketError) {
  228. case QAbstractSocket::HostNotFoundError:
  229. qCritical() << "Host not found error:" << socket->errorString();
  230. break;
  231. case QAbstractSocket::ConnectionRefusedError:
  232. qCritical() << "Connection refused error:" << socket->errorString();
  233. break;
  234. case QAbstractSocket::DatagramTooLargeError:
  235. qCritical() << "Datagram too large error:" << socket->errorString();
  236. break;
  237. default:
  238. qCritical() << "Socket error:" << socket->errorString();
  239. break;
  240. }
  241. }

注意:

为什么new QThread();不能指定父对象为this?

如果指定父对象为this的话, QThread 的生命周期就由其父对象MyUdpTools管理,不是由QThread直接管理,这不是推荐的做法。

不同线程之间的通信要用信号与槽进行通信。

在子线程中不能调用函数,会影响线程安全,可以在主线程中使用函数QMetaObject::invokeMethod 使得 子线程能直接调用函数。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/936700
推荐阅读
相关标签
  

闽ICP备14008679号