📢博客主页: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服务端的顺序
如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了.此时还没来得及连接信号槽,那么这个请求就有可能错过了
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回显服务器 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~
评论前必须登录!
注册