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

欢迎来到【Qt开发笔记】微信公众号【知识宝库】!
⭐关注博主后,方可进🐧群895876809下载学习资料📑。
一、【项目测试运行】
1、服务器--启动

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


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











二、【工程项目简介】
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配置;备份管理器定时备份数据库与文件
三、【工程核心源码】
/** 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
/** 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;// 获取当前用户IDintgetUserId() const { return userId; }// 设置用户IDvoidsetUserId(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
/** 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 chunksqint64 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; // 100MBwhile (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);}















































































