当前位置: 首页 > news >正文

【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概览

主要的类有两个.QUdpSocketQNetworkDatagram

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>   // 需要引入networkQT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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服务端的顺序

  1. 将服务端套接字连接到相应的处理信号槽上
  2. 绑定套接字到指定端口上,开始监听指定的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 处理信号槽

完成处理请求的过程

  1. 读取请求并解析
  2. 根据请求计算响应
  3. 把响应写回到客户端
// 这个函数完成的逻辑,就是服务器的最核心逻辑了
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个 QLineEditQPushButtonQListwidget

  • 先使用水平布局QLineEditQPushButton 放好,并设置这两个控件的垂直方向的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回显服务器 做了一个较为详细的介绍,不知道对你有没有帮助呢

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

相关文章:

  • Flutter Dart 循环语句 for while do..while break、continue
  • CGAL 网格内部生成随机点
  • 图论---朴素Prim(稠密图)
  • Linux内核netlink机制 - 连接器(Netlink Connector)
  • 解决cannot find attribute `serde` in this scope记录
  • 远程访问服务器的Jupyter Notebook
  • 生成随机验证码-解析与优化
  • 代码随想录算法训练营第一天:数组part1
  • 第六章 QT基础:6、QT的Qt 时钟编程
  • 协作开发攻略:Git全面使用指南 — 第三部分 特殊应用场景
  • JW01三合一传感器详解(STM32)
  • 深度剖析操作系统核心(第一节):从X86/ARM/MIPS处理器架构到虚拟内存、分段分页、Linux内存管理,再揭秘进程线程限制与优化秘籍,助你成为OS高手!
  • Ubuntu 一站式部署 RabbitMQ 4 并“彻底”迁移数据目录的终极实践
  • 【回眸】Aurix TC397 IST 以太网 UDP 相关开发
  • 观成科技:摩诃草组织Spyder下载器流量特征分析
  • SpringCloud——负载均衡
  • Mediamtx与FFmpeg远程与本地推拉流使用
  • 信息系统项目管理工程师备考计算类真题讲解七
  • 【晶振】晶振的工作原理及其与单片机关系
  • 【C语言】C语言中的联合体与枚举类型
  • 限制再放宽!新版市场准入负面清单缩减到106项
  • 爱奇艺要转型做微剧?龚宇:是误解,微剧是增量业务,要提高投资回报效益
  • 对话地铁读书人|媒体人Echo:读书使人远离“班味”
  • 瞭望:高校大门要向公众打开,不能让“一关了之”成为常态
  • 世界读书日|阅读在上海
  • “80后”保利文化集团董事长王波挂职哈尔滨副市长,负责文旅、招商