QT -- UdpSocket通信實例,使用Qt的UDP通信協議,實現局域網組播通信軟件功能

1.簡介效果

在之前的文章中,介紹了TCP協議在Qt中的應用實現方式傳送門,TCP通信方式是面向對象,可靠的連接服務。而與之對應的是不可靠,無連接的UDP通信協議,兩者是TCP/IP協議簇中較爲常用的兩者通信方式。

之前的博文中,實現了利用Qt調用TCP協議的局域網通信聊天,而這篇博文將介紹UDP協議在Qt中的簡單運用,其想要達到的效果如下。

在這裏插入圖片描述

使用UDP就和TCP不同的是,UDP協議只管發,實用於及時通信領域,它只管發送,不管對方是否收到,但因爲其傳輸效率高效,消耗資源小,所以嚐嚐結合於TCP協議一同使用在通信連接中。我們最常用的就是QQ,QQ採用TCP連接來保持在線狀態,使其連接至上層的騰訊服務器,而聊天發送消息功能大都採用UDP方式,當UDP協議不能正常轉發的時候,就會採用TCP協議進行發送,所以,TCP和UDP協議常常一同使用。

2.項目設計

1)流程圖

UDP協議流程圖,如下圖所示,經此流程圖後,我們可以清晰的知道,UDP是如何通信工作,並進行調用。

圖一

UDP協議沒有明確的客戶端和服務端可言,只要綁定了對應的IP地址和端口號,就可以進行通信,如很多的組播軟件,可以使綁定同一個IP。使得多個端口號的客戶在此IP下進行通信和數據傳輸。

2)項目構建

新建爲widget項目,構建如下所示項目樹

在這裏插入圖片描述

3)界面構建

兩個界面設計構建如下

  • 界面一 - secondwdiget

在這裏插入圖片描述

  • 界面二 - widget

在這裏插入圖片描述
使用行編輯框和文本編輯框構建簡單的界面如上所示。

4)代碼設計

和Qt使用TCP一樣,想要在Qt中使用網絡編程的包。需要在.pro工程文件裏面添加如下代碼

QT       += network

a.widget.h

首先還是需要添加Qt中封裝好的UDP的頭文件

#include <QUdpSocket>

之後對其進行初始化聲明,並定義一個處理消息隊列的槽函數,用以在UDP通信套接字連接後發送消息,之後便是重寫事件過濾器用以響應鍵盤Enter鍵的輸入,具體代碼如下

public:
	void dealMsg();         //槽函數,處理對方發過來的數據
protected:
    bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private:
    QUdpSocket *udpSocket;

b.widget.cpp

在執行文件中,對構建函數中的udpSocket分配動態空間,當連接好之後,自動並觸發連接的槽函數。

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->resize(450,300);
    this->setWindowTitle("服務器端口:8888");
    udpSocket = new QUdpSocket(this);

    //綁定端口號
    udpSocket->bind(8888);
    //udpSocket->bind(QHostAddress::AnyIPv4,8888);        //必須是主機的ipv4類通信方式,以及本機的ip地址
    //當對方發送數據過來。自動觸發readyRead()
  connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);
	//鍵盤Enter鍵設置
    ui->ButtonSend->setFocus();
    ui->ButtonSend->setDefault(true);
    ui->textEditShow->installEventFilter(this);//設置完後自動調用其eventFilter函數

}

之後便是對自定義處理消息隊列的槽函數進行函數實現

void Widget::dealMsg()
{
    //讀取對方發送的內容
    char buf[1024] = {0};           //內容
    QHostAddress cliAddr;           //對方地址
    quint16 port;                   //對方端口
    qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    //添加時間
    QTime cur_time = QTime::currentTime();
    QString time_info = cur_time.toString("hh:mm:ss");
    if(len >0)
    {
        //獲取到文本格式化 如 [192.168.1.1 : 8888]aaaa 的格式
        QString str = QString("[%1:%2] %3 [%4]")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf)
                .arg(time_info);
        //ui->textEditShow->setText(str);
        ui->textEditShow->append(str);
    }

}

然後便是對事件過濾器的函數實現

bool Widget::eventFilter(QObject *target, QEvent *event)
{
    if(target == ui->textEditShow) //對象爲需要發送的焦點
        {
            if(event->type() == QEvent::KeyPress)//回車鍵
            {
                 QKeyEvent *k = static_cast<QKeyEvent *>(event);

                 if(k->key() == Qt::Key_Return)
                 {
                     on_ButtonSend_clicked();
                     return true;
                 }
            }
        }
        return QWidget::eventFilter(target,event);
}

最後是點擊發送按鈕,獲取編輯框中的內容,使用通信套接字,將其發送給綁定特定端口號的一方

void Widget::on_ButtonSend_clicked()
{
    //獲取對方的ip和端口
    QString ip = ui->lineEditIp->text();
    qint16 port = ui->lineEditPort->text().toInt();

    //獲取編輯區內容
    QString str = ui->textEditShow->toPlainText();
    //給指定的ip發送數據
    udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
    ui->textEditShow->clear();

}

c.secondwidget.h

添加socket頭文件方法和聲明,以及消息隊列處理函數,和鍵盤響應函數同上

public:
	void dealMsg2();         //槽函數,處理對方發過來的數據
protected:
    bool eventFilter(QObject *target, QEvent *event);//事件過濾器
private:
    QUdpSocket *udpSocket;

d.secondwidget.cpp

構造函數和第一個界面有一點點不同,如下

SecondWidget::SecondWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::SecondWidget)
{
    ui->setupUi(this);

    this->resize(450,300);
    this->setWindowTitle("服務器端口2:9999");
    udpSocket2 = new QUdpSocket(this);
    //綁定
    udpSocket2->bind(9999);
    connect(udpSocket2,&QUdpSocket::readyRead,this,&SecondWidget::dealMsg2);
    ui->ButtonSend2->setFocus();
    ui->ButtonSend2->setDefault(true);
    //ui->ButtonSend->setShortcut(Qt::Key_Enter|Qt::Key_Return);
    ui->textEditShow2->installEventFilter(this);//設置完後自動調用其eventFilter函數
}

其他的函數實現方式和第一個界面相同,這類不再贅述。待正確編譯之後,即可實現這樣的效果

在這裏插入圖片描述

  • 注意:Qt中UDP通信只需要在同一個ip下(需要綁定自己電腦上的ip,使用ipconfig命令查看該電腦的ipv4的地址),綁定對應的端口號。即可進行通信連接

3.源代碼

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

    void dealMsg();         //槽函數,處理對方發過來的數據
protected:
    bool eventFilter(QObject *target, QEvent *event);//事件過濾器

private slots:
    void on_ButtonSend_clicked();

    void on_ButtonClose_clicked();

private:
    Ui::Widget *ui;

private:
    QUdpSocket *udpSocket;
};

#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>
#include <QDebug>
#include <QTime>
#include <QKeyEvent>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);
    this->resize(450,300);
    this->setWindowTitle("服務器端口:8888");
    udpSocket = new QUdpSocket(this);

    //綁定端口號
    udpSocket->bind(8888);
    //udpSocket->bind(QHostAddress::AnyIPv4,8888);        //必須是主機的ipv4類通信方式,以及本機的ip地址
        
    //當對方發送數據過來。自動觸發readyRead()
    connect(udpSocket,&QUdpSocket::readyRead,this,&Widget::dealMsg);

    ui->ButtonSend->setFocus();
    ui->ButtonSend->setDefault(true);
    ui->textEditShow->installEventFilter(this);//設置完後自動調用其eventFilter函數

}

Widget::~Widget()
{
    delete ui;
}

void Widget::dealMsg()
{
    //讀取對方發送的內容
    char buf[1024] = {0};           //內容
    QHostAddress cliAddr;           //對方地址
    quint16 port;                   //對方端口
    qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    QTime cur_time = QTime::currentTime();
    QString time_info = cur_time.toString("hh:mm:ss");
    if(len >0)
    {
        //獲取到文本格式化  [192.168.1.1 : 8888]aaaa
        QString str = QString("[%1:%2] %3 [%4]")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf)
                .arg(time_info);
        //ui->textEditShow->setText(str);
        ui->textEditShow->append(str);
    }

}

bool Widget::eventFilter(QObject *target, QEvent *event)
{
    if(target == ui->textEditShow)
        {
            if(event->type() == QEvent::KeyPress)//回車鍵
            {
                 QKeyEvent *k = static_cast<QKeyEvent *>(event);

                 if(k->key() == Qt::Key_Return)
                 {
                     on_ButtonSend_clicked();
                     return true;
                 }
            }
        }
        return QWidget::eventFilter(target,event);
}

void Widget::on_ButtonSend_clicked()
{
    //獲取對方的ip和端口
    QString ip = ui->lineEditIp->text();
    qint16 port = ui->lineEditPort->text().toInt();

    //獲取編輯區內容
    QString str = ui->textEditShow->toPlainText();
    //給指定的ip發送數據
    udpSocket->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
    ui->textEditShow->clear();

}

void Widget::on_ButtonClose_clicked()
{
    udpSocket->disconnectFromHost();
    udpSocket->close();
    qDebug() << "主服務器連接斷開!";
    this->close();

}

secondwidget.h

#ifndef SECONDWIDGET_H
#define SECONDWIDGET_H

#include <QWidget>
#include <QUdpSocket>

namespace Ui {
class SecondWidget;
}

class SecondWidget : public QWidget
{
    Q_OBJECT

public:
    explicit SecondWidget(QWidget *parent = nullptr);
    ~SecondWidget();
    void dealMsg2();            //消息隊列處理
protected:
    bool eventFilter(QObject *target, QEvent *event);//事件過濾器

private slots:
    void on_ButtonSend2_clicked();

    void on_ButtonClose2_clicked();

private:
    Ui::SecondWidget *ui;
private:
    QUdpSocket *udpSocket2;

};

#endif // SECONDWIDGET_H

secondwidget.cpp

#include "secondwidget.h"
#include "ui_secondwidget.h"
#include <QHostAddress>
#include <QDebug>
#include <QTime>
#include <QKeyEvent>

SecondWidget::SecondWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::SecondWidget)
{
    ui->setupUi(this);

    this->resize(450,300);
    this->setWindowTitle("服務器端口2:9999");
    udpSocket2 = new QUdpSocket(this);
    //綁定
    udpSocket2->bind(9999);
    connect(udpSocket2,&QUdpSocket::readyRead,this,&SecondWidget::dealMsg2);
    ui->ButtonSend2->setFocus();
    ui->ButtonSend2->setDefault(true);
    //ui->ButtonSend->setShortcut(Qt::Key_Enter|Qt::Key_Return);
    ui->textEditShow2->installEventFilter(this);//設置完後自動調用其eventFilter函數
}

SecondWidget::~SecondWidget()
{
    delete ui;
}

void SecondWidget::dealMsg2()
{
    //讀取對方發送的內容
    char buf[1024] = {0};           //內容
    QHostAddress cliAddr;           //對方地址
    quint16 port;                   //對方端口
    qint64 len = udpSocket2->readDatagram(buf,sizeof(buf),&cliAddr,&port);
    QTime cur_time = QTime::currentTime();
    QString time_info = cur_time.toString("hh:mm:ss");
    if(len >0)
    {
        //獲取到文本格式化  [192.68.1.144 : 8888]aaaa
        QString str = QString("[%1:%2] %3 [%4]")
                .arg(cliAddr.toString())
                .arg(port)
                .arg(buf)
                .arg(time_info);
        //ui->textEditShow2->setText(str);
        ui->textEditShow2->append(str);
    }
}

bool SecondWidget::eventFilter(QObject *target, QEvent *event)
{
    if(target == ui->textEditShow2)
        {
            if(event->type() == QEvent::KeyPress)//回車鍵
            {
                 QKeyEvent *k = static_cast<QKeyEvent *>(event);

                 if(k->key() == Qt::Key_Return)
                 {
                     on_ButtonSend2_clicked();
                     return true;
                 }
            }
        }
        return QWidget::eventFilter(target,event);
}

void SecondWidget::on_ButtonSend2_clicked()
{
    //獲取對方的ip和端口
    QString ip = ui->lineEditIp2->text();
    qint16 port = ui->lineEditPort2->text().toInt();

    //獲取編輯區內容
    QString str = ui->textEditShow2->toPlainText();
    //給指定的ip發送數據
    udpSocket2->writeDatagram(str.toUtf8(),QHostAddress(ip),port);
    ui->textEditShow2->clear();
}

void SecondWidget::on_ButtonClose2_clicked()
{
    udpSocket2->disconnectFromHost();
    udpSocket2->close();
    qDebug() << "斷開連接!";
    this->close();


}

如果感興趣想要源文件的,請移至傳送門源代碼文件下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章