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();


}

如果感兴趣想要源文件的,请移至传送门源代码文件下载

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