【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> // 需要引入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服务端的顺序
- 将服务端套接字连接到相应的处理信号槽上
- 绑定套接字到指定端口上,开始监听指定的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回显服务器 做了一个较为详细的介绍,不知道对你有没有帮助呢
觉得博主写得还不错的三连支持下吧!会继续努力的~