设计目标
- 即时通讯体验:实现类似微信的桌面即时通讯客户端与配套服务端,支持账号注册、登录、添加好友、单聊及离线消息等基础 IM 功能。
- 良好用户体验:基于 Qt 自绘 UI,提供无边框窗口、阴影、圆角、拖拽移动窗口、平滑滚动条、点击动画、侧边栏按钮反馈等,使桌面端界面更现代、美观。
- 稳定可靠的连接:客户端通过
QTcpSocket与服务器通信,支持自动重连、心跳检测、错误信息提示、断线恢复等,保证在普通网络条件下的稳定性。 - 可维护的数据管理:服务端以 JSON 文件持久化用户数据(账号、密码、联系人、聊天记录、好友申请、离线消息等),简化部署的同时便于调试和扩展。
- 可扩展的架构:客户端和服务端都采用模块化设计(网络层 / UI 层、ServerManager / ClientManager / MessageHandler / HeartbeatMonitor 等),为后续扩展群聊、文件/图片消息、更多资料字段等功能预留空间。
功能描述
客户端(wechat)
-
账号注册
- 通过
registerwindow完成账号注册,校验账号、密码长度与两次输入一致性,强制勾选隐私政策。 - 注册成功后,将昵称等基本资料写入本地
userdata/profile/<username>.json,并通过信号告知登录界面自动回填账号密码。
- 通过
-
登录与会话管理
LoginWindow负责登录界面展示与交互,校验用户名/密码、用户协议勾选状态。- 在需要时自动连接到服务端(默认
localhost:8888),显示连接状态和错误信息。 - 登录流程中带有“至少等待一定时间”的加载逻辑,避免瞬间闪过造成体验生硬。
- 登录成功后通过信号通知
main.cpp,隐藏登录窗口并打开chatwindow。
-
网络通信与会话恢复
NetworkManager负责所有与服务端的 TCP 通信与 JSON 协议封装。- 支持:
- 连接 / 断开服务器、错误处理。
- 登录 / 注册请求与结果反馈。
- 获取联系人列表、添加/删除联系人。
- 好友申请的发送、接受/拒绝、列表获取。
- 单聊消息发送、聊天记录获取。
- 离线消息接收。
- 资料更新广播(昵称等)。
- 实现自动重连机制(指数退避、最大重连次数),在网络中断后尽量恢复连接并自动重新登录。
- 所有消息以一行一条 JSON 的形式通过
\n分隔,便于流式处理。
-
主聊天界面与联系人管理
chatwindow为主工作界面,包含:- 左侧联系人列表(自定义
contactwidget,呈现头像、昵称、最后一条消息、时间与未读数)。 - 右侧消息列表(发送气泡
sendwidget、接收气泡recvwidget)。 - 底部输入框与发送按钮,支持 Enter 发送、Shift+Enter 换行。
- 侧边工具栏(头像按钮、聊天/通讯录/设置等图标按钮),带有点击动画与高亮状态。
- 左侧联系人列表(自定义
- 支持:
- 添加好友(通过
AddContactDialog发送好友申请)。 - 接收好友申请与红点提醒(
FriendRequestDialog+ badge)。 - 查看联系人资料、删除联系人、删除本地聊天记录。
- 本地头像管理与圆角裁剪显示(侧边栏头像、联系人头像、消息气泡头像)。
- 根据本地 profile 与服务器昵称组合确定显示名。
- 添加好友(通过
- 对滚动条、布局进行了大量细节优化(按像素滚动、自动隐藏滚动条、最大化/还原后强制刷新布局)以保证消息列表在各种尺寸下表现正常。
-
UI/交互优化
- 所有主要窗口(
MainWindow、LoginWindow、registerwindow、chatwindow)均采用无边框 + 透明背景 + 自定义拖拽逻辑。 - 使用
QPropertyAnimation为contactwidget点击提供缩放动画,强化交互反馈。 - 通过事件过滤器、拖拽事件(拖入图片/文件时给出提示)等提升易用性。
- 所有主要窗口(
服务端(wechat_server)
-
服务启动/停止与状态监控
MainWindow提供服务端 GUI:- 设置监听端口、启动/停止服务器。
- 显示在线客户端列表与在线人数。
- 实时显示日志(颜色区分 INFO/WARNING/ERROR),支持清空。
- 广播消息发送(服务端向所有在线客户端发送系统消息)。
- 通过自定义无边框窗口实现拖拽、最小化/最大化/关闭等逻辑。
-
连接与会话管理
ServerManager:- 内部持有
QTcpServer,统一管理监听端口和新连接。 - 管理
ClientManager、MessageHandler、HeartbeatMonitor三大核心模块。 - 处理新客户端连接/断开、维护 socket 缓冲区,并将带换行的 JSON 消息分包交给
MessageHandler。 - 负责启动/停止心跳检测及资源清理。
- 内部持有
-
客户端与用户数据管理
ClientManager:- 管理在线客户端(
ClientInfo):用户名、socket、IP、端口、登录时间、最后心跳时间、在线状态。 - 管理持久化的用户数据(
UserData):用户名、密码、昵称、注册时间、联系人列表、聊天记录、好友申请、离线消息、每个联系人的最后一条消息等。 - 用户数据以 JSON 文件形式存储于
userdata/<username>.json。 - 功能包括:
- 用户注册 / 登录验证。
- 联系人增删与双向同步。
- 聊天记录保存与裁剪(限制最大条数)。
- 离线消息队列管理与清空。
- 好友申请增删查、状态流转(pending/accepted/rejected)。
- 资料更新(昵称)与统计信息(聊天记录数量、未处理好友申请数量等)。
- 管理员操作:删除用户、清空聊天记录、修改密码等。
- 管理在线客户端(
-
消息分发与业务协议
MessageHandler负责解析并处理所有来自客户端的 JSON 消息:- 登录
login:校验账号、密码;调用ClientManager::addClient维护在线表;返回联系人列表,并推送用户的离线消息;广播userOnline事件。 - 注册
register:创建用户数据文件;校验用户名重复与昵称非空等。 - 单聊消息
message:- 保存发送方的聊天记录。
- 接收方在线:即时推送 + 保存接收方聊天记录。
- 接收方离线:写入离线消息 + 保存接收方聊天记录。
- 心跳
heartbeat:更新最后心跳时间并回复心跳确认。 - 获取联系人
getContacts:将联系人用户名、昵称、在线状态及最后一条消息封装为数组返回。 - 获取聊天记录
getChatHistory:返回指定会话的全部历史消息。 - 添加/删除联系人
addContact/removeContact:更新双方联系人列表,同时在删除时向对方推送contactRemoved通知。 - 好友申请相关:
friendRequest、acceptFriendRequest、rejectFriendRequest、getFriendRequests,涵盖申请记录、互加好友、自动发送系统消息 “我通过了你的朋友验证请求,现在我们可以开始聊天了”、离线/在线通知等。 - 更新资料
updateProfile:更新服务器端昵称并将profileUpdated广播给所有在线客户端。 - 测试/帮助消息:处理简单
test/ping/测试文本命令及错误 JSON 时的帮助提示。
- 登录
-
心跳与超时管理
HeartbeatMonitor(通过ServerManager持有)周期性检查每个客户端的最后心跳时间。- 当客户端超过设定超时时间未发送心跳时,触发超时信号并断开该连接,确保长时间无响应连接被清理。
-
日志与用户管理 UI
Logger:单例日志模块,统一记录 Info/Warning/Error 日志,并向主界面发送信号以在文本框中展示。UserManagerDialog:- 可视化展示所有注册用户。
- 显示昵称、注册时间、在线状态、联系人数量、聊天记录数量等信息。
- 支持管理员删除用户、清空用户聊天记录、修改用户密码。
设计方案
通信与协议设计
- 传输层:基于
QTcpSocket/QTcpServer的长期 TCP 连接,适合实时消息推送。 - 应用层协议:
- 消息体为 UTF-8 编码 JSON 文本,每条消息一行,以
\n为分隔符。 - 统一以
type字段区分消息类型,部分消息还包含success、message、data等字段。 - 这种设计利于调试(可直接查看原始 JSON)和扩展(新增 type 即可)。
- 消息体为 UTF-8 编码 JSON 文本,每条消息一行,以
客户端整体设计
- 单一
NetworkManager实例共享:在main.cpp中创建NetworkManager,并传入登录窗、注册窗、聊天窗构造函数,使所有 UI 层统一复用同一网络连接与状态。 - UI 与业务解耦:
- UI 层(多个
QWidget+QDialog)只关心信号/槽接口,如loginSuccess、contactsUpdated、chatMessageReceived等。 - 具体网络细节(重连、解析 JSON 等)完全封装在
NetworkManager中。
- UI 层(多个
- 本地缓存与远端数据混合使用:
- 本地
userdata/profile储存昵称、省份、头像等与服务器端 JSON 用户数据互为补充。 chatwindow在绘制联系人列表时,优先使用本地 profile 的昵称,其次服务器返回的昵称,最后使用账号名。
- 本地
服务端整体设计
- 核心三层模块:
ServerManager:对外提供“启动/停止服务器”的统一接口,同时持有其他核心组件。ClientManager:负责“谁在线”和“用户是什么样”的问题;既维护在线客户端,又管理落盘用户数据。MessageHandler:负责“收到消息要做什么”的问题;将所有业务逻辑放在一个集中位置,易于维护协议演进。
- 数据持久化方案:
- 每个用户一个 JSON 文件,字段包含账号、密码、昵称、注册时间、联系人列表、聊天记录、好友申请、离线消息、与各联系人的最后一条消息。
- 通过批量加载 +
QApplication::processEvents防止一次性读取过多文件卡顿 UI。
- 管理员界面:
- 借助
UserManagerDialog提供最基础的运维功能,简化调试与测试。
- 借助
系统框架
客户端模块划分
-
入口与窗口切换
main.cpp:创建QApplication、NetworkManager、LoginWindow、registerwindow、chatwindow,并通过 3D 翻转动画实现登录/注册切换。
-
网络层
NetworkManager:- 维护
QTcpSocket与断线重连定时器。 - 暴露高层接口:
login/registerUser/fetchContacts/sendChatMessage/requestFriend/updateProfile等。 - 通过
handleMessage将 JSON 消息映射到 Qt 信号,供 UI 层订阅。
- 维护
-
登录注册层
LoginWindow:- 负责登录表单、错误提示、与服务器连接状态显示。
- 触发
openRegisterRequested、loginSuccess等信号。
registerwindow:- 实现注册表单校验、注册逻辑、注册成功后本地 profile 写入、返回登录界面等。
-
聊天与联系人管理
chatwindow:- 管理联系人内存结构(
ChatContact+QMap/QHash),以及对应的contactwidget实例映射。 - 负责聊天消息列表、输入框交互、联系人右键菜单(查看资料 / 删除联系人 / 删除聊天记录)。
- 管理好友申请红点、侧边栏按钮动画、窗口状态切换下的布局刷新的细节。
- 管理联系人内存结构(
contactwidget/sendwidget/recvwidget:- 以自定义控件形式封装单个联系人条目和单条消息气泡,统一控制样式与动画。
-
资料与对话框
ProfileDialog:编辑个人资料(昵称、省份、头像),并与NetworkManager协作,将修改同步到服务器和联系人列表。FriendRequestDialog:展示待处理好友申请列表,触发“接受/拒绝”操作。AddContactDialog:输入好友账号,触发requestFriend。
服务端模块划分
-
入口与主界面
main.cpp:创建QApplication,显示服务端MainWindow。MainWindow:负责服务控制 UI、日志显示、在线客户端列表、广播消息、用户管理入口等。
-
连接与业务逻辑核心
ServerManager:- 管理
QTcpServer与新连接。 - 持有
ClientManager、MessageHandler、HeartbeatMonitor。 - 内部维护每个 socket 的读缓冲区,负责基于行的消息拆包。
- 管理
ClientManager:- 维护在线客户端映射(
username -> ClientInfo),以及所有用户持久化数据(username -> UserData)。 - 提供一系列用于
MessageHandler的业务方法(注册、登录验证、联系人管理、聊天记录和离线消息管理、好友申请状态管理等)。
- 维护在线客户端映射(
MessageHandler:- 将所有 JSON 消息按
type分派到对应处理函数。 - 在内部调用
ClientManager的接口、通过sendMessage给客户端回复或广播。
- 将所有 JSON 消息按
HeartbeatMonitor:- 周期性检查
ClientInfo::lastHeartbeat,对超时客户端断连。
- 周期性检查
-
日志与管理员工具
Logger:集中记录运行时事件,并通过信号连接到MainWindow的日志文本框。UserManagerDialog:管理员级别操作界面,操作ClientManager中的数据。
实现过程概要
-
基础通信与简单 UI
- 首先搭建 Qt 客户端和服务端工程,完成 TCP 连接测试(如
test/ping命令)。 - 实现最小可用的登录/注册流程与基础聊天功能。
- 首先搭建 Qt 客户端和服务端工程,完成 TCP 连接测试(如
-
联系人与聊天记录管理
- 在服务端引入
ClientManager,将用户数据以 JSON 文件落盘。 - 按用户名维度维护联系人列表与聊天记录,客户端则实现联系人列表 UI 与聊天窗口。
- 在服务端引入
-
完善协议与用户体验
- 扩展 JSON 协议:添加
getContacts、getChatHistory、addContact、removeContact等类型。 - 在客户端实现
NetworkManager::handleMessage中对各类消息的解析与 UI 信号派发。 - 调整 UI:无边框窗口、拖拽、圆角、阴影、按钮样式等。
- 扩展 JSON 协议:添加
-
好友申请与资料系统
- 设计并实现好友申请流程(发送、接受、拒绝、查询),在服务端记录
friendRequests。 - 客户端增加好友申请对话框、红点提醒,服务端通过广播/推送消息同步状态。
- 引入
ProfileDialog与本地 profile/头像文件,增强个人与联系人显示效果。
- 设计并实现好友申请流程(发送、接受、拒绝、查询),在服务端记录
-
稳定性与运维能力提升
- 客户端增加断线自动重连与登录状态恢复逻辑;服务端增加
HeartbeatMonitor。 - 引入统一
Logger以及颜色区分日志级别,便于问题排查。 - 增加
UserManagerDialog,以 GUI 形式管理用户与聊天记录,辅助测试与维护。
- 客户端增加断线自动重连与登录状态恢复逻辑;服务端增加
心得体会与改进思考
-
1)Qt 在桌面 IM 客户端中的优势
- 借助 Qt 丰富的 UI 控件与信号/槽机制,可以较容易构建出跨平台的 IM 客户端界面。
- 通过自绘与样式表,能做出比较接近原生微信的窗口风格(无边框、圆角、阴影、侧边栏图标等),提升用户观感。
-
2)前后端协议清晰是演进的关键
- 使用 JSON +
type字段的协议设计,结构清晰、易于调试,后续功能扩展(比如群聊、文件消息、@提醒)只需增加新的 type 与字段。 - 客户端
NetworkManager与服务端MessageHandler分别承担“协议封装/解析”,使 UI 与数据层关系自然清晰。
- 使用 JSON +
-
3)长连接与可靠性的实践
- 在客户端加入自动重连、心跳响应、错误文案提示,使得在不稳定网络下体验更平滑。
- 服务端通过
HeartbeatMonitor清理超时连接,并在ServerManager中谨慎处理 socket 状态、缓冲区与异常情况,有利于线上稳定性。
-
4)文件型持久化的利与弊
- 单用户单 JSON 文件的方式非常适合教学与个人项目:部署简单、可直接查看和编辑用户数据。
- 当用户量或消息量增大时,JSON 读写与锁粒度需要进一步优化,甚至迁移到数据库(如 SQLite/MySQL)更合理。
-
5)用户体验细节的重要性
- 在登录/注册中加入“最少等待时间”、友好错误提示、勾选协议校验,使流程看起来更专业。
- 对滚动条按像素滚动、自动隐藏、窗口大小变化时强制刷新布局等细节处理,让界面在多种分辨率下更自然。
- 点击动画、按钮 hover/pressed 状态等微交互,对整体观感提升很大。
-
6)后续可改进与扩展方向
- 安全性:目前密码以明文存储在 JSON 中,后续应改为哈希(如 PBKDF2/bcrypt)并考虑 TLS 加密传输。
- 消息类型扩展:图片/文件消息目前已有拖拽与剪贴板检测的基础,可以在协议层增加对应字段并在服务端做文件转发或 URL 分发。
- 多终端同步:当前离线消息与资料更新已具备基本能力,可进一步扩展为多终端同时在线的状态同步策略。
- 性能与架构升级:在用户规模增大时,引入数据库、分层服务(认证/消息/推送)甚至拆分为微服务,都有清晰的演进路径。
UI界面展示
登录界面

注册界面

聊天界面





服务端界面



一些细节控件的问题解决:
实现只在滚动时出现滚动条:
QSS 只能控制长什么样,不能“何时显示/隐藏”。
“滚动时显示,停一会儿自动隐藏”需要用代码配合:监听滚动条变化 → 显示滚动条 → 用 QTimer 延时再隐藏。
下面给你一个针对 contactList_2 的完整实现步骤。
- 头文件中新增成员和槽函数
在 chatwindow.h 里:
#include <QTimer>#include <QScrollBar>
在 class chatwindow 里(public: / private: 区域补充):
private: Ui::chatwindow *ui; QTimer *m_scrollbarTimer; // 控制滚动条自动隐藏的定时器private slots: void onMessageListScrolled(int value); // 滚动时触发 void hideMessageScrollbar(); // 超时隐藏滚动条
- 构造函数里初始化、连接信号
在 chatwindow 构造函数中(ui->setupUi(this); 之后)加:
// 1. 初始化定时器(比如 800ms 不再滚动就隐藏)
m_scrollbarTimer = new QTimer(this);m_scrollbarTimer->setInterval(800);m_scrollbarTimer->setSingleShot(true);// 2. 初始时先隐藏滚动条ui->contactList_2->verticalScrollBar()->hide();// 3. 监听滚动条的 valueChanged 信号(无论滚轮、拖动、代码滚动都会触发)connect(ui->contactList_2->verticalScrollBar(), &QScrollBar::valueChanged, this, &chatwindow::onMessageListScrolled);// 4. 定时器超时后隐藏滚动条connect(m_scrollbarTimer, &QTimer::timeout, this, &chatwindow::hideMessageScrollbar);
- 槽函数实现
在 chatwindow.cpp 里实现这两个槽:
// 滚动发生时:显示滚动条,并重置计时器void chatwindow::onMessageListScrolled(int){ QScrollBar *sb = ui->contactList_2->verticalScrollBar(); if (!sb->isVisible()) { sb->show(); // 滚动时让滑块出现 } // 只要还在滚动,就不断重置计时器 m_scrollbarTimer->start();}// 一段时间没有滚动:隐藏滚动条void chatwindow::hideMessageScrollbar(){ ui->contactList_2->verticalScrollBar()->hide();}
解决窗口条目宽度每一行的 QListWidgetItem 宽度等于 contactList 的视口宽度

contactwidget *contwidget = new contactwidget;// 当前 contactList 可用的内容宽度(不含滚动条)
int rowWidth = ui->contactList->viewport()->width();// 把条目宽度设成“等于视口宽度”,高度用 contactwidget 自己的 height
tmpItem->setSizeHint(QSize(rowWidth, contwidget->sizeHint().height()));ui->contactList->setItemWidget(tmpItem, contwidget);