Qt中UDP回显服务器和客户端
在进⾏⽹络编程之前, 需要在项⽬中的 .pro ⽂件中添加 network 模块.
添加之后要⼿动编译⼀下项⽬, 使 Qt Creator 能够加载对应模块的头⽂件
UDP Socket
核⼼ API 概览
主要的类有两个. QUdpSocket 和 QNetworkDatagram
QUdpSocket 表⽰⼀个 UDP 的 socket ⽂件.
QNetworkDatagram 表⽰⼀个 UDP 数据报.
回显服务器
- 创建界⾯, 包含⼀个 QListWidget ⽤来显⽰消息.

- 创建 QUdpSocket 成员
修改 widget.h
C++
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
void processResponse();
private:
Ui::Widget *ui;
QUdpSocket*socket;
};
#endif // WIDGET_H
修改 widget.cpp, 完成 socket 后续的初始化
⼀般来说, 要先连接信号槽, 再绑定端⼝.
如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了
C++
#include <QMessageBox>
#include <QNetworkDatagram>
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::processRequest);
//绑定端口号
bool ret=socket->bind(QHostAddress::Any,9090);
if(!ret)
{
//绑定失败
QMessageBox::critical(this,"服务器启动出错",socket->errorString());
return;
}
}
- 实现 processRequest , 完成处理请求的过程
• 读取请求并解析
• 根据请求计算响应
• 把响应写回到客⼾端
C++
//服务器最核心的逻辑
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);
}
- 实现 process 函数
C++
QString Widget::process(const QString &request)
{
return request;
}
💡 "根据请求处理响应" 是服务器开发中的最核⼼的步骤.
⼀个商业服务器程序, 这⾥的逻辑可能是⼏万⾏⼏⼗万⾏代码量级的
此时, 服务器程序编写完毕.
但是直接运⾏还看不出效果. 还需要搭配客⼾端来使⽤.
回显客⼾端
- 创建界⾯. 包含⼀个 QLineEdit , QPushButton , QListWidget
• 先使⽤⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直⽅向的sizePolicy 为 Expanding
• 再使⽤垂直布局把 QListWidget 和上⾯的⽔平布局放好.
• 设置垂直布局的 layoutStretch 为 5, 1 (当然这个尺⼨⽐例根据个⼈喜好微调).

- 在 widget.cpp 中, 先创建两个全局常量, 表⽰服务器的 IP 和 端⼝
C++
//定义两个常量,表示服务器的地址和端口号
const QString&SERVER_IP="127.0.0.1";
const quint16 SERVER_PORT=9090;
- 创建 QUdpSocket 成员
修改 widget.h, 定义成员
C++
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
void processResponse();
private:
Ui::Widget *ui;
QUdpSocket*socket;
};
#endif // WIDGET_H
修改 widget.cpp, 初始化 socket
C++
#include <QWidget>
#include <QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
void processResponse();
private:
Ui::Widget *ui;
QUdpSocket*socket;
};
#endif // WIDGET_H
- 给发送按钮 slot 函数, 实现发送请求.
C++
void Widget::on_pushButton_clicked()
{
//获取到输入框的内容
const QString& text=ui->lineEdit->text();
//构造UDP的请求数据
QNetworkDatagram requestDatagram(text.toUtf8(),QHostAddress(SERVER_IP),SERVER_PORT);
//发送请求数据
socket->writeDatagram(requestDatagram);
//把发送的数据添加到列表框
ui->listWidget->addItem("客户端说:"+text);
//把输入框的内容清空一下
ui->lineEdit->setText("");
}
- 再次修改 Widget 的构造函数, 通过信号槽, 来处理服务器的响应.
C++
//通过信号槽来处理,来处理服务器返回的数据
connect(socket,&QUdpSocket::readyRead,this,&Widget::processResponse);
void Widget::processResponse()
{
//通过这个函数来处理收到的响应
//1.读取到响应数据
const QNetworkDatagram& responseDatagram=socket->receiveDatagram();
QString response=responseDatagram.data();
//2.把响应数据显示到界面上
ui->listWidget->addItem("服务器说:"+response);
}