簡 述: 瞭解Upd
通信之QUdpSocket
的unicast
單播、broadcast
廣播、multicast
組播,書寫一個簡單地例子;然後寫了一個小的Qt
例子,用來實現和驗證它的空間的一些屬性和功能的用法。
文章目錄
本篇的csdn/github.io同步博文: Upd通信之QUdpSocket的unicast單播、broadcast廣播、multicast組播
系統環境:
編程環境: MacOS 10.14.6 (18G103)
編程軟件: Qt 5.9.8
, Qt Creator 4.8.2
QUdpSocket講解:
UDP
通信是輕量的,不可靠(表示有概率會丟包),面向數據報,無連接的協議。用途可以比如:遠程視頻等
對於UDP通信而言,其實是沒有區分客戶端或者服務端的,應爲任何一個UdpSocket既可以看做客戶端,也可以看做服務端;這點與TCP的不一樣的。
另外就是QUdpSocket
是以數據報的形式傳輸數據,而非連續的數據流。發送的數據報一般也都是QByteArray
類型的字節數組;數據報的長度一般是低於512字節的,且每一個數據報都是要包含有發送者和接受者的IP
和port
等信息。
其中udp的消息傳播方式有如下三種:
-
unicast單播:
一個udp客戶端只能夠發送數據報到另外一個指定的地址和端口的udp客戶端,是一對一的數據傳輸
-
broadcast廣播:
一個udp客戶端發送的數據報,在同一網絡範圍內其他所有的udp客戶端都可以收到。其支持
IPV4
廣播📢,只需要將接收對象設置爲QHostAddress::Broadcast
,且ip
地址爲255.255.255.255
(代表整個地址段的所有ip) -
multicast組播:
也被稱爲多播。就是相當於羣聊功能。udp客戶端加入到 另一個組播IP地址指定的多播組,成員向組播地址發送的數據報組內成員都可以接收到,使用
QUdpSocket::joinMuliticastGroup()
函數實現加入多播功能;加入多播後,UDP的發送和正常的UDP數據收發一樣。
而關於組播IP地址,是有着一些約定的:
綜上:若是在家庭或者辦公室或局域網中進行udp的測試,可以使用的組播地址是範圍是:239.0.0.0~238.255.255.255
QUdpSocket的主要常用接口:
函數 | 功能 |
---|---|
bool bind(quint16 port = 0) | 爲udp通信綁定一個端口 |
qint64 writeDatagram(QByteArray kdatagram, QHostAddress &host, quint16 port) | 向目標地址和端口的udp客戶端發送數據報,返回成功發送的字節數 |
bool hasPendingDatagrams() | 至少有一個數據報需要讀取的時,返回true |
qint64 pendingDatagramSize() | 返回第一個待讀取數據報的大小 |
qint64 readDatagram(char *data, qint64 maxSize) | 讀取一個數據報,返回成功讀取的數據報的字節數 |
bool joinMulticastGroup(QHostAddress &groupAddress) | 加入一個多播組 |
bool leaveMulticastGroup(QHostAddress &groupAddress) | 離開一個多播組 |
unicast單播/broadcast廣播:
其中單播和組播的圖解如下:
multicast組播:
其中組播的關係圖如下:
運行效果:
這裏先放一張運行效果圖:
unicast單播/broadcast廣播:
multicast組播:
源碼分析:
其中核心部分的源碼,重點和一些難點以及需要注意的一些地方,貼出來如下
unicast單播/broadcast廣播:
其中.h頭文件:
#ifndef EXTRANS_H
#define EXTRANS_H
#include <QMainWindow>
#include <QLabel>
#include <QUdpSocket>
#include <QString>
namespace Ui {
class ExTrans;
}
/*!
* \class ExTrans 一個UDP的Deam測試,同時測試單播和廣播
* \brief 因爲是在同一臺電腦測試,所以IP相同,需要綁定兩個不同端口的,這樣不會衝突;
* 若是兩臺電腦進行測試,那麼可以約定使用相同的端口號,使用不同的IP;來進行通訊
*/
class ExTrans : public QMainWindow
{
Q_OBJECT
public:
explicit ExTrans(QWidget *parent = nullptr);
~ExTrans();
private slots:
void on_actBind_triggered(); //綁定端口
void on_actDisbind_triggered(); //解除綁定
void on_actClean_triggered(); //清除文本信息
void on_actQuit_triggered(); //關閉程序
void on_btnUnicast_clicked(); //單播消息
void on_btnBroadcast_clicked(); //廣播消息
void onSocketStateChange(QAbstractSocket::SocketState socketState); //socket 狀態發生變化
void onSocketReadyRead(); //讀取 socket 傳入的數據
private:
QString getLocalIp(); //獲取本機IP
private:
Ui::ExTrans *ui;
QLabel* m_labSocketState;
QUdpSocket* m_udpSocket;
};
#endif // EXTRANS_H
其中.cpp源文件:
#include "ExTrans.h"
#include "ui_ExTrans.h"
#include <QHostInfo>
ExTrans::ExTrans(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ExTrans)
{
ui->setupUi(this);
setWindowTitle("Udp通信:unicast(單播) + broadcast(廣播)的使用");
QString hostName = QHostInfo::localHostName();
QString ip = getLocalIp();
ui->plainTextEdit->appendPlainText("主機名稱:" + hostName + "\n主機IP:" + ip + "\n");
m_labSocketState = new QLabel("Socket狀態:");
m_labSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(m_labSocketState);
ui->comboBoxIp->addItem(getLocalIp());
m_udpSocket = new QUdpSocket(this);
connect(m_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(m_udpSocket->state());
connect(m_udpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}
ExTrans::~ExTrans()
{
delete ui;
}
//綁定端口
void ExTrans::on_actBind_triggered()
{
quint16 port = ui->spinBoxBind->value(); //本機UDP端口
if (m_udpSocket->bind(port)) { //端口綁定成功
ui->plainTextEdit->appendPlainText("端口綁定成功:" + QString::number(m_udpSocket->localPort()));
ui->actBind->setEnabled(false);
ui->actDisbind->setEnabled(true);
} else {
ui->plainTextEdit->appendPlainText("端口綁定失敗");
}
}
//解除綁定
void ExTrans::on_actDisbind_triggered()
{
m_udpSocket->abort(); //斷開,中止套接字
ui->plainTextEdit->appendPlainText("端口解除綁定成功");
ui->actBind->setEnabled(true);
ui->actDisbind->setEnabled(false);
}
//清除文本信息
void ExTrans::on_actClean_triggered()
{
ui->plainTextEdit->clear();
}
//關閉程序
void ExTrans::on_actQuit_triggered()
{
close();
}
//單播消息
void ExTrans::on_btnUnicast_clicked()
{
QString targetIp = ui->comboBoxIp->currentText();
QHostAddress targetAddr(targetIp); //目標 Ip
quint16 targetPort = ui->spinBoxPort->value(); //目標 port
QString msg = ui->lineEdit->text(); //發送的消息
QByteArray str = msg.toUtf8();
m_udpSocket->writeDatagram(str, targetAddr, targetPort); //發送數據報
ui->plainTextEdit->appendPlainText(QString("[Send: ] %1").arg(msg));
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}
//廣播消息
void ExTrans::on_btnBroadcast_clicked()
{
quint16 targetPort = ui->spinBoxPort->value(); //目標 port
QString msg = ui->lineEdit->text(); //發送的消息
QByteArray str = msg.toUtf8();
m_udpSocket->writeDatagram(str, QHostAddress::Broadcast, targetPort); //發送 數據報 給所有IP
ui->plainTextEdit->appendPlainText(QString("[廣播: ] %1").arg(msg));
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}
//socket 狀態發生變化
void ExTrans::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState) {
case QAbstractSocket::UnconnectedState:
m_labSocketState->setText("socket狀態:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
m_labSocketState->setText("socket狀態:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
m_labSocketState->setText("socket狀態:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
m_labSocketState->setText("socket狀態:ConnectedState");
break;
case QAbstractSocket::BoundState:
m_labSocketState->setText("socket狀態:BoundState");
break;
case QAbstractSocket::ClosingState:
m_labSocketState->setText("socket狀態:ClosingState");
break;
case QAbstractSocket::ListeningState:
m_labSocketState->setText("socket狀態:ListeningState");
break;
default:
m_labSocketState->setText("socket狀態:其他未知狀態...");
break;
}
}
//讀取 socket 傳入的數據
void ExTrans::onSocketReadyRead()
{
while (m_udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(m_udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
m_udpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort); //讀取數據包,消息+來自的Ip和port
QString str = datagram.data();
QString peer = QString("[From: %1 %2] %3").arg(peerAddr.toString()).arg(QString::number(peerPort)).arg(str);
ui->plainTextEdit->appendPlainText(peer);
}
}
//獲取本機IP
QString ExTrans::getLocalIp()
{
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString Ip = "";
if (hostInfo.addresses().isEmpty())
return 0;
foreach (QHostAddress addr, hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
Ip = addr.toString();
break;
}
}
return Ip;
}
multicast組播:
其中.h頭文件:
#ifndef EXMULTICAST_H
#define EXMULTICAST_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QLabel>
#include <QHostInfo>
#include <QHostAddress>
namespace Ui {
class ExMulticast;
}
class ExMulticast : public QMainWindow
{
Q_OBJECT
public:
explicit ExMulticast(QWidget *parent = nullptr);
~ExMulticast();
private slots:
void on_actStart_triggered();
void on_actStop_triggered();
void on_actClear_triggered();
void on_actQuit_triggered();
void onSocketStateChange(QAbstractSocket::SocketState socketState); //socket 狀態發生變化
void onSocketReadyRead(); //讀取 socket 傳入的數據
void on_btnSend_clicked();
private:
QString getLocalIp(); //獲取本機IP
private:
Ui::ExMulticast *ui;
QUdpSocket* m_udpSocket; //用於通訊的 socket
QLabel* m_labSocketState;
QHostAddress m_groupAddress; //組播地址
};
#endif // EXMULTICAST_H
其中.cpp源文件:
#include "ExMulticast.h"
#include "ui_ExMulticast.h"
ExMulticast::ExMulticast(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::ExMulticast)
{
ui->setupUi(this);
setWindowTitle("Udp通信:multicate(組播)的使用");
QString hostName = QHostInfo::localHostName();
QString ip = getLocalIp();
ui->plainTextEdit->appendPlainText("主機名稱:" + hostName + "\n主機IP:" + ip + "\n");
m_labSocketState = new QLabel("Socket狀態:");
m_labSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(m_labSocketState);
ui->comboBoxIp->addItem(getLocalIp());
m_udpSocket = new QUdpSocket(this); //用於通訊使用的 Socket
//Multicast路由層次,1表示只在同一局域網內
//組播TTL: 生存時間,每跨1個路由會減1,多播無法跨過大多數路由所以爲1
//默認值是1,表示數據包只能在本地的子網中傳送。
m_udpSocket->setSocketOption(QAbstractSocket::MulticastTtlOption, 1);
connect(m_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(m_udpSocket->state());
connect(m_udpSocket, SIGNAL(readyRead()), this, SLOT(onSocketReadyRead()));
}
ExMulticast::~ExMulticast()
{
delete ui;
}
void ExMulticast::on_actStart_triggered()
{
QString ip = ui->comboBoxIp->currentText();
m_groupAddress = QHostAddress(ip);
quint16 port = ui->spinBoxBind->value();
if (m_udpSocket->bind(QHostAddress::AnyIPv4, port, QUdpSocket::ShareAddress)) { //綁定端口
m_udpSocket->joinMulticastGroup(m_groupAddress); //加入多播組
ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"] 加入組播(組播地址:" + ip + " 端口:" + QString::number(port) + ")成功");
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
} else {
ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"] 加入組播(組播地址:" + ip + " 端口:" + QString::number(port) + ")失敗");
}
}
void ExMulticast::on_actStop_triggered()
{
m_udpSocket->leaveMulticastGroup(m_groupAddress); //退出組播
m_udpSocket->abort(); //解除綁定
ui->plainTextEdit->appendPlainText("[用戶:" + getLocalIp() +"] 退出組播成功");
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
}
void ExMulticast::on_actClear_triggered()
{
ui->plainTextEdit->clear();
}
void ExMulticast::on_actQuit_triggered()
{
close();
}
void ExMulticast::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch (socketState) {
case QAbstractSocket::UnconnectedState:
m_labSocketState->setText("socket狀態:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
m_labSocketState->setText("socket狀態:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
m_labSocketState->setText("socket狀態:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
m_labSocketState->setText("socket狀態:ConnectedState");
break;
case QAbstractSocket::BoundState:
m_labSocketState->setText("socket狀態:BoundState");
break;
case QAbstractSocket::ClosingState:
m_labSocketState->setText("socket狀態:ClosingState");
break;
case QAbstractSocket::ListeningState:
m_labSocketState->setText("socket狀態:ListeningState");
break;
default:
m_labSocketState->setText("socket狀態:其他未知狀態...");
break;
}
}
void ExMulticast::onSocketReadyRead()
{
while (m_udpSocket->hasPendingDatagrams()) {
QByteArray datagram;
datagram.resize(m_udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
m_udpSocket->readDatagram(datagram.data(), datagram.size(), &peerAddr, &peerPort); //讀取數據包,消息+來自的Ip和port
QString str = datagram.data();
QString peer = QString("[From: %1 %2] %3").arg(peerAddr.toString()).arg(QString::number(peerPort)).arg(str);
ui->plainTextEdit->appendPlainText(peer);
}
}
QString ExMulticast::getLocalIp()
{
QString hostName = QHostInfo::localHostName();
QHostInfo hostInfo = QHostInfo::fromName(hostName);
QString Ip = "";
if (hostInfo.addresses().isEmpty())
return 0;
foreach (QHostAddress addr, hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::IPv4Protocol) {
Ip = addr.toString();
break;
}
}
return Ip;
}
void ExMulticast::on_btnSend_clicked()
{
quint16 port = ui->spinBoxBind->value();
QString msg = ui->lineEdit->text();
QByteArray datagram = msg.toUtf8();
m_udpSocket->writeDatagram(datagram, m_groupAddress, port);
// m_udpSocket->writeDatagram(datagram.data(), datagram.size(), m_groupAddress, port);
ui->plainTextEdit->appendPlainText("[multicst] " + msg);
ui->lineEdit->clear();
ui->lineEdit->setFocus();
}