天天看點

從零開始學Qt(89):UDP單點傳播和廣播

作者:未來奇兵
從零開始學Qt(89):UDP單點傳播和廣播

執行個體程式實作UDP單點傳播和廣播,其主視窗是繼承自QMainWindow的類,界面用UI設計器設計。程式可以進行UDP資料報的發送和接收,兩個運作執行個體之間可以進行UDP通信,這兩個執行個體可以運作在同一台計算機上,也可以運作在不同的計算機上。下圖是兩個執行個體在一台計算機上運作時通信的界面。

從零開始學Qt(89):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。

從零開始學Qt(89):UDP單點傳播和廣播

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位元組數組,因為本程式隻是傳輸字元串,是以簡單地将其轉換為字元串即可。如果傳輸的是自定義格式的字元串或二進制資料,需要對接收到的資料進行解析。

————————————————

覺得有用的話請關注點贊,謝謝您的支援!

對于本系列文章相關示例完整代碼有需要的朋友,可關注并在評論區留言!

繼續閱讀