【QT】UDP通信QUdpSocket(单播、广播、组播) 您所在的位置:网站首页 抓取UDP点播通信 【QT】UDP通信QUdpSocket(单播、广播、组播)

【QT】UDP通信QUdpSocket(单播、广播、组播)

2023-11-27 06:20| 来源: 网络整理| 查看: 265

目录 1. UDP通信概述2. UDP消息传送的三种模式3. QUdpSocket类的接口函数4. UDP单播和广播代码示例4.1 测试说明4.2 MainWindow.h4.3 MainWindow.cpp4.4 界面展示 5. UDP组播代码示例5.1 组播的特性5.2 MainWindow.h5.3 MainWindow.cpp5.4 界面展示

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()函数实现加入多播组的功能。

在单播、广播和多播模式下,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()不同,该函数立即关闭套接字,丢弃写入缓冲区中的任何挂起数据。

4. UDP单播和广播代码示例 4.1 测试说明

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

4.2 MainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: 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 4.3 MainWindow.cpp #include "mainwindow.h" #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->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_pActBindPort = new QAction(QIcon(":/new/prefix1/res/绑定端口.png"), QStringLiteral("绑定端口"), this); m_pActUnbindPort = 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_pActBindPort); ui->mainToolBar->addAction(m_pActUnbindPort); ui->mainToolBar->addSeparator(); ui->mainToolBar->addAction(m_pActClearText); ui->mainToolBar->addSeparator(); ui->mainToolBar->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(1600); 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(3200); 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); // connect connect(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("**已成功绑定"); m_pPlainText->appendPlainText("绑定端口:" + QString::number(m_pUdpSocket->localPort())); //使能 m_pActBindPort->setEnabled(false); m_pActUnbindPort->setEnabled(true); } else { m_pPlainText->appendPlainText("绑定失败"); } } void MainWindow::slotActUnbindPort() { m_pUdpSocket->abort(); //解除绑定 m_pPlainText->appendPlainText("**已解除绑定"); m_pActBindPort->setEnabled(true); m_pActUnbindPort->setEnabled(false); } void MainWindow::slotActClearText() { m_pPlainText->clear(); } void MainWindow::slotActQuit() { QMessageBox::StandardButton button = QMessageBox::question(this, "", "是否要退出?"); if (button == QMessageBox::StandardButton::Yes) this->close(); } void MainWindow::slotSocketStateChanged(QAbstractSocket::SocketState socketState) { switch (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::slotBtnSend() { QString msg = m_pLineEdit->text(); QByteArray str = msg.toUtf8(); QString targetIp = m_pComboTargetAddr->currentText(); //目标IP QHostAddress 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 addrList = hostInfo.addresses(); if (!addrList.isEmpty()) { for (int i = 0; i localIP = addr.toString(); break; } } } return localIP; } 4.4 界面展示

在这里插入图片描述

5. UDP组播代码示例 5.1 组播的特性

组播报文的目的地址使用D类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。 5.2 MainWindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: 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 5.3 MainWindow.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); // connect connect(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的情况,所以就需要写default switch (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() for (int i = 0; i localIP = addr.toString(); break; } } } return localIP; } 5.4 界面展示

在这里插入图片描述



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有