1.UDP通信概述
UDP(User Datagram Protocol,用戶數(shù)據(jù)報協(xié)議)是輕量的、不可靠的、面向數(shù)據(jù)報 (datagram)
、無連接的協(xié)議,它可以用于對可靠性要求不高的場合。與 TCP 通信不同,兩個程序之間進行 UDP 通信無需預先建立持久的 socket 連接,UDP 每次發(fā)送數(shù)據(jù)報都需要指定目標地址和端口
(如圖14-6 所示)。
QUdpSocket 類用于實現(xiàn) UDP 通信,它從 QAbstractSocket 類繼承,因而與 QTcpSocket 共享大部分的接口函數(shù)。主要區(qū)別是 QUdpSocket 以數(shù)據(jù)報傳輸數(shù)據(jù),而不是以連續(xù)的數(shù)據(jù)流。發(fā)送數(shù)據(jù)報使用函數(shù)QUdpSocket::writeDatagram(),數(shù)據(jù)報的長度一般少于512 字節(jié),每個數(shù)據(jù)報包含發(fā)送者和接收者的 IP 地址和端口等信息。
要進行 UDP 數(shù)據(jù)接收,要用 QUdpSocket::bind()函數(shù)先綁定一個端口,用于接收傳入的數(shù)據(jù)報。當有數(shù)據(jù)報傳入時會發(fā)射readyRead()信號,使用readDatagram()函數(shù)來讀取接收到的數(shù)據(jù)報。
UDP 消息傳送有單播、廣播、組播三種模式,其示意圖如圖 14-7 所示
-
單播 (unicast)模式:一個 UDP 客戶端發(fā)出的數(shù)據(jù)報只發(fā)送到另一個指定地址和端口的UDP 客戶端,是一對一的數(shù)據(jù)傳輸。
-
廣播(broadcast)模式:一個 UDP 客戶端發(fā)出的數(shù)據(jù)報,在同一網(wǎng)絡范圍內(nèi)其他所有的UDP 客戶端都可以收到。QUdpSocket 支持IPv4 廣播。廣播經(jīng)常用于實現(xiàn)網(wǎng)絡發(fā)現(xiàn)的協(xié)議。要獲取廣播數(shù)據(jù)只需在數(shù)據(jù)報中指定接收端地址為 QHostAddress::Broadcast,一般的廣播地址是 255.255.255.255
-
組播 (multicast)模式:也稱為多播。UDP 客戶端加入到另一個組播IP 地址指定的多播組,成員向組播地址發(fā)送的數(shù)據(jù)報組內(nèi)成員都可以接收到,類似于 QQ 群的功能。QUdpSocket::joinMulticastGroup()函數(shù)實現(xiàn)加入多播組的功能,加入多播組后,UDP 數(shù)據(jù)的收發(fā)和UDP數(shù)據(jù)收發(fā)方法一樣。
使用廣播和多播模式,UDP 可以實現(xiàn)一些比較靈活的通信功能,而 TCP 通信只有單播模式?jīng)]有廣播和多播模式。所以,UDP 通信雖然不能保證數(shù)據(jù)傳輸?shù)臏蚀_性,但是具有靈活性,一般的即時通信軟件都是基于UDP 通信的。
QUdpSocket 類從QAbstractSocket 繼承而來,但是又定義了較多新的功能函數(shù)用于實現(xiàn) UDP特有的一些功能,如數(shù)據(jù)報讀寫和多播通信功能。QUdpSocket 沒有定義新的信號。QUdpSocket的主要功能函數(shù)見表 14-6 (包括從 QAbstractSocket 繼承的函數(shù),省略了函數(shù)中的 const 關鍵字,省略了缺省參數(shù))
在單播、廣播和多播模式下,UDP 程序都是對等的,不像 TCP 通信那樣分為客戶端和服務器端。多播和廣播的實現(xiàn)方式基本相同,只是數(shù)據(jù)報的目標IP 地址設置不同,多播模式需要加入多播組,實現(xiàn)方式有較大差異。
為分別演示這三種 UDP 通信模式,本節(jié)設計了兩個實例。Samp14_3 實例演示 UDP 單播和廣播通信,Samp14_4實例演示UDP組播通信。
2. UDP 單播和廣播
2.1 UDP 通信實例程序功能
實例程序 samp14_3 實現(xiàn) UDP 單播和廣播,其主窗口是繼承自 QMainWindow 的類,界面用UI 設計器設計。程序可以進行 UDP 數(shù)據(jù)報的發(fā)送和接收,samp14_3 的兩個運行實例之間可以進行 UDP 通信,這兩個實例可以運行在同一臺計算機上,也可以運行在不同的計算機上。圖 14-8和圖14-9是samp14_3兩個實例在一臺計算機上運行時通信的界面。
在同一臺計算機上運行時,兩個運行實例需要綁定不同的端口,例如實例A 定端口 1200,實例B綁定端口 3355。實例A 向?qū)嵗?B 發(fā)送數(shù)據(jù)報時,需要指定實例 B 所在主機的IP 地址、綁定端口作為目標地址和目標端口
(為了準確識別相應的客戶端,與上篇中元組
的概念很相似),這樣實例 B 才能接收到數(shù)據(jù)報。
如果兩個實例在不同計算機上運行,則可以使用相同的端口,因為 IP 地址不同了,不會導致綁定時發(fā)生沖突。一般的UDP 通信程序都是在不同的計算機上運行的,約定一個固定的端口作為通信端口。
2.2 主窗口類定義和構造函數(shù)
主窗口是基于QMainWindow 的類 MainWindow,界面采用 UI 設計器設計。MainWindow類的定義如下(省略了 UI 設計器為 actions 和按鈕生成的槽函數(shù)聲明):
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QLabel *LabSocketState;//socket狀態(tài)顯示標簽
QUdpSocket *udpSocket;//
QString getLocalIP();//獲取本機IP地址
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
//自定義槽函數(shù)
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//讀取socket傳入的數(shù)據(jù)
...
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
QUdpSocket 類型的私有變量 udpSocket 是用于UDP 通信的 socket。
定義了兩個自定義槽函數(shù),onSocketStateChange()與 udpSocket 的 stateChange()信號關聯(lián),用于顯示 udpSocket 當前的狀態(tài);onSocketReadyRead()信號與 udpSocket 的readyRead()信號關聯(lián),用于讀取緩沖區(qū)的數(shù)據(jù)報。
MainWindow 的構造函數(shù)主要完成udpSocket 的創(chuàng)建、信號與槽函數(shù)的關聯(lián),代碼如下:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabSocketState=new QLabel(QString::fromLocal8Bit("Socket狀態(tài):"));//
LabSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(LabSocketState);
QString localIP=getLocalIP();//本機IP
this->setWindowTitle(this->windowTitle()+QString::fromLocal8Bit("----本機IP:")+localIP);
ui->comboTargetIP->addItem(localIP);
udpSocket=new QUdpSocket(this);//用于與連接的客戶端通訊的QTcpSocket
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(udpSocket->state());
connect(udpSocket,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
}
onSocketStateChange(udpSocket->state());
代碼與上篇TCP通信程序里的完全一樣。
2.3 UDP通信的實現(xiàn)
要實現(xiàn)UDP 數(shù)據(jù)的接收,必須先用QUdpSocket::bind()函數(shù)綁定一個端口,用于監(jiān)聽傳入的數(shù)據(jù)報,解除綁定則使用 abort()函數(shù)。程序主窗口上的“綁定端口”和“解除綁定”按鈕的響應代碼如下:
void MainWindow::on_actStart_triggered()
{//綁定端口
quint16 port=ui->spinBindPort->value(); //本機UDP端口
if (udpSocket->bind(port))//綁定端口成功
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已成功綁定"));
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**綁定端口:")
+QString::number(udpSocket->localPort()));
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
}
else
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**綁定失敗"));
}
void MainWindow::on_actStop_triggered()
{//解除綁定
udpSocket->abort(); //不能解除綁定
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已解除綁定"));
}
綁定端口后,socket 的狀態(tài)變?yōu)橐呀壎顟B(tài)“BoundState”,解除綁定后狀態(tài)變?yōu)槲催B接狀態(tài)UnconnectedState”
發(fā)送點對點消息和廣播消息都使用 QUdpSocket:: writeDatagram()函數(shù),窗口上“發(fā)送消息和“廣播消息”兩個按鈕的代碼如下:
void MainWindow::on_btnSend_clicked()
{//發(fā)送消息 按鈕
QString targetIP=ui->comboTargetIP->currentText(); //目標IP
QHostAddress targetAddr(targetIP);
quint16 targetPort=ui->spinTargetPort->value();//目標port
QString msg=ui->editMsg->text();//發(fā)送的消息內(nèi)容
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,targetAddr,targetPort); //發(fā)出數(shù)據(jù)報
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("[out] ")+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
void MainWindow::on_btnBroadcast_clicked()
{ //廣播消息 按鈕
quint16 targetPort=ui->spinTargetPort->value(); //目標端口
QString msg=ui->editMsg->text();
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("[broadcast] ")+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
使用writeDatagram()函數(shù)向一個目標用戶發(fā)送消息時,需要指定目標地址和端口。
在廣播消息時,只需將目標地址更換為一個特殊地址,即廣播地址 OHostAddress::Broadcast,一般是255.255.255.255。通信演示如下圖所示:
QUdpSocket 發(fā)送的數(shù)據(jù)報是 QByteArray 類型的字節(jié)數(shù)組,數(shù)據(jù)報的長度一般不超過 512 字節(jié)。數(shù)據(jù)報的內(nèi)容可以是文本字符串,也可以自定義格式的二進制數(shù)據(jù),文本字符串無需以換行符結束。
QUdpSocket 接收到數(shù)據(jù)報后發(fā)射 readyRead()信號,在關聯(lián)的槽函數(shù) onSocketReadyRead()里讀取緩沖區(qū)的數(shù)據(jù)報,代碼如下:
void MainWindow::onSocketReadyRead()
{//讀取收到的數(shù)據(jù)報
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[From "+peerAddr.toString()+":"+QString::number(peerPort)+"] ";
ui->plainTextEdit->appendPlainText(peer+str);
}
}
hasPendingDatagrams()表示是否有待讀取的傳入數(shù)據(jù)報。
pendingDatagramSize()返回待讀取數(shù)據(jù)報的字節(jié)數(shù)。
readDatagram()函數(shù)用于讀取數(shù)據(jù)報的內(nèi)容,其函數(shù)原型為:
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
輸入?yún)?shù)data和 maxSize 是必須的,表示最多讀取 maxSize 字節(jié)的數(shù)據(jù)到變量 data 里。address和 port 變量是可選的,用于獲取數(shù)據(jù)報來源的地址和端口。上面的代碼中使用了完整的參數(shù)形式,從而可以獲得數(shù)據(jù)報來源的地址 peerAddr 和端口 peerPort。如果無需獲取來源地址和端口,可以采用簡略形式,即:
udpSocket->readDatagram(datagram,data(),datagram,size());
讀取的數(shù)據(jù)報內(nèi)容是 QByteArray 字節(jié)數(shù)組,因為本程序只是傳輸字符串,所以簡單地將其轉(zhuǎn)換為字符串即可。如果傳輸?shù)氖亲远x格式的字符串或二進制數(shù)據(jù),需要對接收到的數(shù)據(jù)進行解析。文章來源:http://www.zghlxwxcb.cn/news/detail-627707.html
2.4 源碼
2.4.1 可視化UI設計
文章來源地址http://www.zghlxwxcb.cn/news/detail-627707.html
2.4.2 mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QUdpSocket>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QLabel *LabSocketState;//socket狀態(tài)顯示標簽
QUdpSocket *udpSocket;//
QString getLocalIP();//獲取本機IP地址
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
//自定義槽函數(shù)
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//讀取socket傳入的數(shù)據(jù)
//
void on_actStart_triggered();
void on_actStop_triggered();
void on_actClear_triggered();
void on_btnSend_clicked();
void on_actHostInfo_triggered();
void on_btnBroadcast_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
2.4.3 mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QtNetwork>
QString MainWindow::getLocalIP()
{
QString hostName=QHostInfo::localHostName();//本地主機名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QString localIP="";
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
localIP=aHost.toString();
break;
}
}
return localIP;
}
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabSocketState=new QLabel(QString::fromLocal8Bit("Socket狀態(tài):"));//
LabSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(LabSocketState);
QString localIP=getLocalIP();//本機IP
this->setWindowTitle(this->windowTitle()+QString::fromLocal8Bit("----本機IP:")+localIP);
ui->comboTargetIP->addItem(localIP);
udpSocket=new QUdpSocket(this);//用于與連接的客戶端通訊的QTcpSocket
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(udpSocket->state());
connect(udpSocket,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
}
MainWindow::~MainWindow()
{
udpSocket->abort();
delete udpSocket;
delete ui;
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):UnconnectedState"));
break;
case QAbstractSocket::HostLookupState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):HostLookupState"));
break;
case QAbstractSocket::ConnectingState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):ConnectingState"));
break;
case QAbstractSocket::ConnectedState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):ConnectedState"));
break;
case QAbstractSocket::BoundState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):BoundState"));
break;
case QAbstractSocket::ClosingState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):ClosingState"));
break;
case QAbstractSocket::ListeningState:
LabSocketState->setText(QString::fromLocal8Bit("scoket狀態(tài):ListeningState"));
}
}
void MainWindow::onSocketReadyRead()
{//讀取收到的數(shù)據(jù)報
while(udpSocket->hasPendingDatagrams())
{
QByteArray datagram;
datagram.resize(udpSocket->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
udpSocket->readDatagram(datagram.data(),datagram.size(),&peerAddr,&peerPort);
QString str=datagram.data();
QString peer="[From "+peerAddr.toString()+":"+QString::number(peerPort)+"] ";
ui->plainTextEdit->appendPlainText(peer+str);
}
}
void MainWindow::on_actStart_triggered()
{//綁定端口
quint16 port=ui->spinBindPort->value(); //本機UDP端口
if (udpSocket->bind(port))//綁定端口成功
{
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已成功綁定"));
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**綁定端口:")
+QString::number(udpSocket->localPort()));
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
}
else
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**綁定失敗"));
}
void MainWindow::on_actStop_triggered()
{//解除綁定
udpSocket->abort(); //不能解除綁定
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("**已解除綁定"));
}
void MainWindow::on_actClear_triggered()
{
ui->plainTextEdit->clear();
}
void MainWindow::on_btnSend_clicked()
{//發(fā)送消息 按鈕
QString targetIP=ui->comboTargetIP->currentText(); //目標IP
QHostAddress targetAddr(targetIP);
quint16 targetPort=ui->spinTargetPort->value();//目標port
QString msg=ui->editMsg->text();//發(fā)送的消息內(nèi)容
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,targetAddr,targetPort); //發(fā)出數(shù)據(jù)報
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("[out] ")+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
void MainWindow::on_actHostInfo_triggered()
{//本機地址 按鈕
QString hostName=QHostInfo::localHostName();//本地主機名
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("本機主機名:")+hostName+"\n");
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
QString IP=aHost.toString();
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("本機IP地址:")+aHost.toString());
if (ui->comboTargetIP->findText(IP)<0)
ui->comboTargetIP->addItem(IP);
}
}
}
void MainWindow::on_btnBroadcast_clicked()
{ //廣播消息 按鈕
quint16 targetPort=ui->spinTargetPort->value(); //目標端口
QString msg=ui->editMsg->text();
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);
ui->plainTextEdit->appendPlainText(QString::fromLocal8Bit("[broadcast] ")+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
}
到了這里,關于14-3_Qt 5.9 C++開發(fā)指南_QUdpSocket實現(xiàn) UDP 通信_UDP 單播和廣播的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!