概述
Qt 网络编程主要依赖其 Qt Network
模块,提供了跨平台的网络通信能力,支持 TCP、UDP、HTTP 等多种网络协议。下面从核心类、典型场景和示例代码三个方面介绍 Qt 网络编程的基础用法。
使用 Qt 网络功能前,需在项目文件(.pro
)中添加模块依赖:
cpp
QT += network # 启用网络模块
常用核心类:
- TCP 通信 :
QTcpServer
(服务器端,监听连接)、QTcpSocket
(客户端 / 服务器端连接后的数据传输) - UDP 通信 :
QUdpSocket
(无连接的数据报传输) - HTTP 通信 :
QNetworkAccessManager
(管理 HTTP 请求)、QNetworkRequest
(请求信息)、QNetworkReply
(响应结果)
TCP 通信
TCP(Transmission Control Protocol)是一种被大多数Internet网路协议(如HTTP和FTP)用于数据传输的低级网络协议,它是可靠的、面向流、面向连接的传输协议,特别适合用于连续数据的传输。
TCP通信必须先建立TCP协议,通信端分为客户端和服务端。如下图

Qt提供QTcpSocket类和QTcpServer类用于建立TCP通信应用程序。服务器端程序必须使用QTcpServer用于端口监听,建立服务器;QTcpSocket用于建立连接后使用套接字进行通信。
QTcpServer是从QObject继承的类,它主要用于服务器端建立网络监听,创建网络Socket连接。QTcpServer类的主要接口函数入下:

服务器端程序首先需要用QTcpServer::listen()开始服务器端监听,可以指定监听的IP地址和端口,一般一个服务程序只监听某个端口的网络连接。当有新的客户端接入时,QTcpServer内部的incommingConnection函数会创建一个与客户端连接的QTcpSocket对象,然后发射信号newConnection函数。在newConnection信号的槽函数中,可以用nextPengdingConnection接受客户端的连接,然后使用QTcpSocket与客户端通信。所以在客户端与服务器建立TCP连接后,具体的数据通信是通过QTcpSocket完成的。QTcpSocket类提供了TCP协议的接口,可以用QTcpSocket类实现标准的网络通信协议入POP3、SMTP和NNTP,也可以设计自定义协议。
QTcpSocket是从QIODevice间接继承的类,所以具有流读写的功能,继承关系如下:

QTcpSocket类除了构造与析构外,其他函数都是从QAbstractSocket继承或者重定义的。QAbstractSocket用于TCP通信的主要接口函数如下:

TCP客户端使用QTcpSocket与Tcp服务器建立连接并通信。客户端的QTcpSocket实例首先通过connectToHost函数尝试连接到服务器,需要指定服务器的IP地址和端口。connectToHost函数是异步方式连接到服务器,不会阻塞程序运行,连接后发射connected信号。如果需要使用阻塞式方式连接服务器,则使用waitForConnected函数阻塞程序运行,直到连接成功或失败,例如:
cpp
socket->connectToHost("192.168.100.100", 6666);
if(socket->waitForConnected(1000))
{
qDebug("connected");
}
与服务器端建立socket连接后,就可以向缓冲区写数据或者从接收缓冲区读取数据,实现数据的通信。当缓冲区有新数据进入时,会触发readyRead信号,一般在此信号的槽函数里读取缓冲区数据。QTcpSocket是从QIODevice间接继承的,所以可以使用流数据读写功能。一个QTcpSocket实例既可以接收数据也可以发送数据,且接收与发射是异步工作的,有各自的缓冲区。
TCP网络编程流程图
官方文档中内容
服务器端的设计
ui设计

cpp
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void handle();
QString process(const QString &request);
private:
Ui::Widget *ui;
QTcpServer *_tcpSever;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
setWindowTitle("TCP服务器");
//初始化
_tcpSever = new QTcpServer(this);
//通过槽函数进行连接
connect(_tcpSever, &QTcpServer::newConnection, this, &Widget::handle);
//监听端口
bool ret = _tcpSever->listen(QHostAddress::Any, 8888);
if(!ret)
{
QMessageBox::critical(this, "服务器启动失败", _tcpSever->errorString());
return;
}
}
void Widget::handle()
{
//获取到新的连接对应的socket.
QTcpSocket *clientSocket = _tcpSever->nextPendingConnection();
QString log = QString('[') + clientSocket->peerAddress().toString() + ":"
+ QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
ui->listWidget->addItem(log);
//通过信号槽,处理收到请求的情况
connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
//读取出请求数据
QString request = clientSocket->readAll();
//根据请求处理响应
const QString &response = process(request);
//写会客户端
clientSocket->write(response.toUtf8());
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] req: " +
request + ", resp: " + response;
ui->listWidget->addItem(log);
});
//通过信号槽,处理断开连接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
QString log = "[" + clientSocket->peerAddress().toString() + ":"
+ QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
ui->listWidget->addItem(log);
clientSocket->deleteLater();
});
}
QString Widget::process(const QString &request)
{
return request;
}
Widget::~Widget()
{
delete ui;
}
客户端的设计
ui设计

cpp
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
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_btnSend_clicked();
private:
Ui::Widget *ui;
QTcpSocket *_tcpClient;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗⼝标题
setWindowTitle("TCP客户端");
//实例化socket对象
_tcpClient = new QTcpSocket(this);
//与服务器建⽴连接
_tcpClient->connectToHost("127.0.0.1", 8888);
//处理服务器返回的响应.
connect(_tcpClient, &QTcpSocket::readyRead, this, [=](){
//读取当前接收缓冲区中的所有数据
QString response = _tcpClient->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});
//等待并确认连接是否出错
if(!_tcpClient->waitForConnected()){
QMessageBox::critical(this, "连接服务器出错!", _tcpClient->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnSend_clicked()
{
//获取输⼊框的内容
const QString &text = ui->lineEdit->text();
//清空输⼊框内容
ui->lineEdit->setText("");
//把消息显⽰到界⾯上
ui->listWidget->addItem(QString("客⼾端说: ") + text);
//发送消息给服务器
_tcpClient->write(text.toUtf8());
}
测试结果
先启动服务器,然后再启动客户端,在客户端发送数据给服务器。

UDP 通信
UDP(User Datagram Protocol,用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,它可以用于对可靠性要求不高的场合。与TCP通信不同,两个程序之间进行UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址与端口。

QUdpSocket类用于实现UDP通信,它从QAbstractSocket类继承,因而与QTcpSocket共享大部分的接口函数。主要区别是QUdpSocket以数据报传输数据,而不是以连续的数据流。发送数据使用函数QUdpSocket::writeDatagram(),数据报的长度一般少于512字节,每个数据报包含发送者和接受者的IP地址和端口等信息。
要进行UDP数据接收,要用QUdpSocket::bind()函数先绑定一个端口,用于接收传入的数据报。当有数据报传入时会发射readyRead()信号,使用readDatagram()函数来读取接收到的数据报。
QUdpSocket是从QAbstractSocket继承而来,但是又定义了较多新的功能函数用于实现UDP特有的一些功能。QUdpSocket的主要功能函数如下:


服务器端的设计
在ui界面,设计一个listWidget控件,用于显示客户端发送过来的数据
cpp
//widget.h
#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();
void handle();
QString process(const QString &req);
private:
Ui::Widget *ui;
QUdpSocket *_udpSocket;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//设置窗口标题
setWindowTitle("UDP服务器端");
//实例化socket
_udpSocket = new QUdpSocket(this);
//在收到数据并准备就绪后会触发readyRead信号,然后与handle函数建立连接
connect(_udpSocket, &QUdpSocket::readyRead, this, &Widget::handle);
//绑定端口号,如果绑定失败,会调用对话框报错
bool ret = _udpSocket->bind(QHostAddress::Any, 8888);
if(!ret)
{
QMessageBox::critical(this, "服务器启动错误", _udpSocket->errorString());
return;
}
}
void Widget::handle()
{
//1、读取请求,读取一个UDP数据报,并且获取数据
const QNetworkDatagram &requestDatagram = _udpSocket->receiveDatagram();
QString request = requestDatagram.data();
//2、根据请求计算响应
const QString &response = process(request);
//3、将响应写回到客户端
//封装数据报,并将其发送给客户端
QNetworkDatagram responseDatagram(response.toUtf8(),
requestDatagram.senderAddress(),
requestDatagram.senderPort());
_udpSocket->writeDatagram(responseDatagram);
//显示打印日志
QString log = "[" + requestDatagram.senderAddress().toString()
+ ":" + QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
QString Widget::process(const QString &req)
{
return req;
}
Widget::~Widget()
{
delete ui;
}
客户端的设计
在ui界面,设计一个listWidget空间,用于显示信息;Line Edit用于客户端数据的输入,PushButton用于将Line Edit中的数据发送给服务器。
cpp
//widget.h
#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_btnSend_clicked();
void handle();
private:
Ui::Widget *ui;
QUdpSocket *_udpClient;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>
//提前定义好服务器的 IP 和 端⼝
const QString &SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8888;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
_udpClient = new QUdpSocket(this);
setWindowTitle("UDP客户端");
//在收到数据并准备就绪后会触发readyRead信号,然后与handle函数建立连接
connect(_udpClient, &QUdpSocket::readyRead, this, &Widget::handle);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnSend_clicked()
{
//获取到输⼊框的内容
const QString &text = ui->lineEdit->text();
//构造请求数据
QNetworkDatagram requestData(text.toUtf8(),
QHostAddress(SERVER_IP),
SERVER_PORT);
//发送请求
_udpClient->writeDatagram(requestDatagram);
//消息添加到列表框中
ui->listWidget->addItem("客户端:" + text);
//清空输⼊框
ui->lineEdit->setText("");
}
void Widget::handle()
{
//读取响应数据
const QNetworkDatagram &responseDatagram = _udpClient->receiveDatagram();
QString response = responseDatagram.data();
//把响应数据显示到界面上
ui->listWidget->addItem(QString("服务器说: ") + response);
}
测试结果
先启动服务器,然后再启动客户端,在客户端发送数据给服务器。

HTTP通信

Qt网络模块提供一些类实现OSI七层网络模型中高层的网络协议,如HTTP、FTP、SNMP等,这些类主要是QNetworkRequest网络请求、QNetworkReply网络回复和QNetworkAccessManager网络访问管理器。其中,QNetworkRequest类通过一个URL地址发起网络协议请求,也保存网络请求的信息,目前支持HTTP、FTP和局部文件URLs的上传和下载。QNetworkAccessManager类用于协调网络操作。在QNetworkRequest发起一个网络请求后,QNetworkAccessManager类负责发送网络请求,创建网络响应。QNetworkReply类表示网络请求的响应。QNetworkAccessManager在发送一个网络请求后创建一个网络响应。QNetworkReply提供的信号finished、readyRead和downloadProgress可以检测网络响应的执行情况,执行响应操作。QNetworkReply是QIODevice的子类,所以QNetworkReply支持流读写功能,也支持异步或者同步工作模式。
进行Qt开发时,和服务器之间的通信很多时候也会用到HTTP协议。
-
通过HTTP从服务器获取数据;
-
通过HTTP向服务器提交数据

Http的代码设计
cpp
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QNetworkAccessManager>
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();
private:
Ui::Widget *ui;
QNetworkAccessManager *_httpManager;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QUrl>
#include <QNetworkReply>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
setWindowTitle("Http客户端");
_httpManager = new QNetworkAccessManager(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
//获取到输⼊框中的URL, 构造QUrl对象
QUrl url(ui->lineEdit->text());
//构造HTTP请求对象
QNetworkRequest request(url);
//发送GET请求
QNetworkReply *response = _httpManager->get(request);
//通过信号槽来处理响应
connect(response, &QNetworkReply::finished, this, [=]() {
if(response->error() == QNetworkReply::NoError)
{
//响应正确
QString html(response->readAll());
ui->plainTextEdit->setPlainText(html);
//qDebug() << html;
} else {
//响应出错
ui->plainTextEdit->setPlainText(response->errorString());
}
response->deleteLater();
});
}
测试结果
如果使用的是http没有问题,但是如果使用https的时候有问题,可以按照如下操作:将libssl-1_1-x64.dll与libcrypto-1_1-x64.dll文件放在D:\software\qt5\5.14.2\mingw73_64\bin目录下(自己的就找到之前安装Qt软件包的位置),当然如果是32位系统,则是如下两个文件libssl-1_1.dll和libcrypto-1_1.dll(这两个文件我这里有,也可以网络下载)
此处建议使用QPlainTextEdit而不是QTextEdit。主要因为QTextEdit要进行富文本解析,如果得到的HTTP响应体积很大,就会导致界面渲染缓慢甚至被卡住。
