Qt —UDP通信QUdpSocket 简介 +案例

1. UDP通信概述


   UDP是无连接、不可靠、面向数据报(datagram)的协议,可以应用于对可靠性要求不高的场合。与TCP通信不同,UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址和端口。

   QUdpSocket以数据报传输数据,而不是以连续的数据流。发送数据报使用函数         QUdpSocket::writeDatagram()数据报的长度一般少于512字节,每个数据报包含发送者和接收者的IP地址和端口等信息。
     UDP数据接收,首先要使用QUdpSocket::bind()绑定一个端口,绑定端口后,socket的状态会变为已绑定状态“BoundState”。

当有数据报传入时,QudpSocket会自动发射readyRead()信号,在其槽函数中使用QUdpSocket::readDatagram()进行数据读取。abort()为解除绑定,解除后socket状态变为未连接状态“UnconnectedState”。

2. UDP消息传送的三种模式

单播模式(unicast):一个UDP客户端发送数据报到指定地址和端口的另一UDP客户端,是一对一的数据传输。

广播模式(broadcast):一个UDP客户端发出的数据报,在同一网络范围内其他所有的UDP客户端都可以收到。QUdpSocket支持IPv4广播。需要在数据报中指定接收端地址为QHostAddress::Broadcast,一般的广播地址是255.255.255.255

组播模式(multicast):UDP客户端加入到另一个组播IP地址的多播组,成员向组播地址发送的数据报,其加入组播的所有成员都可以接收到,类似于QQ群功能。QUdpSocket::joinMulticastGroup()函数实现加入多播组的功能。

QUdpSocket::leaveMulticastGroup()函数实现

在单播、广播和多播模式下,UDP程序都是对等的,不像TCP通信分为客户端和服务端。
TCP通信只有单播模式。UDP通信虽然不能保证数据传输的准确性,但是具有灵活性,一般的即时通信软件都是基于UDP通信的。

3. QUdpSocket类的接口函数

bool bind(quint16 port = 0)  为UDP通信绑定一个端口

qint64 writeDatagram(QByteArray& datagram, QHostAddress& host, quint16 port) 向目标地址和端口的UDP客户端发送数据报,返回成功发送的字节数,数据报的长度一般不超过512字节。

bool hasPendingDatagrams()     当至少有一个数据报需要读取时,返回true

qint64 pendingDatagramSize()    返回第一个待读取的数据报的大小

qint64 readDatagram(char* data, qint64 maxSize) 读取一个数据报,返回成功读取的字节数

qint64 readDatagram(char* data, qint64 maxSize, QHostAddress* address, quint16* port) 读取一个数据报,返回成功读取的字节数。发送方的主机地址和端口存储在*address和*port中(除非指针为0)

bool joinMulticastGroup(QHostAddress& groupAddress)      加入一个多播组

bool leaveMulticastGroup(QHostAddress& groupAddress)    离开一个多播组

void abort() 终止当前连接并重置套接字。通常在析构函数中写入。与disconnectFromHost()不同,该函数立即关闭套接字,丢弃写入缓冲区中的任何挂起数据。
原文链接:https://blog.csdn.net/WL0616/article/details/129050373

4.UDP对话小案例

实现发送和接收端互相发信息,类似QQ  (界面使用UI设计)

4.1.接收端

receiver.h

#ifndef RECESIVER_H
#define RECESIVER_H#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Recesiver; }
QT_END_NAMESPACEclass Recesiver : public QWidget
{Q_OBJECTpublic:Recesiver(QWidget *parent = nullptr);~Recesiver();private slots:void on_pushButton_2_clicked(); //启动槽函数void start();void on_pushButton_clicked();private:Ui::Recesiver *ui;QUdpSocket *receiver;
};
#endif // RECESIVER_H

receiver.cpp

#include "recesiver.h"
#include "ui_recesiver.h"Recesiver::Recesiver(QWidget *parent): QWidget(parent), ui(new Ui::Recesiver)
{ui->setupUi(this);setWindowTitle(QStringLiteral("接收端"));ui->lineEdit->setText("127.0.0.1");receiver =new QUdpSocket(this);
}Recesiver::~Recesiver()
{delete ui;
}//接收信息void Recesiver::start(){QByteArray datagram;datagram.resize(receiver->pendingDatagramSize()); //接收到的数据的长度receiver->readDatagram(datagram.data(),datagram.size());ui->textEdit->append(QStringLiteral("对方:")+datagram);}//启动按钮(发送端发送信息给接收端)
void Recesiver::on_pushButton_2_clicked()
{receiver->bind(ui->lineEdit_2->text().toInt());//设置端口号将其转为整型connect(receiver,&QUdpSocket::readyRead,this,[&](){start();});ui->pushButton_2->setEnabled(false);ui->lineEdit_2->setEnabled(false);
}//发送按钮(接收端发送信息给发送端)
void Recesiver::on_pushButton_clicked()
{QByteArray datagram=ui->textEdit_2->toPlainText().toUtf8();  //在输入端输入发送的内容receiver->writeDatagram(datagram.data(),datagram.size(),QHostAddress(ui->lineEdit->text()),ui->lineEdit_3->text().toInt());//qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);//发送数据,大小,发送主机的ip,发送主机的端口号ui->textEdit->append(QStringLiteral("本机:")+ui->textEdit_2->toPlainText());//发送信息的具体内容在发送端的聊天记录里能体现ui->textEdit_2->clear();
}

4.2发送端

sender.h

#ifndef SENDER_H
#define SENDER_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Sender; }
QT_END_NAMESPACEclass Sender : public QWidget
{Q_OBJECTpublic:Sender(QWidget *parent = nullptr);~Sender();void  start2();
private slots:void on_pushButton_clicked();private:Ui::Sender *ui;QUdpSocket *sender;
};
#endif // SENDER_H

sender.cpp

#include "sender.h"
#include "ui_sender.h"Sender::Sender(QWidget *parent): QWidget(parent), ui(new Ui::Sender)
{ui->setupUi(this);sender=new QUdpSocket(this);setWindowTitle(QStringLiteral("发送端"));//senderui->lineEdit_3->setText("888");sender->bind(ui->lineEdit_3->text().toInt());//绑定端口号connect(sender,&QUdpSocket::readyRead,this,[&](){start2();});//start2();
}void Sender::start2()
{QByteArray datagram;datagram.resize(sender->pendingDatagramSize()); //接收到的数据的长度sender->readDatagram(datagram.data(),datagram.size());ui->textEdit->append(QStringLiteral("对方:")+datagram);
}Sender::~Sender()
{delete ui;
}//发送按钮 发送信息给接收端
void Sender::on_pushButton_clicked()
{QByteArray datagram=ui->textEdit_2->toPlainText().toUtf8();  //在输入端输入发送的内容sender->writeDatagram(datagram.data(),datagram.size(),QHostAddress(ui->lineEdit->text()),ui->lineEdit_2->text().toInt());//qint64 writeDatagram(const char *data, qint64 len, const QHostAddress &host, quint16 port);//发送数据,大小,发送主机的ip,发送主机的端口号ui->textEdit->append(QStringLiteral("本机:")+ui->textEdit_2->toPlainText());//发送信息的具体内容在发送端的聊天记录里能体现ui->textEdit_2->clear();}

结果:

(ps:对话有点搞笑,哈哈哈)

5.4. UDP单播和广播代码示例

4.1 测试说明

  本实例实现UDP通信的单播和广播。两个实例可以运行在同一台计算机上,也可以运行在不同的计算机上。
这里的两个实例是运行在同一台计算机上,需要注意,在同一台计算机上运行时,两个实例需要绑定不同的端口。例如实例A绑定端口1600,实例B绑定端口3200,实例A向实例B发送数据报时,需要指定实例B的端口,这样实例B才能收到数据。
如果两个实例在不同的计算机上运行,则端口可以一样,因为IP地址不同了,不会导致绑定时发生冲突。一般的UDP通信程序都是在不同的计算机上运行的,约定一个固定的端口作为通信端口。

4.2主要程序

用UI设计器件和代码化UI分别设计

ui设计器设计:

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QLabel>
#include <QAbstractSocket>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();void   handleEvents();                                                 // 信号与槽处理private:Ui::MainWindow *ui;QUdpSocket *udpScoket;QLabel *labstateScoket;   //Scoket状态栏标签QString  getLocalIp();  //获取本机的IP地址
private slots:void slotActBindPort();   //绑定端口void slotActUnbindPort(); //解除端口void slotActClearText();  //清空文本框void slotActQuit();       //退出void slotSocketReadyRead();  //读取socket传入的数据void slotSocketStateChanged(QAbstractSocket::SocketState socketState);void on_pushButton_clicked(); //发送信息void on_pushButton_2_clicked(); //广播信息
};
#endif // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTextEdit>
#include <QMessageBox>
#include <QHostInfo>#include <QAction>
#include <QPushButton>
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);QString localIP = getLocalIp(); //获取本机的IP地址this->setWindowTitle(this->windowTitle() + "---IP:" + localIP);ui->comboBox->addItem(localIP); //在目标地址中添加IPudpScoket = new QUdpSocket(this);//状态栏labstateScoket = new QLabel(QStringLiteral("socket状态:"), this);labstateScoket->setMinimumWidth(150);ui->statusBar->addWidget(labstateScoket);handleEvents();ui->spinBox->setMinimum(1);     //设置绑定端口的最大最小值和当前的端口值ui->spinBox->setMaximum(65535);ui->spinBox->setValue(1600);ui->spinBox_2->setMinimum(1);   //设置目标端口的最大最小值和当前的端口值ui->spinBox_2->setMaximum(65535);ui->spinBox_2->setValue(3200);}MainWindow::~MainWindow()
{udpScoket->abort();delete udpScoket;udpScoket = nullptr;delete ui;
}// 信号与槽处理
void MainWindow::handleEvents()
{connect(ui->action, &QAction::triggered, this, &MainWindow::slotActBindPort );connect(ui->action_2, &QAction::triggered, this, &MainWindow::slotActUnbindPort);connect(ui->action_3, &QAction::triggered, this, &MainWindow::slotActClearText);connect(ui->action_4, &QAction::triggered, this, &MainWindow::slotActQuit);connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::on_pushButton_clicked);connect(ui->pushButton_2, &QPushButton::clicked, this, &MainWindow::on_pushButton_2_clicked);connect(udpScoket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);connect(udpScoket, &QUdpSocket::readyRead, this, &MainWindow::slotSocketReadyRead);}QString MainWindow::getLocalIp()
{QString hostName = QHostInfo::localHostName();//获取本机主机名QHostInfo hostInfo = QHostInfo::fromName(hostName);  //返回主机名的IP地址QString localIP = "";QList<QHostAddress> addrList = hostInfo.addresses();   //主机IP地址列表if (!addrList.isEmpty()){for (int i = 0; i < addrList.size(); i++){QHostAddress addr = addrList.at(i);if (QAbstractSocket::IPv4Protocol == addr.protocol()){localIP = addr.toString();break;}}}return localIP;
}//绑定端口
void MainWindow::slotActBindPort()
{quint16 port=ui->spinBox->value();   //绑定本机UDp端口if(udpScoket->bind(port)){ui->textEdit->append(QStringLiteral("**已经绑定成功"));ui->textEdit->append(QStringLiteral("**绑定端口:")+QString::number(udpScoket->localPort()));ui->action->setEnabled(false); //开始绑定失效ui->action_2->setEnabled(true);//解除绑定使能}elseui->textEdit->append(QStringLiteral("绑定失败"));
}//解除端口
void MainWindow::slotActUnbindPort()
{udpScoket->abort();//解除绑定ui->action->setEnabled(true); //开始绑定失效ui->action_2->setEnabled(false);//解除绑定使能ui->textEdit->append(QStringLiteral("**已经解除绑定"));}//清空文本框
void MainWindow::slotActClearText()
{ui->textEdit->clear();
}//退出
void MainWindow::slotActQuit()
{QMessageBox::StandardButton button = QMessageBox::question(this, "", QStringLiteral("是否要退出?"));if (button == QMessageBox::StandardButton::Yes)this->close();
}//读取socket传入的数据
void MainWindow::slotSocketReadyRead()
{while(udpScoket->hasPendingDatagrams()) //当有数据传入数据报{QByteArray  datagtam;datagtam.resize(udpScoket->pendingDatagramSize());//读取数据报大小QHostAddress peerAddr;  //在已连接的状态下,返回对方的socket的地址quint16 peerPort;       //在已连接的状态下,返回对方的socket的端口udpScoket->readDatagram(datagtam.data(),datagtam.size(),&peerAddr,&peerPort);//读取数据报的内容//qint64 readDatagram(char *data, qint64 maxlen, QHostAddress *host = nullptr, quint16 *port = nullptr);QString str=datagtam.data();QString  peer="[From "+peerAddr.toString()+":"+QString::number(peerPort)+"]";ui->textEdit->append( peer + str);}
}//状态栏显示Socket变化情况
void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState)
{switch (socketState){case QAbstractSocket::UnconnectedState: labstateScoket->setText(QStringLiteral("socket状态:UnconnectedState")); break;case QAbstractSocket::HostLookupState: labstateScoket->setText(QStringLiteral("socket状态:HostLookupState")); break;case QAbstractSocket::ConnectingState: labstateScoket->setText(QStringLiteral("socket状态:ConnectingState")); break;case QAbstractSocket::ConnectedState: labstateScoket->setText(QStringLiteral("socket状态:ConnectedState")); break;case QAbstractSocket::BoundState: labstateScoket->setText(QStringLiteral("socket状态:BoundState")); break;case QAbstractSocket::ClosingState: labstateScoket->setText(QStringLiteral("socket状态:ClosingState")); break;default: break;}}//发送信息
void MainWindow::on_pushButton_clicked()
{QString msg = ui->lineEdit->text(); //发送信息QByteArray str = msg.toUtf8();QString targetIp = ui->comboBox->currentText();  //目标IP就是主机地址的ipQHostAddress targetAddr(targetIp);quint16 targetPort = ui->spinBox_2->value();  //目标端口udpScoket->writeDatagram(str, targetAddr, targetPort);//发出数据报ui->textEdit->append("[out] " + msg);ui->lineEdit->clear();ui->lineEdit->setFocus();}
//广播信息
void MainWindow::on_pushButton_2_clicked()
{quint16 targetPort = ui->spinBox_2->value();  //目标端口QString msg=ui->lineEdit->text();QByteArray str = msg.toUtf8();udpScoket->writeDatagram(str, QHostAddress::Broadcast, targetPort);//发出数据报ui->textEdit->append("[broadcast] " + msg);ui->lineEdit->clear();ui->lineEdit->setFocus();}

代码化:

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QAction>
#include <QComboBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QHostInfo>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSpinBox>
#include <QUdpSocket>
#include <QVBoxLayout>namespace Ui {class MainWindow;
}class MainWindow : public QMainWindow {Q_OBJECTpublic:explicit MainWindow(QWidget* parent = 0);~MainWindow();private slots:void slotActBindPort();void slotActUnbindPort();void slotActClearText();void slotActQuit();void slotSocketStateChanged(QAbstractSocket::SocketState socketState);void slotBtnSend();void slotBtnBroad();void slotSocketReadyRead();  //读取socket传入的数据private:Ui::MainWindow* ui;QAction* m_pActBindPort;QAction* m_pActUnbindPort;QAction* m_pActClearText;QAction* m_pActQuit;QWidget* m_pCentralWidget;QLabel* m_pLabBindPort;QLabel* m_PLabTargetAddr;QLabel* m_pLabTargetPort;QSpinBox* m_pSpinBindPort;QComboBox* m_pComboTargetAddr;QSpinBox* m_pSpinTargetPort;QLineEdit* m_pLineEdit;QPushButton* m_pBtnSend;QPushButton* m_pBtnBroad;QPlainTextEdit* m_pPlainText;QLabel* m_pLabState;QUdpSocket* m_pUdpSocket;QString getLocalIP();
};#endif  // MAINWINDOW_H

.cpp

#include "mainwindow.h"
#include <QToolBar>
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);//this->setWindowIcon(QIcon(":/new/prefix1/res/TitleIcon.png"));this->setWindowTitle(QStringLiteral("UDP Send/Receiver"));//工具栏ui->toolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);m_pActBindPort = new QAction(QIcon(":/new/Check.png"), QStringLiteral("绑定端口"), this);m_pActUnbindPort = new QAction(QIcon(":/new/Break.png"), QStringLiteral("结束绑定"), this);m_pActClearText = new QAction(QIcon(":/new/remove.png"), QStringLiteral("清空文本"), this);m_pActQuit = new QAction(QIcon(":/new/back.png"), QStringLiteral("退出"), this);ui->toolBar->addAction(m_pActBindPort);ui->toolBar->addAction(m_pActUnbindPort);ui->toolBar->addSeparator();ui->toolBar->addAction(m_pActClearText);ui->toolBar->addSeparator();ui->toolBar->addAction(m_pActQuit);//界面布局m_pCentralWidget = new QWidget(this);QHBoxLayout* HLay1 = new QHBoxLayout;m_pLabBindPort = new QLabel(QStringLiteral("绑定端口"), m_pCentralWidget);m_pSpinBindPort = new QSpinBox(m_pCentralWidget);m_pSpinBindPort->setMinimum(1);m_pSpinBindPort->setMaximum(65535);m_pSpinBindPort->setValue(3200);m_PLabTargetAddr = new QLabel(QStringLiteral("目标地址"), m_pCentralWidget);m_pComboTargetAddr = new QComboBox(m_pCentralWidget);m_pLabTargetPort = new QLabel(QStringLiteral("目标端口"), m_pCentralWidget);m_pSpinTargetPort = new QSpinBox(m_pCentralWidget);m_pSpinTargetPort->setMinimum(1);m_pSpinTargetPort->setMaximum(65535);m_pSpinTargetPort->setValue(1600);HLay1->addWidget(m_pLabBindPort, 1, Qt::AlignRight);HLay1->addWidget(m_pSpinBindPort, 2);HLay1->addWidget(m_PLabTargetAddr, 1, Qt::AlignRight);HLay1->addWidget(m_pComboTargetAddr, 4);HLay1->addWidget(m_pLabTargetPort, 1, Qt::AlignRight);HLay1->addWidget(m_pSpinTargetPort, 2);QHBoxLayout* HLay2 = new QHBoxLayout;m_pLineEdit = new QLineEdit(m_pCentralWidget);m_pBtnSend = new QPushButton(QStringLiteral("发送消息"), m_pCentralWidget);m_pBtnBroad = new QPushButton(QStringLiteral("广播消息"), m_pCentralWidget);HLay2->addWidget(m_pLineEdit);HLay2->addWidget(m_pBtnSend);HLay2->addWidget(m_pBtnBroad);QVBoxLayout* VLay = new QVBoxLayout(m_pCentralWidget);  //主布局必须设置parent,否则不会显示布局// QVBoxLayout* VLay = new QVBoxLayout();VLay->addLayout(HLay1);VLay->addLayout(HLay2);m_pPlainText = new QPlainTextEdit(m_pCentralWidget);VLay->addWidget(m_pPlainText);this->setCentralWidget(m_pCentralWidget);this->setLayout(VLay);  //设置为窗体的主布。在指定了主布局的parent之后,这句话可有可无QString localIP = getLocalIP();this->setWindowTitle(this->windowTitle() + "---IP:" + localIP);m_pComboTargetAddr->addItem(localIP);m_pUdpSocket = new QUdpSocket(this);//状态栏m_pLabState = new QLabel(QStringLiteral("socket状态:"), this);m_pLabState->setMinimumWidth(150);ui->statusbar->addWidget(m_pLabState);// connectconnect(m_pActBindPort, &QAction::triggered, this, &MainWindow::slotActBindPort);connect(m_pActUnbindPort, &QAction::triggered, this, &MainWindow::slotActUnbindPort);connect(m_pActClearText, &QAction::triggered, this, &MainWindow::slotActClearText);connect(m_pActQuit, &QAction::triggered, this, &MainWindow::slotActQuit);connect(m_pBtnSend, &QPushButton::clicked, this, &MainWindow::slotBtnSend);connect(m_pBtnBroad, &QPushButton::clicked, this, &MainWindow::slotBtnBroad);connect(m_pUdpSocket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &MainWindow::slotSocketReadyRead);
}MainWindow::~MainWindow() {//m_pUdpSocket->abort();//delete m_pUdpSocket;//m_pUdpSocket = nullptr;delete ui;
}void MainWindow::slotActBindPort()
{quint16 port = m_pSpinBindPort->value();  //本机UDP端口if (m_pUdpSocket->bind(port)) {m_pPlainText->appendPlainText(QStringLiteral("**已成功绑定"));m_pPlainText->appendPlainText(QStringLiteral("绑定端口:") + QString::number(m_pUdpSocket->localPort()));//使能m_pActBindPort->setEnabled(false);m_pActUnbindPort->setEnabled(true);} else {m_pPlainText->appendPlainText(QStringLiteral("绑定失败"));}
}void MainWindow::slotActUnbindPort() {m_pUdpSocket->abort();  //解除绑定m_pPlainText->appendPlainText(QStringLiteral("**已解除绑定"));m_pActBindPort->setEnabled(true);m_pActUnbindPort->setEnabled(false);
}void MainWindow::slotActClearText() { m_pPlainText->clear(); }void MainWindow::slotActQuit() {QMessageBox::StandardButton button = QMessageBox::question(this, "", QStringLiteral("是否要退出?"));if (button == QMessageBox::StandardButton::Yes)this->close();
}void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState) {switch (socketState){case QAbstractSocket::UnconnectedState: m_pLabState->setText(QStringLiteral("socket状态:UnconnectedState")); break;case QAbstractSocket::HostLookupState: m_pLabState->setText(QStringLiteral("socket状态:HostLookupState")); break;case QAbstractSocket::ConnectingState: m_pLabState->setText(QStringLiteral("socket状态:ConnectingState")); break;case QAbstractSocket::ConnectedState: m_pLabState->setText(QStringLiteral("socket状态:ConnectedState")); break;case QAbstractSocket::BoundState: m_pLabState->setText(QStringLiteral("socket状态:BoundState")); break;case QAbstractSocket::ClosingState: m_pLabState->setText(QStringLiteral("socket状态:ClosingState")); break;default: break;}}void MainWindow::slotBtnSend()
{QString msg = m_pLineEdit->text();QByteArray str = msg.toUtf8();QString targetIp = m_pComboTargetAddr->currentText();  //目标IPQHostAddress targetAddr(targetIp);quint16 targetPort = m_pSpinTargetPort->value();  //目标端口m_pUdpSocket->writeDatagram(str, targetAddr, targetPort);m_pPlainText->appendPlainText("[out] " + msg);m_pLineEdit->clear();m_pLineEdit->setFocus();}void MainWindow::slotBtnBroad()
{QString msg = m_pLineEdit->text();QByteArray str = msg.toUtf8();quint16 targetPort = m_pSpinTargetPort->value();m_pUdpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort);m_pPlainText->appendPlainText("[out] " + msg);m_pLineEdit->clear();m_pLineEdit->setFocus();}void MainWindow::slotSocketReadyRead()
{while (m_pUdpSocket->hasPendingDatagrams()){QByteArray dataGram;dataGram.resize(m_pUdpSocket->pendingDatagramSize());QHostAddress peerAddress;quint16 peerPort;m_pUdpSocket->readDatagram(dataGram.data(), dataGram.size(), &peerAddress, &peerPort);QString str = dataGram.data();QString peer = "[From " + peerAddress.toString() + ":" + QString::number(peerPort) + "]";m_pPlainText->appendPlainText(peer + str);}
}QString MainWindow::getLocalIP() {QString hostName = QHostInfo::localHostName();QHostInfo hostInfo = QHostInfo::fromName(hostName);QString localIP = "";QList<QHostAddress> addrList = hostInfo.addresses();if (!addrList.isEmpty()) {for (int i = 0; i < addrList.size(); i++) {QHostAddress addr = addrList.at(i);if (QAbstractSocket::IPv4Protocol == addr.protocol()) {localIP = addr.toString();break;}}}return localIP;
}

结果: 

 发现自己用UI设计时,发送信息总会出现两个发送信号,并且有一个是空白的,暂时还没有找到问题所在之处,嘤嘤嘤~(/≧▽≦)/

5. UDP组播代码示例

5.1 组播的特性

组播报文的目的地址使用D类IP地址,D类地址不能出现在IP报文的地址字段。用同一个IP多播地址接收多播数据报的所有主机构成一个组,称为多播组(或组播组)。所有的信息接收者都加入一个组内,并且一旦加入后,流向组地址的数据报立即开始向接收者传输,组中的所有的成员都能接收到数据报。组中的成员是动态的,主机可以在任何人时间加入和离开组。

关于组播IP地址,有以下约定:

224.0.0.0 ~ 224.0.0.255   为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其他地址供路由协议使用。
224.0.1.0 ~ 224.0.1.255   是公用组播地址,可以用于Internet。
224.0.2.0 ~ 238.255.255.255     为用户可用的组播地址(临时组地址),全网范围内有效。
239.0.0.0 ~ 239.255.255.255     为本地管理组播地址,仅在特定的本地范围内有效。所以,若是在家庭或办公室局域网内测试UDP组播功能,可以使用这些IP。
原文链接:https://blog.csdn.net/WL0616/article/details/129050373

常量定义

描述

QUdpSocket::ShareAddress

0x1

1、允许其他服务绑定同样的地址和端口
2、当多进程通过监听同一地址和端口,进而共享单个服务的负载时,将十分有用(例如:一个拥有几个预先建立的监听者的WEB服务器能够改善响应时间)。不过,由于任何服务都允许重新绑定(rebind),该选项应该引起某些安全上的考虑
3、需要注意的是,把该选项和ReuseAddressHint结合,也会允许你的服务重新绑定一个已存在的共享地址
4、在Unix上,该选项等同于SO_REUSEADDR;在Windows上,该选项被忽略

QUdpSocket::DontShareAddress

0x2

1、采用专有的方式绑定某个地址和端口,其他任何服务都不能再重新绑定

2、通过该选项,确保绑定成功,指定的服务将是地址和端口唯一监听者,就算是拥有ReuseAddressHint的服务也不允许重新绑定

3、在安全性上,该选项优于ShareAddress,但是在某些操作系统上需要管理员的权限才能运行

4、在Unix和Mac OS上,绑定地址和端口的默认行为是非共享,所以该选项会被忽略;在Windows上,等同于SO_EXCLUSIVEADDRUSE套接字选项

QUdpSocket::ReuseAddressHint

0x4

1、为QUdpSocke提供提示,即在地址和端口已经被其他套接字绑定的情况下,也应该试着重新绑定

2、在Unix上,该选项被忽略;在Windows上等同于SO_REUSEADDR 套接字选项

QUdpSocket::DefaultForPlatform

0x0

1、当前平台的默认选项

2、在Unix和Mac OS上,该选项等同于DontShareAddress + ReuseAddressHint;在Windows上等同于ShareAddress

5.2主要程序 

.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QAction>
#include <QComboBox>
#include <QHBoxLayout>
#include <QHostInfo>
#include <QLabel>
#include <QLineEdit>
#include <QMainWindow>
#include <QMessageBox>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QRegExp>
#include <QSpinBox>
#include <QUdpSocket>
#include <QVBoxLayout>namespace Ui {class MainWindow;
}class MainWindow : public QMainWindow {Q_OBJECTpublic:explicit MainWindow(QWidget* parent = 0);~MainWindow();private slots:void slotActJoinMulti();void slotActLeaveMulti();void slotActClearText();void slotActQuit();void slotSocketStateChanged(QAbstractSocket::SocketState socketState);void slotBtnMultiMsg();void slotReadyRead();private:Ui::MainWindow* ui;QAction* m_pActJoinMulti;QAction* m_pActLeaveMulti;QAction* m_pActClearText;QAction* m_pActQuit;QWidget* m_pCentralWidget;QLabel* m_pLabPort;QLabel* m_pLabAddr;QSpinBox* m_pSpinPort;QComboBox* m_pComboAddr;QLineEdit* m_pLineEdit;QPushButton* m_pBtnSendMulti;QPlainTextEdit* m_pPlainText;QLabel* m_pLabState;QUdpSocket* m_pUdpSocket;QHostAddress m_multicastAddr;QString getLocalIP();
};#endif  // MAINWINDOW_H

.cpp

#include "mainwindow.h"#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->setWindowTitle(QStringLiteral("UDP Multicast"));//工具栏ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);m_pActJoinMulti = new QAction(QIcon(":/new/prefix1/res/添加群组.png"), QStringLiteral("加入组播"), this);m_pActLeaveMulti = new QAction(QIcon(":/new/prefix1/res/退出群组.png"), QStringLiteral("退出组播"), this);m_pActClearText = new QAction(QIcon(":/new/prefix1/res/清空.png"), QStringLiteral("清空文本"), this);m_pActQuit = new QAction(QIcon(":/new/prefix1/res/退出.png"), QStringLiteral("退出"), this);ui->mainToolBar->addAction(m_pActJoinMulti);ui->mainToolBar->addAction(m_pActLeaveMulti);ui->mainToolBar->addSeparator();ui->mainToolBar->addAction(m_pActClearText);ui->mainToolBar->addSeparator();ui->mainToolBar->addAction(m_pActQuit);//界面布局m_pCentralWidget = new QWidget(this);m_pLabPort = new QLabel(QStringLiteral("组播端口"), m_pCentralWidget);m_pSpinPort = new QSpinBox(m_pCentralWidget);m_pSpinPort->setMinimum(1);m_pSpinPort->setMaximum(65535);m_pSpinPort->setValue(3200);m_pLabAddr = new QLabel(QStringLiteral("组播地址"), m_pCentralWidget);m_pComboAddr = new QComboBox(m_pCentralWidget);m_pComboAddr->setEditable(true);  //下拉框可编辑输入m_pComboAddr->addItem("239.0.0.1");// 正则匹配 D类IP:224.0.0.0~239.255.255.255// .必须使用转义字符\,否则.会匹配任意字符// C++中"\"在字符串中表示要用"\\"// 是 - 不是 ~ ; 是[0-9]不是[0~9]QRegExp regExp("^(22[4-9]|23[0-9])(\\.((\\d)|([1-9]\\d)|(1\\d{2})|(2[0-4]\\d)|(25[0-5]))){3}$");QValidator* pValidator = new QRegExpValidator(regExp, this);m_pComboAddr->setValidator(pValidator);QHBoxLayout* HLay1 = new QHBoxLayout();HLay1->addWidget(m_pLabPort, 1, Qt::AlignRight);HLay1->addWidget(m_pSpinPort, 1);HLay1->addWidget(m_pLabAddr, 1, Qt::AlignRight);HLay1->addWidget(m_pComboAddr, 2);m_pLineEdit = new QLineEdit(m_pCentralWidget);m_pBtnSendMulti = new QPushButton(QStringLiteral("组播消息"), m_pCentralWidget);QHBoxLayout* HLay2 = new QHBoxLayout();HLay2->addWidget(m_pLineEdit, 4);HLay2->addWidget(m_pBtnSendMulti, 1);m_pPlainText = new QPlainTextEdit(m_pCentralWidget);QVBoxLayout* VLay = new QVBoxLayout(m_pCentralWidget);VLay->addLayout(HLay1);VLay->addLayout(HLay2);VLay->addWidget(m_pPlainText);this->setCentralWidget(m_pCentralWidget);this->setLayout(VLay);//状态栏m_pLabState = new QLabel(QStringLiteral("socket状态:"), this);m_pLabState->setMinimumWidth(150);ui->statusBar->addWidget(m_pLabState);QString str = getLocalIP();this->setWindowTitle(this->windowTitle() + "---IP:" + str);m_pUdpSocket = new QUdpSocket(this);m_pUdpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);// connectconnect(m_pActJoinMulti, &QAction::triggered, this, &MainWindow::slotActJoinMulti);connect(m_pActLeaveMulti, &QAction::triggered, this, &MainWindow::slotActLeaveMulti);connect(m_pActClearText, &QAction::triggered, this, &MainWindow::slotActClearText);connect(m_pActQuit, &QAction::triggered, this, &MainWindow::slotActQuit);connect(m_pUdpSocket, &QUdpSocket::stateChanged, this, &MainWindow::slotSocketStateChanged);connect(m_pBtnSendMulti, &QPushButton::clicked, this, &MainWindow::slotBtnMultiMsg);connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &MainWindow::slotReadyRead);
}MainWindow::~MainWindow() { delete ui; }void MainWindow::slotActJoinMulti() {QString ip = m_pComboAddr->currentText();m_multicastAddr = QHostAddress(ip);quint16 multicastPort = m_pSpinPort->value();if (m_pUdpSocket->bind(QHostAddress::AnyIPv4, multicastPort, QUdpSocket::ShareAddress)) {m_pUdpSocket->joinMulticastGroup(m_multicastAddr);  //加入多播组m_pPlainText->appendPlainText("**加入组播成功");m_pPlainText->appendPlainText("**组播地址IP:" + ip);m_pPlainText->appendPlainText("**绑定端口:" + QString::number(multicastPort));m_pActJoinMulti->setEnabled(false);m_pActLeaveMulti->setEnabled(true);m_pComboAddr->setEditable(false);} else {m_pPlainText->appendPlainText("**绑定端口失败");}
}void MainWindow::slotActLeaveMulti() {m_pUdpSocket->leaveMulticastGroup(m_multicastAddr);  //退出组播m_pUdpSocket->abort();                               //解除绑定m_pActJoinMulti->setEnabled(true);m_pActLeaveMulti->setEnabled(false);m_pComboAddr->setEnabled(true);m_pComboAddr->setEditable(true);m_pPlainText->appendPlainText("**已退出组播,解除端口绑定");
}void MainWindow::slotActClearText() { m_pPlainText->clear(); }void MainWindow::slotActQuit() {QMessageBox::StandardButton button = QMessageBox::question(this, "", "是否退出?");if (QMessageBox::StandardButton::Yes == button) {this->close();}
}void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState) {// case并不包含所有的情况,因为没有写listening的情况,所以就需要写defaultswitch (socketState) {case QAbstractSocket::UnconnectedState: m_pLabState->setText("socket状态:UnconnectedState"); break;case QAbstractSocket::HostLookupState: m_pLabState->setText("socket状态:HostLookupState"); break;case QAbstractSocket::ConnectingState: m_pLabState->setText("socket状态:ConnectingState"); break;case QAbstractSocket::ConnectedState: m_pLabState->setText("socket状态:ConnectedState"); break;case QAbstractSocket::BoundState: m_pLabState->setText("socket状态:BoundState"); break;case QAbstractSocket::ClosingState: m_pLabState->setText("socket状态:ClosingState"); break;default: break;}
}void MainWindow::slotBtnMultiMsg() {QString msg = m_pLineEdit->text();QByteArray str = msg.toUtf8();quint16 multiPort = m_pSpinPort->value();m_pUdpSocket->writeDatagram(str, m_multicastAddr, multiPort);m_pPlainText->appendPlainText("[multicast] " + msg);m_pLineEdit->clear();m_pLineEdit->setFocus();
}void MainWindow::slotReadyRead() {while (m_pUdpSocket->hasPendingDatagrams()) {QByteArray dataGram;dataGram.resize(m_pUdpSocket->pendingDatagramSize());QHostAddress peerAddr;quint16 peerPort;m_pUdpSocket->readDatagram(dataGram.data(), dataGram.size(), &peerAddr, &peerPort);QString str = dataGram.data();QString peer = "[From " + peerAddr.toString() + ":" + QString::number(peerPort) + "] ";m_pPlainText->appendPlainText(peer + str);qDebug() << m_pUdpSocket->peerAddress();qDebug() << m_pUdpSocket->localAddress().toString();qDebug() << m_pUdpSocket->localPort();}
}QString MainWindow::getLocalIP() {QString localName = QHostInfo::localHostName();QHostInfo hostInfo = QHostInfo::fromName(localName);QList<QHostAddress> addrList = hostInfo.addresses();QString localIP = "";if (!addrList.isEmpty()) {for (int i = 0; i < addrList.size(); i++) {QHostAddress addr = addrList.at(i);if (QAbstractSocket::IPv4Protocol == addr.protocol()) {localIP = addr.toString();break;}}}return localIP;
}

结果: 

原文链接:https://blog.csdn.net/WL0616/article/details/129050373 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/64425.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot配置ym管理各种日记(log)

1&#xff1a;yml配置mybatis_plus默认日记框架 mybatis-plus:#这个作用是扫描xml文件生效可以和mapper接口文件使用&#xff0c;#如果不加这个,就无法使用xml里面的sql语句#启动类加了MapperScan是扫描指定包下mapper接口生效&#xff0c;如果不用MapperScan可以在每一个mapp…

docker常用中间件安装

文章目录 1、前言2、中间件安装2.1、mysql2.2、gitlab容器2.3、nacos2.4、redis2.5、xxljob2.6、zipkin2.7、sentinel2.8、seata2.8.1、获取镜像2.8.2、运行容器并获取配置 2.9、rockerMQ2.9.1、rockerMQ-namesrv2.9.2、rockerMQ-broker2.9.3、rockerMQ-console 2.10、jenkins2…

每日一题(链表中倒数第k个节点)

每日一题&#xff08;链表中倒数第k个节点&#xff09; 链表中倒数第k个结点_牛客网 (nowcoder.com) 思路: 如下图所示&#xff1a;此题仍然定义两个指针&#xff0c;fast指针和slow指针&#xff0c;假设链表的长度是5&#xff0c;k是3&#xff0c;那么倒数第3个节点就是值为…

【进阶篇】MySQL分库分表详解

文章目录 0. 前言1. 垂直分库分表2. 水平分库分表 1. 理解过程及实现方案问题讨论衍生出分库分表策略借助成熟组件使用分库分表阶段完成后面临的问题1. 异地多活问题2. 数据迁移问题3. 分布式事务问题4. join查询的问题 分库分表的策略实现示例 2. 参考文档 0. 前言 假设有一个…

windows笔记本远程连接如何打开任务管理器?

参考素材&#xff1a; https://jingyan.baidu.com/article/8275fc86a97f5207a03cf6cd.html https://www.anyviewer.cn/how-to/ctrl-alt-delete-remote-desktop-6540.html 网上查了很多方法&#xff0c;都说ctrlaltend可以解决这个问题。 但是笔记本键盘上没有end键。 继续查了一…

【数学建模】清风数模正课5 相关性分析

相关系数 相关性分析的关键是计算相关系数&#xff0c;在本节课中将会介绍两种常用的相关系数&#xff1a;皮尔逊相关系数&#xff08;Pearson&#xff09;和斯皮尔曼相关系数&#xff08;Spearman&#xff09;。 它们可以用来衡量两个变量间相关性的大小&#xff0c;对于不同…

Android 13 - Media框架(9)- NuPlayer::Decoder

这一节我们将了解 NuPlayer::Decoder&#xff0c;学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容&#xff0c;如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单&#xff0c;越读越发现自己的无知&#xff0c;Android 源码真…

安卓 tcp 客户端

安卓 tcp 客户端 Server:8888 是Qt 写的Tcp 服务器 ip 是 192.168.2.103 port是8888 安卓手机运行 kotlin 语法的Tcp Client &#xff0c;连接&#xff0c;收发数据 效果如下图 Tcpclient package com.example.myapplicationimport android.os.Handler import android.os.Loo…

Go操作各大消息队列教程(RabbitMQ、Kafka)

Go操作各大消息队列教程 1 RabbitMQ 1.1 概念 ①基本名词 当前市面上mq的产品很多&#xff0c;比如RabbitMQ、Kafka、ActiveMQ、ZeroMQ和阿里巴巴捐献给Apache的RocketMQ。甚至连redis这种NoSQL都支持MQ的功能。 Broker&#xff1a;表示消息队列服务实体Virtual Host&#x…

深入探讨梯度下降:优化机器学习的关键步骤(一)

文章目录 &#x1f340;引言&#x1f340;什么是梯度下降&#xff1f;&#x1f340;损失函数&#x1f340;梯度(gradient)&#x1f340;梯度下降的工作原理&#x1f340;梯度下降的变种&#x1f340;随机梯度下降&#xff08;SGD&#xff09;&#x1f340;批量梯度下降&#xf…

HTML5+CSS3+JS小实例:科技感满满的鼠标移动推开粒子特效

实例:科技感满满的鼠标移动推开粒子特效 技术栈:HTML+CSS+JS 效果: 源码: 【html】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport&qu…

回归拟合 | 灰狼算法优化核极限学习机(GWO-KELM)MATLAB实现

这周有粉丝私信想让我出一期GWO-KELM的文章&#xff0c;因此乘着今天休息就更新了(希望不算晚) 作者在前面的文章中介绍了ELM和KELM的原理及其实现&#xff0c;ELM具有训练速度快、复杂度低、克服了传统梯度算法的局部极小、过拟合和学习率的选择不合适等优点&#xff0c;而KEL…

HFSS 3维曲线导入

HFSS 3维曲线导入 简介环境参考代码使用结果 简介 如图一所示&#xff0c;CST中可以通过导入和到出由任意点组成的曲线&#xff0c;但是HFSS中貌似不能导入&#xff08;如图二所示&#xff09;&#xff0c;如果我们要将matlab的产生的曲线的点的数据导入特变麻烦&#xff0c;特…

测试验证平台

测试验证平台 1.功能说明&#xff1a; 模拟智能终端车端数据采集及上报的功能&#xff0c;提供数据管理平台的模拟和验证功能。 2.系统组成&#xff1a; 系统示意图 功能要求&#xff1a; 本地电脑实现Imx6配置功能&#xff0c;能够通过运行不同的脚本&#xff0c;模拟不…

大规模网络爬虫系统架构设计 - 云计算和Docker部署

在大规模网络爬虫系统中&#xff0c;合理的架构设计和高效的部署方式是确保系统稳定性和可扩展性的关键。本文将介绍如何利用云计算和Docker技术进行大规模网络爬虫系统的架构设计和部署&#xff0c;帮助你构建高效、可靠的爬虫系统。 1、架构设计原则 在设计大规模网络爬虫系…

英码科技受邀亮相2023WAIE物联网与人工智能展,荣获行业优秀创新力产品奖!

8月28日-30日&#xff0c;2023WAIE 物联网与人工智能展在深圳福田会展中心顺利举办。英码科技受邀亮相本届展会&#xff0c;并现场重点展出了面向智慧交通、智慧校园、智慧应急、智慧园区等不同行业的创新AIoT产品、AI技术服务等内容&#xff0c;与生态伙伴积极探讨市场需求和问…

CentOS配置Java环境报错-bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件

CentOS配置Java环境后执行java -version时报错&#xff1a; -bash: /usr/local/jdk1.8.0_381/bin/java: 无法执行二进制文件原因是所使用的jdk的版本和Linux内核架构匹配不上 使用以下命令查看Linux架构&#xff1a; [rootlocalhost ~]# cat /proc/version Linux version 3.1…

vue3中右侧26个英文字母排列,点击字母,平滑到响应内容

效果图如下&#xff1a; 右侧悬浮 <!-- 右侧悬浮组件 --><div class"right-sort"><div v-for"(item, index) in list" :key"index" class"sort-item" :class"index activeIndex ? sort-item-active : " c…

Spring IOC的理解

总&#xff1a; 控制反转&#xff08;IOC&#xff09;&#xff1a;理论思想&#xff0c;传统java开发模式&#xff0c;对象是由使用者来进行管理&#xff0c;有了spring后&#xff0c;可以交给spring来帮我们进行管理。依赖注入&#xff08;DI&#xff09;&#xff1a;把对应的…

音频——I2S DSP 模式(五)

I2S 基本概念飞利浦(I2S)标准模式左(MSB)对齐标准模式右(LSB)对齐标准模式DSP 模式TDM 模式 文章目录 DSP formatDSP A时序图逻辑分析仪抓包 DSP B时序图逻辑分析仪抓包 DSP format DSP/PCMmode 分为 Mode-A 和 Mode-B 共 2 种模式。不同芯⽚有的称为 PCM mode 有的称为 DSP m…