夜雨聆风学习资料网

ARTICLE · 1191299

3天搞定简历项目IM|即时通讯软件开发实战

3天搞定简历项目IM|即时通讯软件开发实战

欢迎来到【Qt开发笔记】微信公众号【知识宝库】!

本系列【Qt C++开发工程师】项目实战 第203期,本期我们聚焦一个兼具实用性与学习深度的主题——👉《3天搞定简历项目IM|即时通讯软件开发实战》。本系列文章旨在为关注Qt跨平台开发、C++后端开发及系统底层架构的开发者,提供一个系统的学习参考。内容将通过原理讲解与实战结合的方式,解析相关核心知识与设计思路,以期对您的技术探索与实践有所裨益。

⭐关注博主后,方可进🐧群895876809下载学习资料📑。

一、【项目测试运行】

1、服务器--启动

2、客户端1--连接服务器

3、客户端2--连接服务器

4、客户端群组管理
5、服务器数据管理
用户管理:
群组管理:
日志管理:

二、【工程项目简介】

1、项目简介

办公即时通信系统(IMSystem)是一套基于 Qt 与 C++ 开发的桌面即时通讯解决方案,包含客户端(OIMClient)与服务端(OIMServer)两部分。支持私聊、群聊、文件传输、消息搜索、会话历史、群组管理、用户管理、审计日志等完整功能,适用于企业内网或办公场景的即时沟通与协作。

主要特点:

-客户端:登录/注册、会话列表、私聊与群聊、在线用户列表、发送文件、搜索消息、创建/加入/退出群组,界面清晰、操作简便。

-服务端:TCP长连接服务、消息队列与批量处理、SQLite持久化、群组/用户/日志管理界面、文件安全扫描、DDoS防护、限流、审计与监控等,便于部署与运维。

-协议:自定义JSON协议,长度前缀帧,支持心跳、重连与多种消息类型,客户端与服务端协议一致,便于扩展。

2、客户端架构(层级(组件):职责)

  • 入口(main.cpp):启动Qt应用,登录成功后创建主窗口并注入 TcpClient

  • 登录(LoginWindow):服务器地址/端口、用户名/密码、登录与注册

  • 主界面(MainWindow):会话列表、消息展示、输入框、在线用户、群组按钮、文件/搜索 

  • 网络(TcpClient):连接、断线重连、心跳、长度前缀帧解析、消息分发(信号) 

  • 协议(protocol.h):MessageType枚举、Protocol组包/解包、各类create/parse 

  • 控件(UserListWidget): 在线用户列表展示与双击发起会话 

3、服务端架构(层级(组件):职责)

  • 入口(main.cpp):启动Qt应用,显示ServerMainWindow 

  • 主界面(ServerMainWindow):启停TCP服务、端口设置、连接/统计展示、管理菜单(群组/用户/日志) 

  • 网络(TCPServer):监听、接受连接、为每个连接创建ClientHandler、消息入队、定时批量处理 

  • 连接(ClientHandler):单连接收包、长度前缀解析、心跳超时、消息上报MessageHandler 

  • 业务(MessageHandler):根据消息类型调用登录、注册、文本、文件、群组、会话、搜索等处理逻辑 

  • 数据(DatabaseManager):SQLite初始化、表/索引、用户/消息/群组/成员/会话/文件等CRUD 

  • 用户(UserManager):认证(本地/外部AuthProvider)、在线列表、上下线、状态广播 

  • 文件(FileManager):上传存储、下载路径、与VirusScanner集成 

  • 安全(VirusScanner):上传文件类型与内容检测;DDoSProtector限连;RateLimiter限流 

  • 日志(Logger / AuditLogger):系统日志与审计日志 

  • 配置(ConfigManager):读写config.ini(端口、HTTP、安全、数据库、日志等) 

4、通信协议概要

  • 传输格式:每条消息为「4字节长度(小端)+ JSON体」。

  • JSON结构:type(消息类型)、data(业务数据)、timestamp。

  • 消息类型:登录/注册、文本、文件、用户列表、用户状态、群组创建/加入/消息/解散/移除成员、心跳、登出、会话列表、聊天记录、搜索、文件上传/下载等(见protocol.h中MessageType枚举)。

5、功能特性

5.1 客户端功能(功能:说明)

  • 登录/注册:配置服务器地址与端口(默认127.0.0.1:8888),用户名密码登录或注册 

  • 会话列表:私聊与群聊会话统一展示,点击切换会话并加载/展示历史消息 

  • 私聊:从在线用户列表双击用户发起私聊,发送文本消息,历史记录持久化 

  • 群聊:创建群组、通过名称下拉加入群组、发送群消息、退出群组(含右键菜单) 

  • 在线用户:实时显示在线用户列表,用户上下线自动更新 

  • 发送文件:工具菜单「发送文件」或按钮,选择文件后上传并通知对方,支持下载 

  • 搜索消息:工具菜单「搜索消息」,按当前会话范围搜索历史消息并展示结果 

  • 系统通知:被移出群组、消息发送失败等以提示或系统通知形式展示 

  • 退出:系统菜单「退出」或关闭窗口时发送登出并断开连接 

5.2 服务端功能(功能:说明)

  • TCP服务:可配置监听端口(默认8888),接受客户端连接,消息队列 + 批量处理 

  • 用户认证:本地数据库认证(密码SHA-256),可扩展AuthProvider外部认证 

  • 消息路由:私聊、群聊消息持久化并转发给对端或群成员 

  • 群组管理:管理菜单「群组管理」:创建/解散群组、更新信息、添加/移除成员 

  • 用户管理:管理菜单「用户管理」:用户列表、添加/删除用户、修改用户名/昵称/密码、搜索 

  • 日志管理:管理菜单「日志管理」:审计日志筛选与导出、系统日志查看 

  • 文件存储:上传文件落盘、病毒扫描(VirusScanner)、下载路径与权限 

  • 安全与限流:DDoS防护(单IP连接限制)、RateLimiter、消息长度上限(如100MB) 

  • 审计与监控:审计日志记录关键操作;监控面板显示连接数、在线数、CPU/内存等(若启用) 

  • HTTP API:可选HTTP服务(默认端口8080),供管理或第三方调用 

  • 配置与备份:config.ini配置;备份管理器定时备份数据库与文件 

三、【工程核心源码】

1、protocol.h
/* * protocol.h * IMSystem 即时通讯系统 - 客户端通信协议定义 * Author: Vico * Description: 消息类型枚举、用户状态枚举及 Protocol 工具类,用于组包与解析 JSON 消息。 */#ifndef PROTOCOL_H#define PROTOCOL_H#include<QString>#include<QJsonObject>#include<QJsonDocument>#include<QJsonArray>#include<QByteArray>#include<QDateTime>// 消息类型枚举enum MessageType {    MSG_LOGIN = 1,              // 登录请求    MSG_LOGIN_RESPONSE = 2,     // 登录响应    MSG_REGISTER = 3,           // 注册请求    MSG_REGISTER_RESPONSE = 4,  // 注册响应    MSG_TEXT = 5,               // 文本消息    MSG_TEXT_RESPONSE = 6,      // 文本消息响应    MSG_FILE = 7,               // 文件消息    MSG_FILE_RESPONSE = 8,      // 文件消息响应    MSG_USER_LIST = 9,          // 用户列表请求    MSG_USER_LIST_RESPONSE = 10,// 用户列表响应    MSG_USER_STATUS = 11,       // 用户状态更新    MSG_GROUP_CREATE = 12,      // 创建群组    MSG_GROUP_CREATE_RESPONSE = 13, // 创建群组响应    MSG_GROUP_JOIN = 14,        // 加入群组    MSG_GROUP_JOIN_RESPONSE = 15,   // 加入群组响应    MSG_GROUP_MESSAGE = 16,     // 群组消息    MSG_GROUP_MESSAGE_RESPONSE = 17, // 群组消息响应    MSG_LOGOUT = 18,            // 登出    MSG_HEARTBEAT = 19,         // 心跳    MSG_HEARTBEAT_RESPONSE = 20, // 心跳响应    MSG_TEMP_SESSION = 21,     // 临时会话    MSG_SYSTEM_NOTIFICATION = 22, // 系统通知    MSG_SEARCH = 23,           // 消息搜索    MSG_SEARCH_RESPONSE = 24,  // 消息搜索响应    MSG_FILE_UPLOAD = 25,      // 文件上传    MSG_FILE_UPLOAD_RESPONSE = 26, // 文件上传响应    MSG_FILE_DOWNLOAD = 27,    // 文件下载    MSG_FILE_DOWNLOAD_RESPONSE = 28, // 文件下载响应    MSG_GROUP_DISSOLVE = 29,   // 解散群组    MSG_GROUP_DISSOLVE_RESPONSE = 30, // 解散群组响应    MSG_GROUP_METADATA = 31,   // 群组元数据更新    MSG_GROUP_METADATA_RESPONSE = 32, // 群组元数据更新响应    MSG_GROUP_REMOVE_MEMBER = 33, // 移除群组成员    MSG_GROUP_REMOVE_MEMBER_RESPONSE = 34, // 移除群组成员响应    MSG_CHAT_SESSIONS = 35,        // 请求聊天会话列表    MSG_CHAT_SESSIONS_RESPONSE = 36, // 聊天会话列表响应    MSG_CHAT_HISTORY = 37,         // 请求聊天记录    MSG_CHAT_HISTORY_RESPONSE = 38, // 聊天记录响应    MSG_GROUP_LIST = 39,            // 请求所有群组列表    MSG_GROUP_LIST_RESPONSE = 40,   // 群组列表响应    MSG_GROUP_MEMBERS = 41,         // 请求群组成员列表    MSG_GROUP_MEMBERS_RESPONSE = 42 // 群组成员列表响应};// 用户状态枚举enum UserStatus {    STATUS_OFFLINE = 0,    STATUS_ONLINE = 1,    STATUS_AWAY = 2,    STATUS_BUSY = 3,    STATUS_INVISIBLE = 4};// 协议工具类class Protocol {public:    // 创建JSON消息    static QByteArray createMessage(MessageType type, const QJsonObject &data){        QJsonObject message;        message["type"] = static_cast<int>(type);        message["data"] = data;        message["timestamp"] = QDateTime::currentDateTime().toSecsSinceEpoch();        QJsonDocument doc(message);        return doc.toJson(QJsonDocument::Compact);    }    // 解析消息    staticboolparseMessage(const QByteArray &data, MessageType &type, QJsonObject &jsonData){        QJsonParseError error;        QJsonDocument doc = QJsonDocument::fromJson(data, &error);        if (error.error != QJsonParseError::NoError) {            return false;        }        QJsonObject obj = doc.object();        type = static_cast<MessageType>(obj["type"].toInt());        jsonData = obj["data"].toObject();        return true;    }    // 创建登录请求    static QByteArray createLoginRequest(const QString &username, const QString &password){        QJsonObject data;        data["username"] = username;        data["password"] = password;        return createMessage(MSG_LOGIN, data);    }    // 创建注册请求    static QByteArray createRegisterRequest(const QString &username, const QString &password,                                            const QString &nickname) {        QJsonObject data;        data["username"] = username;        data["password"] = password;        data["nickname"] = nickname;        return createMessage(MSG_REGISTER, data);    }    // 创建文本消息    static QByteArray createTextMessage(int fromUserId, const QString &fromUsername,                                       int toUserId, const QString &content) {        QJsonObject data;        data["fromUserId"] = fromUserId;        data["fromUsername"] = fromUsername;        data["toUserId"] = toUserId;        data["content"] = content;        return createMessage(MSG_TEXT, data);    }    // 创建群组消息    static QByteArray createGroupMessage(int fromUserId, const QString &fromUsername,                                        int groupId, const QString &content) {        QJsonObject data;        data["fromUserId"] = fromUserId;        data["fromUsername"] = fromUsername;        data["groupId"] = groupId;        data["content"] = content;        return createMessage(MSG_GROUP_MESSAGE, data);    }    // 创建用户列表请求    static QByteArray createUserListRequest(){        QJsonObject data;        return createMessage(MSG_USER_LIST, data);    }    // 创建创建群组请求    static QByteArray createGroupCreateRequest(const QString &groupName, const QString &description = ""){        QJsonObject data;        data["groupName"] = groupName;        data["description"] = description;        return createMessage(MSG_GROUP_CREATE, data);    }    // 创建加入群组请求    static QByteArray createGroupJoinRequest(int groupId){        QJsonObject data;        data["groupId"] = groupId;        return createMessage(MSG_GROUP_JOIN, data);    }    // 创建心跳请求    static QByteArray createHeartbeatRequest(){        QJsonObject data;        data["timestamp"] = QDateTime::currentDateTime().toSecsSinceEpoch();        return createMessage(MSG_HEARTBEAT, data);    }    // 创建登出请求    static QByteArray createLogoutRequest(){        QJsonObject data;        return createMessage(MSG_LOGOUT, data);    }    // 创建文件消息    static QByteArray createFileMessage(int fromUserId, const QString &fromUsername,                                        int toUserId, const QString &fileName,                                         const QString &filePath, qint64 fileSize) {        QJsonObject data;        data["fromUserId"] = fromUserId;        data["fromUsername"] = fromUsername;        data["toUserId"] = toUserId;        data["fileName"] = fileName;        data["filePath"] = filePath;        data["fileSize"] = fileSize;        return createMessage(MSG_FILE, data);    }    // 创建群组文件消息    static QByteArray createGroupFileMessage(int fromUserId, const QString &fromUsername,                                            int groupId, const QString &fileName,                                            const QString &filePath, qint64 fileSize) {        QJsonObject data;        data["fromUserId"] = fromUserId;        data["fromUsername"] = fromUsername;        data["groupId"] = groupId;        data["fileName"] = fileName;        data["filePath"] = filePath;        data["fileSize"] = fileSize;        return createMessage(MSG_FILE, data);    }    // 创建文件上传请求(包含Base64编码的文件数据)    static QByteArray createFileUploadRequest(const QString &fileName, qint64 fileSize,                                               const QString &fileDataBase64,                                              const QString &fileType = "",                                               const QString &mimeType = "") {        QJsonObject data;        data["fileName"] = fileName;        data["fileSize"] = fileSize;        data["fileType"] = fileType;        data["mimeType"] = mimeType;        data["fileData"] = fileDataBase64;        return createMessage(MSG_FILE_UPLOAD, data);    }    // 创建文件下载请求    static QByteArray createFileDownloadRequest(const QString &fileId, const QString &token = ""){        QJsonObject data;        data["fileId"] = fileId;        data["token"] = token;        return createMessage(MSG_FILE_DOWNLOAD, data);    }    // 创建消息搜索请求    static QByteArray createSearchRequest(const QString &keyword, int sessionId = 0,                                          bool isGroup = false, int limit = 50) {        QJsonObject data;        data["keyword"] = keyword;        if (isGroup) {            data["groupId"] = sessionId;        } else if (sessionId > 0) {            data["userId"] = sessionId;        }        data["limit"] = limit;        return createMessage(MSG_SEARCH, data);    }    // 创建临时会话请求    static QByteArray createTempSessionRequest(int targetUserId, const QString &message){        QJsonObject data;        data["targetUserId"] = targetUserId;        data["message"] = message;        return createMessage(MSG_TEMP_SESSION, data);    }    // 创建解散群组请求    static QByteArray createGroupDissolveRequest(int groupId){        QJsonObject data;        data["groupId"] = groupId;        return createMessage(MSG_GROUP_DISSOLVE, data);    }    // 创建群组元数据更新请求    static QByteArray createGroupMetadataRequest(int groupId, const QString &groupName,                                                  const QString &announcement = "") {        QJsonObject data;        data["groupId"] = groupId;        data["groupName"] = groupName;        data["announcement"] = announcement;        return createMessage(MSG_GROUP_METADATA, data);    }    // 创建移除群组成员请求    static QByteArray createGroupRemoveMemberRequest(int groupId, int memberUserId){        QJsonObject data;        data["groupId"] = groupId;        data["memberUserId"] = memberUserId;        return createMessage(MSG_GROUP_REMOVE_MEMBER, data);    }    // 创建群组列表请求    static QByteArray createGroupListRequest(){        QJsonObject data;        return createMessage(MSG_GROUP_LIST, data);    }    // 创建聊天会话列表请求    static QByteArray createChatSessionsRequest(){        QJsonObject data;        return createMessage(MSG_CHAT_SESSIONS, data);    }    // 创建群组成员列表请求    static QByteArray createGroupMembersRequest(int groupId){        QJsonObject data;        data["groupId"] = groupId;        return createMessage(MSG_GROUP_MEMBERS, data);    }    // 创建聊天记录请求(分页:beforeMessageId=0 取最新一页,>0 取该 id 之前的更早消息)    static QByteArray createChatHistoryRequest(int sessionId, bool isGroup, int limit = 50, int beforeMessageId = 0){        QJsonObject data;        data["sessionId"] = sessionId;        data["isGroup"] = isGroup;        data["limit"] = limit;        if (beforeMessageId > 0) {            data["beforeMessageId"] = beforeMessageId;        }        return createMessage(MSG_CHAT_HISTORY, data);    }};#endif// PROTOCOL_H
2、tcpclient.h
/* * tcpclient.h * IMSystem 即时通讯系统 - 客户端 TCP 网络通信头文件 * Author: Vico * Description: TcpClient 类声明,负责与服务器建立连接、收发消息、心跳与自动重连。 */#ifndef TCPCLIENT_H#define TCPCLIENT_H#include <QObject>#include <QTcpSocket>#include <QTimer>#include <QByteArray>#include "protocol.h"class TcpClient : public QObject{    Q_OBJECTpublic:    explicitTcpClient(QObject *parent = nullptr);    ~TcpClient();    // 连接到服务器    voidconnectToServer(const QString &host, quint16 port);    // 断开连接    voiddisconnectFromServer();    // 启用/禁用自动重连    voidsetAutoReconnect(bool enabled);    boolisAutoReconnectEnabled() const { return autoReconnectEnabled; }    // 设置自动重连参数    voidsetReconnectParams(int maxAttempts = 10, int initialDelayMs = 1000, int maxDelayMs = 30000);    // 发送消息    voidsendMessage(const QByteArray &data);    // 检查是否已连接    boolisConnected() const;    // 获取当前用户ID    intgetUserId() const { return userId; }    // 设置用户ID    voidsetUserId(int id) { userId = id; }    // 获取用户名    QString getUsername() const { return username; }    // 设置用户名    voidsetUsername(const QString &name) { username = name; }signals:    // 连接状态信号    voidconnected();    voiddisconnected();    voidconnectionError(const QString &error);    // 自动重连信号    voidreconnecting(int attemptCount, int maxAttempts);    voidreconnectFailed();    voidreconnectSucceeded();    // 消息接收信号    voidmessageReceived(MessageType type, const QJsonObject &data);    // 登录响应信号    voidloginResponse(bool success, const QString &message, int userId, const QString &username);    // 注册响应信号    voidregisterResponse(bool success, const QString &message);    // 文本消息信号    voidtextMessageReceived(int fromUserId, const QString &fromUsername,                             int toUserId, const QString &content);    // 群组消息信号    voidgroupMessageReceived(int fromUserId, const QString &fromUsername,                             int groupId, const QString &content);    // 用户列表响应信号    voiduserListReceived(const QJsonArray &users);    // 用户状态更新信号    voiduserStatusUpdated(int userId, const QString &username, int status);    // 群组创建响应信号    voidgroupCreateResponse(bool success, const QString &message, int groupId, const QString &groupName);    // 群组加入响应信号    voidgroupJoinResponse(bool success, const QString &message, int groupId, const QString &groupName);    // 文件消息信号    voidfileMessageReceived(int fromUserId, const QString &fromUsername,                            int toUserId, const QString &fileName,                             const QString &filePath, qint64 fileSize);    // 文件上传响应信号    voidfileUploadResponse(bool success, const QString &message, int fileId, const QString &filePath);    // 文件下载响应信号    voidfileDownloadResponse(bool success, const QString &message, const QString &filePath);    // 消息搜索响应信号    voidsearchResponse(bool success, const QJsonArray &results);    // 系统通知信号    voidsystemNotificationReceived(const QString &title, const QString &content, int type);    // 群组解散响应信号    voidgroupDissolveResponse(bool success, const QString &message, int groupId);    // 群组元数据更新响应信号    voidgroupMetadataResponse(bool success, const QString &message, int groupId, const QString &groupName);    // 移除群组成员响应信号    voidgroupRemoveMemberResponse(bool success, const QString &message, int groupId, int memberUserId);    // 消息发送失败信号    voidmessageSendFailed(const QString &reason);    // 群组列表响应信号    voidgroupListReceived(const QJsonArray &groups);    // 聊天会话列表响应信号    voidchatSessionsReceived(const QJsonArray &sessions);    // 聊天记录响应信号(hasMore: 是否还有更早消息;beforeMessageId: 本次请求的游标,0 表示首次加载)    voidchatHistoryReceived(int sessionId, bool isGroup, const QJsonArray &messages, bool hasMore, int beforeMessageId);    // 群组成员列表响应信号    voidgroupMembersReceived(int groupId, const QJsonArray &members);private slots:    voidonConnected();    voidonDisconnected();    voidonReadyRead();    voidonError(QAbstractSocket::SocketError error);    voidonHeartbeatTimeout();    voidattemptReconnect();private:    QTcpSocket *socket;    QTimer *heartbeatTimer;    QTimer *reconnectTimer;    QByteArray buffer;    int userId;    QString username;    // 自动重连相关    bool autoReconnectEnabled;    bool isManualDisconnect;    QString reconnectHost;    quint16 reconnectPort;    int reconnectAttemptCount;    int maxReconnectAttempts;    int reconnectDelayMs;    int maxReconnectDelayMs;    voidprocessBuffer();    voidstartHeartbeat();    voidstopHeartbeat();    voidstartReconnectTimer();    voidstopReconnectTimer();    intcalculateReconnectDelay();};#endif // TCPCLIENT_H
3、tcpclient.cpp
/* * tcpclient.cpp * IMSystem 即时通讯系统 - 客户端 TCP 网络通信实现 * Author: Vico * Description: 连接管理、长度前缀帧解析、消息分发、心跳与断线重连逻辑。 */#include"tcpclient.h"#include<QDebug>#include<QJsonDocument>#include<QJsonObject>#include<QJsonParseError>#include<QFileDialog>#include<QFile>#include<QFileInfo>#include<QTimer>TcpClient::TcpClient(QObject *parent)    : QObject(parent)    , socket(nullptr)    , heartbeatTimer(nullptr)    , reconnectTimer(nullptr)    , userId(0)    , autoReconnectEnabled(true)    , isManualDisconnect(false)    , reconnectHost("")    , reconnectPort(0)    , reconnectAttemptCount(0)    , maxReconnectAttempts(10)    , reconnectDelayMs(1000)    , maxReconnectDelayMs(30000){    socket = new QTcpSocket(this);    connect(socket, &QTcpSocket::connected, this, &TcpClient::onConnected);    connect(socket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected);    connect(socket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead);    connect(socket, &QAbstractSocket::errorOccurred, this, &TcpClient::onError);    heartbeatTimer = new QTimer(this);    heartbeatTimer->setInterval(15000); // 15秒心跳,确保在服务器30秒超时之前发送    connect(heartbeatTimer, &QTimer::timeout, this, &TcpClient::onHeartbeatTimeout);    reconnectTimer = new QTimer(this);    reconnectTimer->setSingleShot(true);    connect(reconnectTimer, &QTimer::timeout, this, &TcpClient::attemptReconnect);}TcpClient::~TcpClient(){    disconnectFromServer();}voidTcpClient::connectToServer(const QString &host, quint16 port){    // 保存服务器信息用于自动重连    reconnectHost = host;    reconnectPort = port;    isManualDisconnect = false;    reconnectAttemptCount = 0;    if (socket->state() == QAbstractSocket::ConnectedState) {        socket->disconnectFromHost();    }    stopReconnectTimer(); // 停止任何正在进行的重连尝试    socket->connectToHost(host, port);}voidTcpClient::disconnectFromServer(){    isManualDisconnect = true; // 标记为手动断开    stopHeartbeat();    stopReconnectTimer(); // 停止自动重连    if (socket->state() == QAbstractSocket::ConnectedState) {        socket->disconnectFromHost();    }}voidTcpClient::sendMessage(const QByteArray &data){    if (!socket || socket->state() != QAbstractSocket::ConnectedState) return;    quint32 size = data.size();    socket->write(reinterpret_cast<const char*>(&size), sizeof(size));    // 大消息分块写入,避免一次性写入过大数据导致阻塞    const int CHUNK_SIZE = 64 * 1024; // 64KB chunks    qint64 offset = 0;    qint64 remaining = data.size();    while (remaining > 0) {        qint64 toWrite = qMin(static_cast<qint64>(CHUNK_SIZE), remaining);        qint64 written = socket->write(data.constData() + offset, toWrite);        if (written <= 0) break;        offset += written;        remaining -= written;    }    socket->flush();}boolTcpClient::isConnected()const{    return socket->state() == QAbstractSocket::ConnectedState;}voidTcpClient::onConnected(){    qDebug() << "已连接到服务器";    // 重置重连状态    bool wasReconnecting = (reconnectAttemptCount > 0);    reconnectAttemptCount = 0;    isManualDisconnect = false;    stopReconnectTimer();    // 立即启动心跳定时器,确保连接保持    // 心跳间隔为15秒,确保在服务器30秒超时之前发送    // startHeartbeat()内部会发送初始心跳,这里不需要重复发送    startHeartbeat();    // 如果是重连成功,发送重连成功信号    if (wasReconnecting) {        emit reconnectSucceeded();    }    emit connected();}voidTcpClient::onDisconnected(){    qDebug() << "与服务器断开连接";    stopHeartbeat();    // 如果不是手动断开,且启用了自动重连,则尝试重连    if (!isManualDisconnect && autoReconnectEnabled && !reconnectHost.isEmpty() && reconnectPort > 0) {        qDebug() << "开始自动重连...";        startReconnectTimer();    } else {        // 手动断开或禁用自动重连,清除用户信息        userId = 0;        username.clear();    }    emit disconnected();}voidTcpClient::onReadyRead(){    buffer.append(socket->readAll());    processBuffer();}voidTcpClient::onError(QAbstractSocket::SocketError error){    QString errorString = socket->errorString();    qDebug() << "连接错误:" << errorString << "错误代码:" << error;    // 如果连接失败且不是手动断开,尝试重连    if (error != QAbstractSocket::RemoteHostClosedError &&         !isManualDisconnect &&         autoReconnectEnabled &&         !reconnectHost.isEmpty() &&         reconnectPort > 0) {        // 连接错误,延迟后重试        if (socket->state() != QAbstractSocket::ConnectedState) {            startReconnectTimer();        }    }    emit connectionError(errorString);}voidTcpClient::onHeartbeatTimeout(){    if (isConnected()) {        sendMessage(Protocol::createHeartbeatRequest());    }}voidTcpClient::processBuffer(){    const quint32 MAX_MESSAGE_SIZE = 100 * 1024 * 1024; // 100MB    while (buffer.size() >= static_cast<int>(sizeof(quint32))) {        quint32 messageSize;        memcpy(&messageSize, buffer.constData(), sizeof(quint32));        if (messageSize > MAX_MESSAGE_SIZE) {            qDebug() << "TcpClient: 消息大小异常,清空缓冲区:" << messageSize;            buffer.clear();            break;        }        if (buffer.size() < static_cast<int>(sizeof(quint32) + messageSize)) {            break;        }        QByteArray messageData = buffer.mid(sizeof(quint32), messageSize);        buffer.remove(0, sizeof(quint32) + messageSize);        // 解析消息        MessageType type;        QJsonObject jsonData;        if (Protocol::parseMessage(messageData, type, jsonData)) {            emit messageReceived(type, jsonData);            // 根据消息类型发送特定信号            switch (type) {            case MSG_LOGIN_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int id = jsonData["userId"].toInt();                QString name = jsonData["username"].toString();                emit loginResponse(success, message, id, name);                break;            }            case MSG_REGISTER_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                emit registerResponse(success, message);                break;            }            case MSG_TEXT: {                int fromUserId = jsonData["fromUserId"].toInt();                QString fromUsername = jsonData["fromUsername"].toString();                int toUserId = jsonData["toUserId"].toInt();                QString content = jsonData["content"].toString();                emit textMessageReceived(fromUserId, fromUsername, toUserId, content);                break;            }            case MSG_TEXT_RESPONSE: {                if (jsonData.contains("success") && !jsonData["success"].toBool()) {                    QString message = jsonData["message"].toString();                    emit messageSendFailed(message);                }                break;            }            case MSG_GROUP_MESSAGE: {                int fromUserId = jsonData["fromUserId"].toInt();                QString fromUsername = jsonData["fromUsername"].toString();                int groupId = jsonData["groupId"].toInt();                QString content = jsonData["content"].toString();                emit groupMessageReceived(fromUserId, fromUsername, groupId, content);                break;            }            case MSG_GROUP_MESSAGE_RESPONSE: {                if (jsonData.contains("success") && !jsonData["success"].toBool()) {                    QString message = jsonData["message"].toString();                    emit messageSendFailed(message);                }                break;            }            case MSG_USER_LIST_RESPONSE: {                QJsonArray users = jsonData["users"].toArray();                emit userListReceived(users);                break;            }            case MSG_USER_STATUS: {                int id = jsonData["userId"].toInt();                QString name = jsonData["username"].toString();                int status = jsonData["status"].toInt();                emit userStatusUpdated(id, name, status);                break;            }            case MSG_GROUP_CREATE_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int groupId = jsonData["groupId"].toInt();                QString groupName = jsonData["groupName"].toString();                emit groupCreateResponse(success, message, groupId, groupName);                break;            }            case MSG_GROUP_JOIN_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int groupId = jsonData["groupId"].toInt();                QString groupName = jsonData["groupName"].toString();                emit groupJoinResponse(success, message, groupId, groupName);                break;            }            case MSG_FILE:            case MSG_FILE_RESPONSE: {                int fromUserId = jsonData["fromUserId"].toInt();                QString fromUsername = jsonData["fromUsername"].toString();                // 文件消息可能是单聊或群组                int toUserId = jsonData.contains("toUserId") ? jsonData["toUserId"].toInt() : 0;                int groupId = jsonData.contains("groupId") ? jsonData["groupId"].toInt() : 0;                QString fileName = jsonData["fileName"].toString();                QString filePath = jsonData["filePath"].toString();                qint64 fileSize = jsonData["fileSize"].toVariant().toLongLong();                // 如果是群组文件,使用groupId,否则使用toUserId                // 为了区分,如果是群组文件,toUserId设为负数(-groupId)                if (groupId > 0) {                    toUserId = -groupId; // 负数表示群组文件                }                emit fileMessageReceived(fromUserId, fromUsername, toUserId, fileName, filePath, fileSize);                break;            }            case MSG_FILE_UPLOAD_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                QString fileId = jsonData["fileId"].toString(); // fileId可能是字符串                QString fileName = jsonData.contains("fileName") ? jsonData["fileName"].toString() : "";                qint64 fileSize = jsonData.contains("fileSize") ? jsonData["fileSize"].toVariant().toLongLong() : 0;                QString filePath = jsonData.contains("filePath") ? jsonData["filePath"].toString() : fileId;                int fileIdInt = jsonData.contains("fileId") && jsonData["fileId"].isDouble()                                ? jsonData["fileId"].toInt() : 0;                // 将文件信息编码到filePath字段(格式:fileId|fileSize|fileName)                if (!fileId.isEmpty()) {                    filePath = QString("%1|%2|%3").arg(fileId).arg(fileSize).arg(fileName);                }                emit fileUploadResponse(success, message, fileIdInt, filePath);                break;            }            case MSG_FILE_DOWNLOAD_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                QString fileName = jsonData.contains("fileName") ? jsonData["fileName"].toString() : "";                QString filePath = jsonData.contains("filePath") ? jsonData["filePath"].toString() :                                   jsonData.contains("fileId") ? jsonData["fileId"].toString() : "";                QString fileDataBase64 = jsonData.contains("fileData") ? jsonData["fileData"].toString() : "";                // 如果响应中包含文件数据,保存文件                if (success && !fileDataBase64.isEmpty()) {                    QString defaultFileName = fileName.isEmpty() ? (filePath.isEmpty() ? "download" : QFileInfo(filePath).fileName()) : fileName;                    QString savePath = QFileDialog::getSaveFileName(nullptr, "保存文件", defaultFileName);                    if (!savePath.isEmpty()) {                        QByteArray fileData = QByteArray::fromBase64(fileDataBase64.toUtf8());                        QFile file(savePath);                        if (file.open(QIODevice::WriteOnly)) {                            file.write(fileData);                            file.close();                            message = QString("文件已保存到: %1").arg(savePath);                        } else {                            message = "保存文件失败: " + file.errorString();                            success = false;                        }                    } else {                        message = "用户取消了保存";                    }                }                emit fileDownloadResponse(success, message, filePath.isEmpty() ? fileName : filePath);                break;            }            case MSG_SEARCH_RESPONSE: {                bool success = jsonData["success"].toBool();                QJsonArray results = jsonData["messages"].toArray();                emit searchResponse(success, results);                break;            }            case MSG_SYSTEM_NOTIFICATION: {                QString title = jsonData["title"].toString();                QString content = jsonData["content"].toString();                int type = jsonData["type"].toInt();                emit systemNotificationReceived(title, content, type);                break;            }            case MSG_GROUP_DISSOLVE_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int groupId = jsonData["groupId"].toInt();                emit groupDissolveResponse(success, message, groupId);                break;            }            case MSG_GROUP_METADATA_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int groupId = jsonData["groupId"].toInt();                QString groupName = jsonData["groupName"].toString();                emit groupMetadataResponse(success, message, groupId, groupName);                break;            }            case MSG_GROUP_REMOVE_MEMBER_RESPONSE: {                bool success = jsonData["success"].toBool();                QString message = jsonData["message"].toString();                int groupId = jsonData["groupId"].toInt();                int memberUserId = jsonData["memberUserId"].toInt();                emit groupRemoveMemberResponse(success, message, groupId, memberUserId);                break;            }            case MSG_GROUP_LIST_RESPONSE: {                QJsonArray groups = jsonData["groups"].toArray();                emit groupListReceived(groups);                break;            }            case MSG_CHAT_SESSIONS_RESPONSE: {                QJsonArray sessions = jsonData["sessions"].toArray();                emit chatSessionsReceived(sessions);                break;            }            case MSG_CHAT_HISTORY_RESPONSE: {                int sessionId = jsonData["sessionId"].toInt();                bool isGroup = jsonData["isGroup"].toBool();                QJsonArray messages = jsonData["messages"].toArray();                bool hasMore = jsonData["hasMore"].toBool(false);                int beforeMessageId = jsonData["beforeMessageId"].toInt(0);                emit chatHistoryReceived(sessionId, isGroup, messages, hasMore, beforeMessageId);                break;            }            case MSG_GROUP_MEMBERS_RESPONSE: {                int groupId = jsonData["groupId"].toInt();                QJsonArray members = jsonData["members"].toArray();                emit groupMembersReceived(groupId, members);                break;            }            default:                break;            }        }    }}voidTcpClient::startHeartbeat(){    if (!heartbeatTimer || !isConnected()) return;    if (!heartbeatTimer->isActive()) {        heartbeatTimer->start();        QTimer::singleShot(100, this, [this]() {            if (isConnected()) {                sendMessage(Protocol::createHeartbeatRequest());            }        });    }}voidTcpClient::stopHeartbeat(){    if (heartbeatTimer && heartbeatTimer->isActive()) {        heartbeatTimer->stop();    }}voidTcpClient::setAutoReconnect(bool enabled){    autoReconnectEnabled = enabled;    if (!enabled) {        stopReconnectTimer();    }}voidTcpClient::setReconnectParams(int maxAttempts, int initialDelayMs, int maxDelayMs){    maxReconnectAttempts = maxAttempts;    reconnectDelayMs = initialDelayMs;    maxReconnectDelayMs = maxDelayMs;}voidTcpClient::startReconnectTimer(){    if (isManualDisconnect || !autoReconnectEnabled || reconnectHost.isEmpty() || reconnectPort == 0) {        return;    }    if (reconnectAttemptCount >= maxReconnectAttempts) {        qDebug() << "已达到最大重连次数,停止重连";        emit reconnectFailed();        return;    }    reconnectAttemptCount++;    int delay = calculateReconnectDelay();    qDebug() << QString("将在 %1 毫秒后进行第 %2/%3 次重连尝试")                .arg(delay).arg(reconnectAttemptCount).arg(maxReconnectAttempts);    emit reconnecting(reconnectAttemptCount, maxReconnectAttempts);    reconnectTimer->start(delay);}voidTcpClient::stopReconnectTimer(){    if (reconnectTimer && reconnectTimer->isActive()) {        reconnectTimer->stop();    }}voidTcpClient::attemptReconnect(){    if (isManualDisconnect || !autoReconnectEnabled || reconnectHost.isEmpty() || reconnectPort == 0) {        return;    }    if (socket->state() == QAbstractSocket::ConnectedState) {        qDebug() << "已连接,无需重连";        return;    }    qDebug() << QString("尝试重连到服务器 %1:%2 (第 %3/%4 次)")                .arg(reconnectHost).arg(reconnectPort)                .arg(reconnectAttemptCount).arg(maxReconnectAttempts);    socket->connectToHost(reconnectHost, reconnectPort);    // 如果连接失败,会在onError中再次触发重连定时器}intTcpClient::calculateReconnectDelay(){    // 指数退避算法:delay = min(initialDelay * 2^(attempt-1), maxDelay)    int delay = reconnectDelayMs * (1 << (reconnectAttemptCount - 1));    return qMin(delay, maxReconnectDelayMs);}
希望这篇文章能为您的C++/Qt开发等学习提供有价值的参考!感谢您的关注👉【Qt开发笔记】微信公众号,获取更多技术干货。

相关学习资料