執行個體程式實作UDP單點傳播和廣播,其主視窗是繼承自QMainWindow的類,界面用UI設計器設計。程式可以進行UDP資料報的發送和接收,兩個運作執行個體之間可以進行UDP通信,這兩個執行個體可以運作在同一台計算機上,也可以運作在不同的計算機上。下圖是兩個執行個體在一台計算機上運作時通信的界面。
在同一台計算機上運作時,兩個運作執行個體需要綁定不同的端口,例如執行個體A綁定端口1200, 執行個體B綁定端口3355。執行個體A向執行個體B發送資料報時,需要指定執行個體B所在主機的IP位址、綁定端口作為目标位址和目标端口,這樣執行個體B才能接收到資料報。
如果兩個執行個體在不同計算機上運作,則可以使用相同的端口,因為IP位址不同了,不會導緻綁定時發生沖突。一般的UDP通信程式都是在不同的計算機上運作的,約定一個固定的端口作為通信端口。
1. 主視窗類定義和構造函數
主視窗是基于QMainWindow的類MainWindow,界面用UI設計器設計。MainWindow類的定義如下(省略了UI設計器為actions和按鈕生成的槽函數聲明):
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *LabSocketState;//狀态欄标簽
QUdpSocket *udpSocket;
QString getLocalIP();//擷取本機 IP 位址
private slots:
//自定義槽函數
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//讀取 socket 傳入的資料
};
QUdpSocket類型的私有變量udpSocket是用于UDP通信的socket。
定義了兩個自定義槽函數,onSocketStateChange()與udpSocket的stateChange()信号關聯,用于顯示udpSocket目前的狀态;onSocketReadyRead()信号與udpSocket的readyRead()信号關聯,用于讀取緩沖區的資料報。
MainWindow的構造函數主要完成udpSocket的建立、信号與槽函數的關聯,代碼如下:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabSocketState=new QLabel("Socket 狀态:");
LabSocketState->setMinimumWidth(200);
ui->statusbar->addWidget(LabSocketState);
QString localIP=getLocalIP() ; //本機 IP
this->setWindowTitle(this->windowTitle()+" 本機 IP: "+localIP);
ui->comboIP->addItem(localIP);
udpSocket=new QUdpSocket(this);
connect(udpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(udpSocket->state());
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadyRead()));
}
槽函數onSocketStateChange()的功能與前面文章中的TCP通信程式裡的完全一樣,不再顯示其具體代碼。
2. UDP端口綁定
要實作UDP資料的接收,必須先用QUdpSocket::bind()函數綁定一個端口,用于監聽傳入的資料報,解除綁定則使用abort()函數。程式主視窗上的“綁定端口”和“解除綁定”按鈕的響應代碼如下:
void MainWindow::on_actionStart_triggered()
{//綁定端口
quint16 port=ui->spinBindPort->value(); //本機 UDP 端口
if(udpSocket->bind(port)){ //綁定端口成功
ui->plainTextEdit->appendPlainText("**已成功綁定");
ui->plainTextEdit->appendPlainText("**綁定端口: "+QString::number(udpSocket->localPort()));
ui->actionStart->setEnabled(false);
ui->actionStop->setEnabled(true);
}else{
ui->plainTextEdit->appendPlainText("**綁定失敗");
}
}
void MainWindow::on_actionStop_triggered()
{//解除綁定
udpSocket->abort(); //解除綁定
ui->actionStart->setEnabled(true);
ui->actionStop->setEnabled(false);
ui->plainTextEdit->appendPlainText("**己解除綁定");
}
綁定端口後,socket的狀态變為己綁定狀态“BoundState”,解除綁定後狀态變為未連接配接狀态“UnconnectedState”。
3.UDP通信實作
發送點對點消息和廣播消息都使用QUdpSocket::writeDatagram()函數,視窗上“發送消息”和“廣播消息”兩個按鈕的代碼如下:
void MainWindow::on_btnSend_clicked()
{ //發送消息按鈕
QString targetIP=ui->comboIP->currentText(); //目标IP
QHostAddress targetAddr(targetIP);
quint16 targetPort=ui->spinTargetPort->value(); //目标 port
QString msg=ui->edtMsg->text();//發送的消息内容
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str, targetAddr, targetPort); //發出資料報
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->edtMsg->clear();
ui->edtMsg->setFocus();
}
void MainWindow::on_btnBroadcast_clicked()
{//廣播消息按鈕
quint16 targetPort=ui->spinTargetPort->value(); //目标端口
QString msg=ui->edtMsg->text();
QByteArray str=msg.toUtf8();
udpSocket->writeDatagram(str,QHostAddress::Broadcast,targetPort);
ui->plainTextEdit->appendPlainText("[broadcast] "+msg);
ui->edtMsg->clear();
ui->edtMsg->setFocus();
}
使用writeDatagram()函數向一個目标使用者發送消息時,需要指定目标位址和端口。
在廣播消息時,隻需将目标位址更換為一個特殊位址,即廣播位址QHostAddress::Broadcast, 一般是255.255.255.255。
QUdpSocket發送的資料報是QByteArray類型的位元組數組,資料報的長度一般不超過512位元組。資料報的内容可以是文本字元串,也可以自定義格式的二進制資料,文本字元串無需以換行符結束。
QUdpSocket接收到資料報後發射readyRead()信号,在關聯的槽函數onSocketReadyRead()裡讀取緩沖區的資料報,代碼如下:
void MainWindow::onSocketReadyRead()
{//讀取收到的資料報
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()表示是否有待讀取的傳入資料報。pendingDatagramSize()傳回待讀取資料報的位元組數。readDatagram()函數用于讀取資料報的内容,其函數原型為:
qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *address = nullptr, quint16 *port = nullptr)
輸入參數data和maxSize是必須的,表示最多讀取maxSize位元組的資料到變量data裡。address和port變量是可選的,用于擷取資料報來源的位址和端口。上面的代碼中使用了完整的參數形式,進而可以獲得資料報來源的位址peerAddr和端口peerPort。如果無需擷取來源位址和端口,可以采用簡略形式,即:
udpSocket->readDatagram(datagram.data(),datagram.size());
讀取的資料報内容是QByteArray位元組數組,因為本程式隻是傳輸字元串,是以簡單地将其轉換為字元串即可。如果傳輸的是自定義格式的字元串或二進制資料,需要對接收到的資料進行解析。
————————————————
覺得有用的話請關注點贊,謝謝您的支援!
對于本系列文章相關示例完整代碼有需要的朋友,可關注并在評論區留言!