云计算百科
云计算领域专业知识百科平台

【QT网络】构建简单Udp回显服务器

📢博客主页:https://blog.csdn.net/2301_779549673 📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson 📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📢本文由 JohnKi 原创,首发于 CSDN🙉 📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、核心API概览
    • 1.1 QUdpSocket
    • 1.2 QNetworkDatagram
    • 1.3 Qt网络注意点
  • 🏳️‍🌈二、UdpServer 服务端
    • 2.1 界面布局
    • 2.2 QUdpSocket 成员
    • 2.3 processRequest 处理信号槽
    • 2.4 process 具体处理过程
    • 2.5 整体代码
  • 🏳️‍🌈三、UdpClient 客户端
    • 3.1 界面布局
    • 3.2 全局变量
    • 3.3 QUdpClient 成员
    • 3.3 on_pushButton_clicked 发送信号槽
    • 3.4 processResponse 响应报文处理槽
    • 3.5 整体代码
  • 🏳️‍🌈四、测试
  • 👥总结

🏳️‍🌈一、核心API概览

主要的类有两个.QUdpSocket 和 QNetworkDatagram

1.1 QUdpSocket

QUdpSocket 表示一个 UDP 的 socket 文件

在这里插入图片描述

1.2 QNetworkDatagram

QNetworkDatagram 表示一个 UDP 的数据包 在这里插入图片描述

1.3 Qt网络注意点

在进行网络编程之前,需要在项目中的.pro 文件中添加 network 模块 在这里插入图片描述

🏳️‍🌈二、UdpServer 服务端

2.1 界面布局

创建界面,包含一个QListwidget 用来显示消息。不需要怎么调,只需要有一个能够回显出客户端数据的界面就行

在这里插入图片描述

2.2 QUdpSocket 成员

先需要在头文件中添加相应库和成员

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket> // 需要引入network

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
Q_OBJECT

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

private:
Ui::Widget *ui;

QUdpSocket* socket;//
};
#endif // WIDGET_H

然后我们需要在主函数中创建相应的对象

socket = new QUdpSocket(this);

这里不得不提醒一下,new的时候附带上this,这样就会将widget作为基类,等widget被销毁的时候,也会跟着销毁。不然就需要在widget的析构函数中带上 delete socket

Udp服务端的顺序

  • 将服务端套接字连接到相应的处理信号槽上
  • 绑定套接字到指定端口上,开始监听指定的ip
  • 如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了.此时还没来得及连接信号槽,那么这个请求就有可能错过了

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

    // 创建出这个对象
    // socket = new QUdpSocket(this); 表示 socket 的父对象是当前 Widget 对象。当 Widget 析构时,会触发 socket 的自动销毁,无需手动 delete。
    // 如果改为 socket = new QUdpSocket;(无父对象),则 socket 的生命周期不再由 Widget 管理。此时必须手动调用 delete socket;,否则 socket 对象会一直存在,导致内存泄漏。
    socket = new QUdpSocket(this);

    // 设置窗口标题
    this->setWindowTitle("服务器");

    // 连接信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if(!ret){
    // 绑定失败!
    QMessageBox::critical(this, "服务器绑定端口号失败", socket->errorString());
    return;
    }
    }

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

    这部分与linux网络是一样的,linux中要么选择在构造函数中将指定的方法回调函数传进去,要么选择使用udpserver对象的函数方法,设置方法回调函数。总而言之,都是先确定好方法回调函数,再去从外面不断收取连接

    在这里插入图片描述

    2.3 processRequest 处理信号槽

    完成处理请求的过程

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
  • // 这个函数完成的逻辑,就是服务器的最核心逻辑了
    void Widget::processRequest()
    {
    // 1. 读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    // 2. 根据请求计算相应(由于是回显服务器,相应不需要计算,就是请求本身)
    const QString& response = process(request);
    // 3. 把相应写回客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    // 把这次交互的信息,显示到界面上
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())
    + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
    }

    2.4 process 具体处理过程

    由于我们此处是实现回显服务器.所以 process 方法中并没有包含实质性的内容

    "根据请求处理响应"是服务器开发中的最核心的步骤一个商业服务器程序,这里的逻辑可能是几万行几十万行代码量级的

    QString Widget::process(const QString &request)
    {
    // 由于当前是会先服务器,相应就是和请求完全一样的
    return request;
    }

    2.5 整体代码

    记得不要忘记在头文件中添加相应的槽函数声明

    #include "widget.h"
    #include "ui_widget.h"
    #include <QMessageBox>
    #include <QNetworkDatagram>

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

    // 创建出这个对象
    // socket = new QUdpSocket(this); 表示 socket 的父对象是当前 Widget 对象。当 Widget 析构时,会触发 socket 的自动销毁,无需手动 delete。
    // 如果改为 socket = new QUdpSocket;(无父对象),则 socket 的生命周期不再由 Widget 管理。此时必须手动调用 delete socket;,否则 socket 对象会一直存在,导致内存泄漏。
    socket = new QUdpSocket(this);

    // 设置窗口标题
    this->setWindowTitle("服务器");

    // 连接信号槽
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);

    // 绑定端口号
    bool ret = socket->bind(QHostAddress::Any, 9090);
    if(!ret){
    // 绑定失败!
    QMessageBox::critical(this, "服务器绑定端口号失败", socket->errorString());
    return;
    }
    }

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

    // 这个函数完成的逻辑,就是服务器的最核心逻辑了
    void Widget::processRequest()
    {
    // 1. 读取请求并解析
    const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
    QString request = requestDatagram.data();
    // 2. 根据请求计算相应(由于是回显服务器,相应不需要计算,就是请求本身)
    const QString& response = process(request);
    // 3. 把响应写回客户端
    QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
    socket->writeDatagram(responseDatagram);
    // 4. 把这次交互的信息,显示到界面上
    QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())
    + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
    }

    QString Widget::process(const QString &request)
    {
    // 由于当前是会先服务器,相应就是和请求完全一样的
    return request;
    }

    🏳️‍🌈三、UdpClient 客户端

    3.1 界面布局

    创建界面:包含3个 QLineEdit,QPushButton,QListwidget

    • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的sizePolicy 为Expanding
    • 再使用垂直布局把 QListwidget 和上面的水平布局放好
    • 设置垂直布局的 layoutStretch为5,1(当然这个尺寸比例根据个人喜好微调)

    在这里插入图片描述 在这里插入图片描述

    3.2 全局变量

    在 widget.cpp 中,先创建两个全局常量,表示服务器的 IP 和 端口

    // 定义两个常量,描述服务器的地址和端口
    const QString& SERVER_IP = "127.0.0.1";
    const quint16 SERVER_PORT = 9090;

    3.3 QUdpClient 成员

    这个部分相对服务端简单一些。

    • 服务端 需要先连接处理信号槽再绑定端口号和指定ip
    • 客户端 只需要建立好与对 服务端响应内容 与 相应的处理函数 的连接就行了

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

    socket = new QUdpSocket(this);

    // 修改窗口标题
    this->setWindowTitle("客户端");

    // 连接服务端相应内容 和 对应处理函数
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
    }

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

    3.3 on_pushButton_clicked 发送信号槽

    在这里,我们的需要实现的功能只是 回显 ,所以只需要,将 lineedit 中的内容,序列化,与目标服务端的ip、端口号绑定起来。然后将一整个请求使用 writeDatagram 发送出去

    void Widget::on_pushButton_clicked()
    {
    // 1. 获取到输入框的内容
    const QString& text = ui->lineEdit->text();
    // 2. 构造 UDP 的请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    // 3. 发送请求数据
    socket->writeDatagram(requestDatagram);
    // 4. 把发送的数据也添加到列表框中
    ui->listWidget->addItem("客户端发送:" + text);
    // 5. 把输入框的内容也清空一些
    ui->lineEdit->setText(text);
    }

    3.4 processResponse 响应报文处理槽

    简单回显即可

    void Widget::processResponse()
    {
    // 通过这个函数来处理收到的响应
    // 1. 读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2. 把响应数据显示到界面上
    ui->listWidget->addItem("服务器响应: " + response);
    }

    3.5 整体代码

    #include "widget.h"
    #include "ui_widget.h"

    // 定义两个常量,描述服务器的地址和端口
    const QString& SERVER_IP = "127.0.0.1";
    const quint16 SERVER_PORT = 9090;

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

    socket = new QUdpSocket(this);

    // 修改窗口标题
    this->setWindowTitle("客户端");

    // 连接服务端相应内容 和 对应处理函数
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
    }

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

    void Widget::on_pushButton_clicked()
    {
    // 1. 获取到输入框的内容
    const QString& text = ui->lineEdit->text();
    // 2. 构造 UDP 的请求数据
    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    // 3. 发送请求数据
    socket->writeDatagram(requestDatagram);
    // 4. 把发送的数据也添加到列表框中
    ui->listWidget->addItem("客户端发送:" + text);
    // 5. 把输入框的内容也清空一些
    ui->lineEdit->setText(text);
    }

    void Widget::processResponse()
    {
    // 通过这个函数来处理收到的响应
    // 1. 读取到响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    // 2. 把响应数据显示到界面上
    ui->listWidget->addItem("服务器响应: " + response);
    }

    🏳️‍🌈四、测试

    可以看到成功回显了

    在这里插入图片描述如果需要测试多个客户端的情况,可以在client的build目录下,找到exe文件,多次打开就行

    在这里插入图片描述 可以发现两个客户端的端口号是不一样的

    在这里插入图片描述


    👥总结

    本篇博文对 【QT网络】构建简单Udp回显服务器 做了一个较为详细的介绍,不知道对你有没有帮助呢

    觉得博主写得还不错的三连支持下吧!会继续努力的~

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【QT网络】构建简单Udp回显服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!