【Qt开发】网络运用
目录
前言:
1,UDP Socket
2,TCP Socket
3,HTTP Client
前言:
Qt这里的网络部分说明都是建立在已经有Linux系统网络知识的基础上,这里不在详细说明。还有,Qt是分模块处理的,默认情况下Qt只链接了gui模块。若是要进行网络方面的代码编写,需要在项目配置文件(.pro文件)中的 QT += core gui 中添加 network,让Qt链接网络模块,确保编译器和链接器知道在编译和链接过程中包含相应的库和头文件。
QT += core gui network
1,UDP Socket
Udp Socket主要的类有两个——QUdpSocket 和 QNetworkDatagram。
QUdpSocket 表示一个 UDP 的 socket文件。
QNetworkDatagram 表示一个 UDP 数据报。
下面来实现一个 UDP Socket。服务端程序如下:
// widget.h头文件
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void processRequest(); // 数据到来时的处理函数
QString process(const QString& request); // 响应
private:
Ui::Widget *ui;
QListWidget* listWidget;
QUdpSocket* socket;
};
// widget.cpp源文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
listWidget = new QListWidget(this);
listWidget->setMaximumSize(this->geometry().width(), this->geometry().height());
listWidget->setMinimumSize(this->geometry().width(), this->geometry().height());
socket = new QUdpSocket(this);
this->setWindowTitle("服务器");
// 先连接信号槽,后绑定端口
// readyRead信号用于在UDP套接字有可读取的数据报到达时发出通知
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
bool ret = socket->bind(QHostAddress::Any, 9090); // Any表示服务器将监听所有可用的网络接口
if (!ret) {
// 绑定失败
QMessageBox::critical(this, "服务器启动出错", socket->errorString());
return;
}
}
void Widget::processRequest()
{
// 读取请求
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
// 根据请求计算响应(这里设计的请求与响应完全一样)
const QString& response = process(request);
// 把响应写回到客户端
QNetworkDatagram responseDatagram(response.toUtf8(),
requestDatagram.senderAddress(), requestDatagram.senderPort());
socket->writeDatagram(responseDatagram);
// 显⽰打印⽇志
QString log = "[" + requestDatagram.senderAddress().toString() + ":" +
QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
listWidget->addItem(log);
}
QString Widget::process(const QString& request)
{
return request;
}
客户端程序如下:
ui界面的设计
// widget.cpp源文件
// 定义服务器的 IP 和 端⼝
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);
this->setWindowTitle("客户端");
socket = new QUdpSocket(this);
connect(socket, &QUdpSocket::readyRead, this, [=]() {
// 读取响应的数据
const QNetworkDatagram responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
// 显示响应的数据
ui->listWidget->addItem(QString("服务器说: ") + response);
});
}
void Widget::on_pushButton_clicked()
{
// 构造请求数据
const QString& text = ui->lineEdit->text();
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP),
SERVER_PORT);
// 发送请求
socket->writeDatagram(requestDatagram);
// 消息添加到列表框中
ui->listWidget->addItem("客户端说: " + text);
// 清空输⼊框
ui->lineEdit->setText("");
}
测试运行图
2,TCP Socket
TCP Socket 核心类有两个——QTcpServer 和 QTcpSocket。
QTcpServer 用于监听端口和获取客户端连接。
QTcpSocket 用户客户端和服务器之间的数据交互。
QByteArray 本质是一个字节数组,用于存储二进制数据和文本数据。它可以很方便的和 QString 进行相互转换。例如:
- 使用 QString 的构造函数即可把 QByteArray 转成 QString。
- 使用 QString 的 toUtf8 函数即可把 QString 转成 QByteArray。
下面来实现一个 TCP Socket。服务端程序如下:
// widget.h头文件
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
void processConnection();
const QString process(const QString request);
private:
Ui::Widget *ui;
QListWidget* listWidget;
// 创建 QTcpServer
QTcpServer* tcpServer;
};
// widget.cpp源文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
listWidget = new QListWidget(this);
listWidget->setMaximumSize(this->geometry().width(), this->geometry().height());
listWidget->setMinimumSize(this->geometry().width(), this->geometry().height());
this->setWindowTitle("服务器");
tcpServer = new QTcpServer(this);
// 通过信号槽,处理客户端建⽴的新连接
connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
// 监听端口
bool ret = tcpServer->listen(QHostAddress::Any, 9090);
if (!ret) {
QMessageBox::critical(nullptr, "服务器启动失败", tcpServer->errorString());
exit(1);
}
}
void Widget::processConnection()
{
// 获取到新的连接对应的socket
QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客户端上线";
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;
listWidget->addItem(log);
});
// 通过信号槽, 处理断开连接的情况
connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
// 显示日志
QString log = QString("[") + clientSocket->peerAddress().toString()
+ ":" + QString::number(clientSocket->peerPort()) + "] 客户端下线";
listWidget->addItem(log);
// 删除clientSocket。该方法不会立刻执行,而是在下一轮的事件循环中被销毁
clientSocket->deleteLater();
});
}
const QString Widget::process(const QString request)
{
return request;
}
客户端程序如下:
// widget.h头文件
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
// 新增 QTcpSocket
QTcpSocket* socket;
};
// widget.cpp源文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("客户端");
socket = new QTcpSocket(this);
// 和服务器建⽴连接
socket->connectToHost("127.0.0.1", 9090);
// 连接信号槽,处理服务器返回的响应
connect(socket, &QTcpSocket::readyRead, this, [=]() {
QString response = socket->readAll();
qDebug() << response;
ui->listWidget->addItem(QString("服务器说: ") + response);
});
// 等待连接并确认连接是否出错
if (!socket->waitForConnected()) {
QMessageBox::critical(nullptr, "连接服务器出错", socket->errorString());
exit(1);
}
}
void Widget::on_pushButton_clicked()
{
// 获取输⼊框的内容
const QString& text = ui->lineEdit->text();
// 清空输⼊框内容
ui->lineEdit->setText("");
// 把消息显示到界⾯上
ui->listWidget->addItem(QString("客户端说: ") + text);
// 发送消息给服务器
socket->write(text.toUtf8());
}
3,HTTP Client
在网络方面,Qt只提供了HTTP客户端的库,没有提供HTTP服务端的库。HTTP本质上也是基于 TCP Socket 进行封装。Qt主要提供了三个类对此操作QNetworkAccessManager,QNetworkRequest,QNetworkReply。
QNetworkAccessManager 提供了 HTTP 的核心操作。
QNetworkRequest 表示一个 HTTP 请求(不含body)。如果需要发送一个带有 body 的请求(比如 post),会在 QNetworkAccessManager 的 post 方法中通过单独的参数来传入 body。
QUrl是Qt表示Url的类,QVariant类似于C语言中的void*,表示一个 “类型可变” 的值。其中的 QNetworkRequest::KnownHeaders 是一个枚举类型,常用取值如下:
QNetworkReply 表示一个 HTTP 响应。
此外,QNetworkReply 还有一个重要的信号 finished,它会在客户端收到完整的响应数据之后触发。代码运用如下:
// widget.h头文件
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_pushButton_clicked();
private:
Ui::Widget *ui;
// 新增属性
QNetworkAccessManager* manager;
};
// widget.cpp源文件
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 实例化属性
manager = new QNetworkAccessManager(this);
}
void Widget::on_pushButton_clicked()
{
// 获取到输⼊框中的URL,构造QUrl对象
QUrl url(ui->lineEdit->text());
// 构造HTTP请求对象,发送GET请求
QNetworkRequest request(url);
QNetworkReply* response = manager->get(request); // get非阻塞函数
// 通过信号槽来处理响应
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进行释放
response->deleteLater();
});
}