Qt 网络编程
概述
Qt 网络编程主要依赖其 Qt Network
模块,提供了跨平台的网络通信能力,支持 TCP、UDP、HTTP 等多种网络协议。下面从核心类、典型场景和示例代码三个方面介绍 Qt 网络编程的基础用法。
使用 Qt 网络功能前,需在项目文件(.pro
)中添加模块依赖:
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函数阻塞程序运行,直到连接成功或失败,例如:
socket->connectToHost("192.168.100.100", 6666);
if(socket->waitForConnected(1000))
{qDebug("connected");
}
与服务器端建立socket连接后,就可以向缓冲区写数据或者从接收缓冲区读取数据,实现数据的通信。当缓冲区有新数据进入时,会触发readyRead信号,一般在此信号的槽函数里读取缓冲区数据。QTcpSocket是从QIODevice间接继承的,所以可以使用流数据读写功能。一个QTcpSocket实例既可以接收数据也可以发送数据,且接收与发射是异步工作的,有各自的缓冲区。
TCP网络编程流程图
官方文档中内容

服务器端的设计
ui设计
//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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设计
//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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控件,用于显示客户端发送过来的数据
//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QUdpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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中的数据发送给服务器。
//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QUdpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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的代码设计
//widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QNetworkAccessManager>QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic: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响应体积很大,就会导致界面渲染缓慢甚至被卡住。