目录
- [Qt 网络](#Qt 网络)
- [UDP Socket](#UDP Socket)
- [TCP Socket](#TCP Socket)
Qt 网络
Qt 为了支持跨平台, 对网络编程的 API 也进行了重新封装.Qt Network 是一个用于C++应用程序网络编程的模块,它提供了一套从底层套接字操 作到高层网络协议(如HTTP)的完整API。其核心特点是将复杂的网络通信功能封装为易于使用的类,并深度融入Qt的事件驱动 和信号槽机制,让开发者能高效地开发客户端和服务器程序。
注意:实际 Qt 开发中进行网络编程, 也不⼀定使用 Qt 封装的网络 API, 也有⼀定可能使用的是系统原生 API 或者其他第三方框架的 API。
在进行网络编程之前, 需要在项目中的 .pro 文件中添加network模块,QT中有很多模块,需要使用哪些模块,需要在.pro 文件中添加,这样在编译的时候才能把相关模块加载进来。

UDP Socket
核心 API 概览
主要的类有两个QUdpSocket 和 QNetworkDatagram
QUdpSocket 表示⼀个 UDP 的 socket 文件.

| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
bind(const QHostAddress&, quint16) |
方法 | 绑定指定的端口号。 | bind |
receiveDatagram() |
方法 | 返回 QNetworkDatagram,读取一个 UDP 数据报。 |
recvfrom |
writeDatagram(const QNetworkDatagram&) |
方法 | 发送一个 UDP 数据报。 | sendto |
readyRead |
信号 | 在收到数据并准备就绪后触发。 | 无(类似于 IO 多路复用的通知机制) |
QNetworkDatagram 表示⼀个 UDP 数据报
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16) |
构造函数 | 通过 QByteArray、目标 IP 地址、目标端口号构造一个 UDP 数据报。通常用于发送数据时。 |
无 |
data() |
方法 | 获取数据报内部持有的数据。返回 QByteArray。 |
无 |
senderAddress() |
方法 | 获取数据报中包含的对端的 IP 地址。 | 无,recvfrom 包含了该功能。 |
senderPort() |
方法 | 获取数据报中包含的对端的端口号。 | 无,recvfrom 包含了该功能。 |
回显服务器
1.创建一个QListWidget页面来显示信息

2.在Widget.h中添加QUdpSocket成员
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:
Ui::Widget *ui;
QUdpSocket* socket; //添加QUdpSocket成员
};
#endif // WIDGET_H
3.修改 widget.cpp, 完成 socket 后续的初始化
注意:⼀般来说, 要先连接信号槽, 再绑定端口.如果顺序反过来, 可能会出现端⼝绑定好了之后, 请求就过来了. 此时还没来得及连接信号槽. 那么这个请求就有可能错过了.
c
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建QUdpSocket对象
socket = new QUdpSocket(this);
//连接信号槽
connect(socket,&QUdpSocket::readyRead,this,&Widget::processRequest);
//绑定IP和端口号
bool ret = socket->bind(QHostAddress::Any,8080);
if(!ret)
{
QMessageBox::critical(this,"服务器启动出错",socket->errorString()); //errorString就相当于Linux中的perror,本质上也是对error的封装。
return;
}
}
//处理客户端请求
void Widget::processRequest()
{
//1.读取请求并解析
QNetworkDatagram requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();//返回值是QByteArray类型,可以直接赋值给QString
//2.根据请求计算响应
QString response = process(request);
//3.把响应写回到客户端
QNetworkDatagram responseDatagram(response.toUtf8(),requestDatagram.senderAddress(),
requestDatagram.senderPort());//toUtf8返回QString中的字节数组QByteArray,senderAddress和senderPort指明发送给谁
socket->writeDatagram(responseDatagram);
//4.打印日志
QString log = "["+requestDatagram.senderAddress().toString()+":"+
QString::number(requestDatagram.senderPort())+"],request:"+request
+" response:"+response;
ui->listWidget->addItem(log);
}
4.实现 process 函数
由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容.
c
const QString& Widget::process(const QString &text)
{
return text;
}
注意: "根据请求处理响应" 是服务器开发中的最核心的步骤.⼀个商业服务器程序, 这里的逻辑可能是几万行几十万行代码量级的。
此时, 服务器程序编写完毕.
但是直接运行还看不出效果. 还需要搭配客户端来使用。
回显客户端
- 创建界⾯. 包含⼀个
QLineEdit,QPushButton,QListWidget
- 先使用⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的
sizePolicy 为 Expanding - 再使用垂直布局把 QListWidget 和上面的水平布局放好.
- 设置垂直布局的 layoutStretch 为 5, 1 (自己课根据个人喜好微调)

2.在 widget.cpp 中, 先创建两个全局常量, 表示服务器的 IP 和 端口
c
const QString SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080;
- 创建 QUdpSocket 成员,修改 widget.h, 定义成员
c
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void process();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
QUdpSocket* socket;
};
4.修改 widget.cpp, 初始化 socket
c
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("client");
//创建QUdpSocket实例
socket = new QUdpSocket(this);
//连接信号槽
connect(socket,&QUdpSocket::readyRead,this,&Widget::process);
}
5.发送按钮 slot 函数, 实现发送请求.
c
void Widget::on_pushButton_clicked()
{
//获取输入数据
QString text = ui->lineEdit->text();
//封装数据报
QNetworkDatagram requestDategram(text.toUtf8(),QHostAddress(SERVER_IP),
SERVER_PORT);
//发送请求
socket->writeDatagram(requestDategram);
ui->listWidget->addItem("客户端说:"+text);
ui->lineEdit->setText("");
}
6.通过信号槽, 来处理服务器的响应.
c
//处理响应
void Widget::process()
{
//收到响应
QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
ui->listWidget->addItem("服务器说:"+response);
}
结果展示,启动多个客户端都可以正常工作

TCP Socket
TCP三次握手,建立流程的过程是操作系统在内核中完成的,应用层的代码只能告诉内核,我要发起一个连接(客户端),或者是我要获取一个建立好的连接(服务器)。
- QTcpServer 是"接待员":它的唯一工作就是监听网络端口,等待并接受新的连接请求。它不参与具体的数据交换。
- QTcpSocket 是"通讯员":它的核心工作是建立一个连接通道,负责所有数据的发送和接收。无论是客户端发起的连接,还是服务器接受的连接,最终进行数据通信的都是 QTcpSocket 对象。
核心 API 概览
核心类是两个: QTcpServer 和 QTcpSocket
QTcpServer 用于监听端口, 和获取客户端连接。
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
listen(const QHostAddress&, quint16 port) |
方法 | 绑定指定的地址和端口号,并开始监听。 | bind 和 listen |
nextPendingConnection() |
方法 | 从系统中获取一个已经建立好的 TCP 连接。返回一个 QTcpSocket,表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信。 |
accept |
newConnection |
信号 | 有新的客户端建立连接好之后触发。 | 无(类似于 IO 多路复用中的通知机制) |
QTcpSocket 用户客户端和服务器之间的数据交互。
| 名称 | 类型 | 说明 | 对标原生 API |
|---|---|---|---|
readAll() |
方法 | 读取当前接收缓冲区中的所有数据,返回 QByteArray 对象。 |
read |
write(const QByteArray&) |
方法 | 把数据写入 socket 中。 | write |
deleteLater |
方法 | 暂时把 socket 对象标记为无效,Qt 会在下个事件循环中析构释放该对象。 | 无(类似于"半自动化的垃圾回收") |
readyRead |
信号 | 有数据到达并准备就绪时触发。 | 无(类似于 IO 多路复用中的通知机制) |
disconnected |
信号 | 连接断开时触发。 | 无(类似于 IO 多路复用中的通知机制) |
QByteArray 用于表示⼀个字节数组. 可以很方便的和 QString 进行相互转换.
例如:
• 使用 QString 的构造函数即可把 QByteArray 转成 QString.
• 使用 QString 的
toUtf8函数即可把 QString 转成 QByteArray
回显服务器
- 创建界⾯. 包含⼀个 QListWidget , 用于显示收到的数据

2.创建 QTcpServer 并初始化
修改 widget.h, 添加 QTcpServer 指针成员.
c
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void processConnection();
QString process(const QString& response);
private:
Ui::Widget *ui;
QTcpServer* server;
};
3.修改 widget.cpp, 实例化 QTcpServer 并进行后续初始化操作
• 设置窗口标题
• 实例化 TCP server. (父元素设为当前控件, 会在父元素销毁时被⼀起销毁).
• 通过信号槽, 处理客户端建立的新连接.
• 监听端口
c
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("server");
//创建QTcpServer实例对象
server = new QTcpServer(this);//绑定对象树
//绑定信号槽,获取新连接时触发信号
connect(server,&QTcpServer::newConnection,this,&Widget::processConnection);
//绑定端口号并监听
bool ret = server->listen(QHostAddress::Any,8080);
if(!ret)
{
QMessageBox::critical(this,"服务器绑定失败",server->errorString());
return;
}
}
4.继续修改 widget.cpp, 实现处理连接的具体方法 processConnection
• 获取到新的连接对应的 socket.
• 通过信号槽, 处理收到请求的情况
• 通过信号槽, 处理断开连接的情况
c
//处理新连接
void Widget::processConnection()
{
//获取新连接
QTcpSocket* socket = server->nextPendingConnection();
QString log = "[" + socket->peerAddress().toString()+":"+
QString::number(socket->peerPort()) + "],client上线!";
ui->listWidget->addItem(log);
//添加信号槽
connect(socket,&QTcpSocket::readyRead,this,[=](){
//获取请求
QString request = socket->readAll();
//处理请求
QString response = process(request);
//发送响应
socket->write(response.toUtf8());
QString log = "["+socket->peerAddress().toString()+":"+
QString::number(socket->peerPort())+"] request:"+request+
",response"+response;
ui->listWidget->addItem(log);
});
//处理断开连接的情况
connect(socket,&QTcpSocket::disconnected,this,[=](){
QString log = "[" + socket->peerAddress().toString()+":"+
QString::number(socket->peerPort()) + "],client下线!";
ui->listWidget->addItem(log);
socket->deleteLater();
});
}
5.实现 process 方法, 实现根据请求处理响应.
由于我们此处是实现回显服务器. 所以 process 方法中并没有包含实质性的内容
c
QString Widget::process(const QString &response)
{
return response;
}
此时, 服务器程序编写完毕.
但是直接运行还看不出效果. 还需要搭配客户端来使用。
回显客户端
- 创建界⾯. 包含⼀个
QLineEdit,QPushButton,QListWidget
- 先使用⽔平布局把 QLineEdit 和 QPushButton 放好, 并设置这两个控件的垂直方向的
sizePolicy 为 Expanding - 再使用垂直布局把 QListWidget 和上面的水平布局放好.
- 设置垂直布局的 layoutStretch 为 5, 1 (自己课根据个人喜好微调)

2.在 widget.cpp 中, 先创建两个全局常量, 表示服务器的 IP 和 端口
c
const QString SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8080;
- 创建 QUdpSocket 成员,修改 widget.h, 定义成员
c
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
void process();
private:
Ui::Widget *ui;
QTcpSocket* socket;
};
3.修改 widget.cpp, 对 QTcpSocket 进行实例化.
• 设置窗口标题
• 实例化 socket 对象 (父元素设为当前控件, 会在父元素销毁时被⼀起销毁).
• 和服务器建立连接.
• 等待并确认连接是否出错
c
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("client");
//实例化QTcpSocket对象
socket = new QTcpSocket(this);
//连接信号槽
connect(socket,&QTcpSocket::readyRead,this,&Widget::process);
//和服务器建立连接
socket->connectToHost(SERVER_IP,SERVER_PORT);
//等待并确认连接是否出错
if(!socket->waitForConnected())
{
QMessageBox::critical(this,"服务器建立连接失败",socket->errorString());
return;
}
}
4.修改 widget.cpp, 给按钮增加点击的 slot 函数, 实现发送请求给服务器.
c
void Widget::on_pushButton_clicked()
{
//获取输入框中的内容
QString text = ui->lineEdit->text();
//发送请求
socket->write(text.toUtf8());
//打印日志
QString log = "客户端说:"+text;
ui->listWidget->addItem(log);
ui->lineEdit->setText("");
}
5.通过信号槽, 处理收到的服务器的响应.
c
//处理获取的内容
void Widget::process()
{
//获取响应
QString response = socket->readAll();
//打印日志
QString log = "服务器说:"+response;
ui->listWidget->addItem(log);
}
先启动服务器, 再启动客户端(可以启动多个), 最终执行效果:
由于我们使用信号槽处理同⼀个客户端的多个请求, 不涉及到循环, 也就不会使客户端之间相互影响了。
