Qt 零基础教程(12)- 网络编程入门之下:HTTP 请求与 TCP 多人聊天室实战
2026/7/11 12:02:42 网站建设 项目流程

前言

大家好,我是白玉同学。上一篇我们学习了 TCP 和 UDP 的基础 Socket 编程,今天我们继续网络编程的下半部分:HTTP 协议请求,以及基于 TCP 实现一个多人聊天室的完整实战项目。

HTTP 是日常开发中最常用的网络协议,而聊天室项目能把前面学的 TCP 知识彻底融会贯通。话不多说,直接开干。

本文属于

一、HTTP 协议与 QNetworkAccessManager

1.1 HTTP 简介

HTTP(HyperText Transfer Protocol,超文本传输协议)是基于 TCP 的应用层协议,是 Web 世界的基石。

  • 默认端口:80(HTTP)、443(HTTPS)
  • 请求方法:GET、POST、PUT、DELETE 等
  • 特点:请求 - 响应模式、无状态

1.2 Qt HTTP 编程三剑客

Qt 将 HTTP 封装为三个核心类:

表格

作用
QNetworkAccessManager调度器,管理所有请求,一个 App 通常只实例化一个
QNetworkRequest封装请求信息(URL、Header、请求方法等)
QNetworkReply封装响应(状态码、响应体、错误信息等)

1.3 异步编程模型

Qt 的网络请求全部是异步的,通过信号槽获取结果,不会阻塞主线程:

创建QNetworkRequest → manager.get/post发送 → 得到QNetworkReply* → 关联reply的finished信号 → 读取响应数据 → reply->deleteLater()

二、GET 与 POST 请求实战

2.1 GET 请求示例

#include <QCoreApplication> #include <QNetworkAccessManager> #include <QNetworkRequest> #include <QNetworkReply> #include <QUrl> #include <QDebug> class HttpDemo : public QObject { Q_OBJECT public: void doGet() { QNetworkRequest request; request.setUrl(QUrl("https://httpbin.org/get?name=baiyu&lang=qt")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QNetworkReply *reply = m_manager.get(request); connect(reply, &QNetworkReply::finished, this, [=](){ if (reply->error() == QNetworkReply::NoError) { QByteArray data = reply->readAll(); qDebug() << "GET响应:" << data; } else { qDebug() << "请求失败:" << reply->errorString(); } reply->deleteLater(); }); } private: QNetworkAccessManager m_manager; };

2.2 POST 请求(JSON 格式)

void doPost() { QNetworkRequest request; request.setUrl(QUrl("https://httpbin.org/post")); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); // 构造JSON请求体 QJsonObject json; json["username"] = "baiyu"; json["password"] = "123456"; QByteArray body = QJsonDocument(json).toJson(); QNetworkReply *reply = m_manager.post(request, body); connect(reply, &QNetworkReply::finished, this, [=](){ // 获取HTTP状态码 int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute).toInt(); qDebug() << "状态码:" << statusCode; QByteArray data = reply->readAll(); qDebug() << "POST响应:" << data; reply->deleteLater(); }); }

2.3 下载文件

void downloadFile(const QString &url, const QString &savePath) { QNetworkRequest request(url); QNetworkReply *reply = m_manager.get(request); // 监听下载进度 connect(reply, &QNetworkReply::downloadProgress, this, [](qint64 bytesReceived, qint64 bytesTotal){ if (bytesTotal > 0) { int percent = bytesReceived * 100 / bytesTotal; qDebug() << "下载进度:" << percent << "%"; } }); connect(reply, &QNetworkReply::finished, this, [=](){ if (reply->error() == QNetworkReply::NoError) { QFile file(savePath); if (file.open(QIODevice::WriteOnly)) { file.write(reply->readAll()); file.close(); qDebug() << "下载完成:" << savePath; } } reply->deleteLater(); }); }

💡 提示:Qt 6 默认支持 HTTPS,但需要系统安装 OpenSSL 库。Windows 下可将libcrypto-1_1-x64.dlllibssl-1_1-x64.dll放到 exe 同级目录。


三、项目实战:TCP 多人聊天室

3.1 需求分析

实现一个基于 TCP 的多人聊天室:

  • 服务端:监听端口,管理所有在线客户端,转发消息
  • 客户端:连接服务器,发送消息,接收所有人的消息
  • 支持:用户上线 / 下线通知、群聊消息

3.2 通信协议设计

为了简单,我们使用纯文本协议,每行一条消息:

表格

消息类型格式说明
普通聊天MSG:用户名:内容群聊消息
系统通知SYS:内容上下线等系统消息
设置用户名NAME:用户名客户端连接后发送

3.3 整体架构

┌─── 客户端A 服务端(8888端口) ───┼─── 客户端B └─── 客户端C 服务端收到任何客户端的消息后,转发给所有其他客户端

四、聊天室服务端实现

4.1 服务端头文件

chatserver.h

#ifndef CHATSERVER_H #define CHATSERVER_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QTextEdit> #include <QList> class ChatServer : public QWidget { Q_OBJECT public: explicit ChatServer(QWidget *parent = nullptr); private slots: void onNewConnection(); void onReadyRead(); void onDisconnected(); private: // 广播消息给所有客户端(可选排除某个) void broadcast(const QString &msg, QTcpSocket *exclude = nullptr); QTcpServer *m_server; QList<QTcpSocket*> m_clients; // 所有在线客户端 QMap<QTcpSocket*, QString> m_names; // socket -> 用户名 QTextEdit *m_logEdit; }; #endif // CHATSERVER_H

4.2 服务端实现

chatserver.cpp

#include "chatserver.h" #include <QVBoxLayout> #include <QLabel> ChatServer::ChatServer(QWidget *parent) : QWidget(parent) { setWindowTitle("聊天室服务端 - 8888"); resize(500, 400); m_logEdit = new QTextEdit; m_logEdit->setReadOnly(true); auto *layout = new QVBoxLayout(this); layout->addWidget(new QLabel("服务端日志:")); layout->addWidget(m_logEdit); m_server = new QTcpServer(this); m_server->listen(QHostAddress::Any, 8888); connect(m_server, &QTcpServer::newConnection, this, &ChatServer::onNewConnection); m_logEdit->append("服务端已启动,监听端口 8888..."); } void ChatServer::onNewConnection() { QTcpSocket *socket = m_server->nextPendingConnection(); m_clients.append(socket); QString ip = socket->peerAddress().toString(); m_logEdit->append(QString("新连接:%1").arg(ip)); connect(socket, &QTcpSocket::readyRead, this, &ChatServer::onReadyRead); connect(socket, &QTcpSocket::disconnected, this, &ChatServer::onDisconnected); } void ChatServer::onReadyRead() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) return; // 按行读取(解决TCP粘包问题的简易方案) while (socket->canReadLine()) { QString line = QString::fromUtf8(socket->readLine()).trimmed(); m_logEdit->append("收到:" + line); // 解析协议 if (line.startsWith("NAME:")) { QString name = line.mid(5); m_names[socket] = name; broadcast(QString("SYS:%1 加入了聊天室").arg(name)); } else if (line.startsWith("MSG:")) { // MSG:内容 QString content = line.mid(4); QString name = m_names.value(socket, "匿名"); broadcast(QString("MSG:%1:%2").arg(name).arg(content)); } } } void ChatServer::onDisconnected() { QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender()); if (!socket) return; QString name = m_names.value(socket, "匿名"); m_clients.removeAll(socket); m_names.remove(socket); socket->deleteLater(); m_logEdit->append(QString("%1 已断开").arg(name)); broadcast(QString("SYS:%1 离开了聊天室").arg(name)); } void ChatServer::broadcast(const QString &msg, QTcpSocket *exclude) { QByteArray data = (msg + "\n").toUtf8(); for (QTcpSocket *client : m_clients) { if (client != exclude && client->isOpen()) { client->write(data); } } }

五、聊天室客户端实现

5.1 客户端头文件

chatclient.h

#ifndef CHATCLIENT_H #define CHATCLIENT_H #include <QWidget> #include <QTcpSocket> #include <QTextEdit> #include <QLineEdit> #include <QPushButton> #include <QLabel> class ChatClient : public QWidget { Q_OBJECT public: explicit ChatClient(QWidget *parent = nullptr); private slots: void onConnectClicked(); void onSendClicked(); void onConnected(); void onReadyRead(); void onDisconnected(); private: void appendMessage(const QString &text); QTcpSocket *m_socket; QString m_username; QTextEdit *m_chatEdit; // 聊天记录 QLineEdit *m_nameEdit; // 用户名 QLineEdit *m_ipEdit; QLineEdit *m_portEdit; QLineEdit *m_msgEdit; // 消息输入 QPushButton *m_connectBtn; QPushButton *m_sendBtn; }; #endif // CHATCLIENT_H

5.2 客户端实现

chatclient.cpp

#include "chatclient.h" #include <QVBoxLayout> #include <QHBoxLayout> ChatClient::ChatClient(QWidget *parent) : QWidget(parent) { setWindowTitle("TCP聊天室 - 客户端"); resize(500, 450); // UI初始化 m_chatEdit = new QTextEdit; m_chatEdit->setReadOnly(true); m_nameEdit = new QLineEdit; m_nameEdit->setPlaceholderText("输入昵称"); m_ipEdit = new QLineEdit("127.0.0.1"); m_portEdit = new QLineEdit("8888"); m_connectBtn = new QPushButton("连接"); m_msgEdit = new QLineEdit; m_msgEdit->setPlaceholderText("输入消息,回车发送"); m_sendBtn = new QPushButton("发送"); m_sendBtn->setEnabled(false); // 顶部连接栏 auto *topLayout = new QHBoxLayout; topLayout->addWidget(new QLabel("昵称:")); topLayout->addWidget(m_nameEdit); topLayout->addWidget(new QLabel("IP:")); topLayout->addWidget(m_ipEdit); topLayout->addWidget(new QLabel("端口:")); topLayout->addWidget(m_portEdit); topLayout->addWidget(m_connectBtn); // 底部发送栏 auto *bottomLayout = new QHBoxLayout; bottomLayout->addWidget(m_msgEdit); bottomLayout->addWidget(m_sendBtn); auto *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(m_chatEdit); mainLayout->addLayout(topLayout); mainLayout->addLayout(bottomLayout); // Socket m_socket = new QTcpSocket(this); connect(m_connectBtn, &QPushButton::clicked, this, &ChatClient::onConnectClicked); connect(m_sendBtn, &QPushButton::clicked, this, &ChatClient::onSendClicked); connect(m_msgEdit, &QLineEdit::returnPressed, this, &ChatClient::onSendClicked); connect(m_socket, &QTcpSocket::connected, this, &ChatClient::onConnected); connect(m_socket, &QTcpSocket::readyRead, this, &ChatClient::onReadyRead); connect(m_socket, &QTcpSocket::disconnected, this, &ChatClient::onDisconnected); } void ChatClient::onConnectClicked() { if (m_nameEdit->text().isEmpty()) { appendMessage("请先输入昵称!"); return; } m_username = m_nameEdit->text(); QString ip = m_ipEdit->text(); quint16 port = m_portEdit->text().toUShort(); m_socket->connectToHost(ip, port); } void ChatClient::onConnected() { appendMessage("已连接服务器"); m_connectBtn->setEnabled(false); m_sendBtn->setEnabled(true); m_nameEdit->setEnabled(false); // 发送用户名 m_socket->write(QString("NAME:%1\n").arg(m_username).toUtf8()); } void ChatClient::onReadyRead() { while (m_socket->canReadLine()) { QString line = QString::fromUtf8(m_socket->readLine()).trimmed(); if (line.startsWith("SYS:")) { appendMessage("【系统】" + line.mid(4)); } else if (line.startsWith("MSG:")) { // MSG:用户名:内容 int idx = line.indexOf(':', 4); if (idx > 0) { QString name = line.mid(4, idx - 4); QString content = line.mid(idx + 1); appendMessage(QString("%1:%2").arg(name).arg(content)); } } } } void ChatClient::onDisconnected() { appendMessage("与服务器断开连接"); m_connectBtn->setEnabled(true); m_sendBtn->setEnabled(false); m_nameEdit->setEnabled(true); } void ChatClient::onSendClicked() { QString msg = m_msgEdit->text(); if (msg.isEmpty()) return; m_socket->write(QString("MSG:%1\n").arg(msg).toUtf8()); m_msgEdit->clear(); } void ChatClient::appendMessage(const QString &text) { m_chatEdit->append(text); }

六、运行效果与扩展思路

6.1 运行步骤

  1. 启动 ChatServer(服务端)
  2. 启动多个 ChatClient,分别输入不同昵称后连接
  3. 任意客户端发送消息,所有客户端都能收到

6.2 关键技术点回顾

  1. 多客户端管理:用QList<QTcpSocket*>保存所有连接
  2. 消息广播:服务端收到消息后转发给所有其他客户端
  3. TCP 粘包处理:使用canReadLine()按行读取,每条消息以\n结尾
  4. sender () 技巧:通过sender()获取哪个 socket 触发了信号

6.3 可以继续扩展的功能

  • 🎯 私聊功能(指定用户发送)
  • 🎯 在线用户列表
  • 🎯 文件传输
  • 🎯 心跳检测(超时断开死连接)
  • 🎯 消息历史记录
  • 🎯 表情包 / 图片发送
  • 🎯 注册登录(结合数据库)

七、总结

本篇我们完成了两部分内容:

HTTP 编程部分:

  • QNetworkAccessManager+QNetworkRequest+QNetworkReply三剑客
  • GET、POST 请求的发送与响应处理
  • 文件下载与进度监听

聊天室实战部分:

  • 基于QTcpServer实现支持多客户端的服务端
  • 基于QTcpSocket实现聊天客户端
  • 自定义简单的文本通信协议
  • 消息广播与 TCP 粘包的简易处理

至此,Qt 网络编程的入门知识就全部讲完了。从 TCP/UDP 底层 Socket 到 HTTP 高层协议,再到聊天室综合项目,相信大家对 Qt 网络开发已经有了完整的认识。

我是白玉同学,Qt 零基础教程系列还在持续更新中。下一篇预告:Qt 多线程编程(QThread + 线程池 + 互斥锁),我们下篇见!

「Qt 零基础教程」系列,代码基于 Qt 6.5 + MinGW 环境。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询