QT 基础聊天应用项目文档
一、项目介绍
本项目是一个基于Qt框架开发的客户端/服务器架构应用,支持用户通信与文件管理功能。客户端与服务器通过TCP协议实现数据交互,采用自定义协议规范数据格式,结合数据库存储用户信息与关系,并通过多线程与线程池优化性能,最终实现了用户认证、好友管理、在线聊天、文件操作(创建、上传、下载、移动、分享等)等核心功能。
二、项目功能
1. 客户端功能
用户认证:支持注册(用户名、密码存储)与登录(验证身份并更新在线状态)。
好友管理:查找用户(在线/离线状态)、添加/删除好友、刷新好友列表、查看在线用户。
在线聊天:选择好友发起对话,实时发送与接收消息。
文件操作:
· 目录管理:创建文件夹、删除目录、重命名、返回上级目录。
· 文件管理:上传本地文件、移动文件、下载文件、分享文件给好友。
· 列表刷新:实时更新当前目录下的文件/文件夹列表。
2. 服务器功能
连接管理:监听客户端连接,使用线程池处理多客户端请求,支持并发通信。
请求处理:解析客户端PDU(协议数据单元),处理注册、登录、消息转发、文件操作等请求。
数据存储:通过数据库维护用户信息(账号、密码、在线状态)与好友关系。
文件管理:处理客户端的文件上传、创建目录、删除等操作,维护服务器文件系统。
消息转发:实现用户间聊天消息、文件分享通知的转发。
三、技术点详解
1. 客户端与服务器搭建及通信(TCP)
搭建方式
客户端:通过QTcpSocket建立与服务器的连接,在Client类中封装连接逻辑(loadConfig加载服务器IP和端口,connectToHost发起连接)。
服务器:通过QTcpServer(自定义MyTcpServer)监听端口,当客户端连接时,创建MyTcpSocket处理该连接,并将其托管给线程池。
通信流程
1. 客户端通过sendMsg方法发送PDU格式的数据;
2. 服务器通过recvMsg接收数据,解析PDU后调用对应处理函数(如登录请求由MsgHandler::login处理);
3. 服务器处理完成后,通过resend方法返回响应或转发消息给目标客户端。
//代码示例//客户端连接服务器(client.cpp):m_tcpSocket.connectToHost(QHostAddress(m_strIP), m_usPort);//服务器监听连接(mytcpserver.cpp):MyTcpServer::getInstance().listen(QHostAddress(m_strIP), m_usPort);
2. 单例模式
应用场景
项目中Client、Server、Index等类均采用单例模式,确保全局唯一实例。
为什么使用
全局资源共享:如客户端的TCP连接、服务器的线程池,需全局唯一实例统一管理。
简化接口:避免频繁传递对象指针,直接通过getInstance()访问。
控制资源创建:防止重复创建资源(如数据库连接、网络 socket)。
线程安全性
· 采用static局部变量实现单例(C++11后静态变量初始化线程安全):
Client& Client::getInstance() {static Client instance;return instance;}
3. 设计模式扩展
(1)观察者模式
· 应用:Qt的信号槽(signal/slot)机制是观察者模式的典型实现。例如,客户端上传文件时,Uploader通过sendPDU信号通知Client发送数据,Client作为观察者响应信号。
// 代码示例
// 连接信号与槽(观察者模式:Uploader为被观察者,Client为观察者)
connect(uploader, &Uploader::sendPDU, this, &Client::sendMsg);
(2)工厂模式
应用:mkPDU函数(protocol.cpp)用于创建不同类型的PDU对象,根据消息类型(uiMsgType)和长度动态生成实例,隐藏对象创建细节。
//代码示例:PDU* mkPDU(uint uiMsgType, uint uiMsgLen) {uint uiPDULen = uiMsgLen + sizeof(PDU);PDU* pdu = (PDU*)malloc(uiPDULen);memset(pdu, 0, uiPDULen);pdu·>uiMsgType = uiMsgType; pdu·>uiPDULen = uiPDULen;pdu·>uiMsgLen = uiMsgLen;return pdu;}
4. 协议设计(PDU)
//结构定义
struct PDU {uint uiPDULen; // 整个PDU长度(含头部)uint uiMsgLen; // 消息体(caMsg)长度uint uiMsgType; // 消息类型(如登录请求、聊天消息)char caData[64]; // 固定长度数据(如用户名、文件名)char caMsg[]; // 柔性数组,存储变长消息(如聊天内容、文件路径)
};
核心技术
柔性数组:caMs作为柔性数组,动态分配内存存储变长数据(如文件内容、长文本),节省空间。
解决粘包/半包:
· 发送端:通过uiPDULen标识整个PDU的长度;
· 接收端:在recvMsg中缓存数据,循环判断缓存长度是否大于等于uiPDULen,若满足则提取完整PDU,否则继续等待。
5. 数据库表设计
(1)用户表(user_info)
(2)好友关系表(friend)
通过user_info存储用户基本信息,online字段用于快速获取在线用户列表;
通过friend表记录双向好友关系(如user_id=1与friend_id=2表示1和2互为好友)。
6. 函数和类的封装
封装原则
单一职责:每个类专注于特定功能(如File类处理文件操作,Friend类处理好友管理)。
接口抽象:通过类的成员函数暴露功能,隐藏实现细节(如Client::sendMsg封装TCP发送逻辑)。
//File类封装文件操作:// file.hclass File : public QWidget {Q_OBJECTpublic:void flushFile(); // 刷新文件列表void uploadFile(); // 上传文件private:QString m_strCurPath; // 当前路径(私有,仅内部访问)};
7. 多线程上传
实现逻辑
客户端通过Uploader类在独立线程中执行文件上传,避免阻塞UI线程。
分块读取文件(每块4096字节),通过信号槽将PDU发送给Client类,由其通过TCP发送。
// uploader.cpp
void Uploader::uploadFile() {QFile file(m_strUploadFilePath);file.open(QIODevice::ReadOnly);while (true) {PDU* datapdu = mkPDU(ENUM_MSG_TYPE_UPLOAD_FILE_DATA_REQUEST, 4096);qint64 ret = file.read(datapdu·>caMsg, 4096); // 分块读取if (ret <= 0) break;datapdu·>uiMsgLen = ret;emit sendPDU(datapdu); // 发送数据块}
}
线程管理
· 通过QThread创建上传线程,上传完成后自动销毁线程:
// uploader.cppvoid Uploader::start() {QThread* thread = new QThread;this·>moveToThread(thread);connect(thread, &QThread::started, this, &Uploader::uploadFile);connect(this, &Uploader::finished, thread, &QThread::quit);thread·>start();}
8. 服务器线程池
作用
减少线程创建与销毁的开销:通过”QThreadPool”复用线程,处理多个客户端连接。
控制并发数:避免线程过多导致的资源竞争,提高系统稳定性
线程数量设计
项目中线程池最大线程数设为8(MyTcpServer::MyTcpServer()中threadPool.setMaxThreadCount(8))。
设计依据:线程数通常设置为CPU核心数的1~2倍,兼顾并发能力与资源消耗(8核CPU环境下合理)。
// mytcpserver.cpp 中处理新连接
void MyTcpServer::incomingConnection(qintptr handle) {MyTcpSocket* socket = new MyTcpSocket;socket·>setSocketDescriptor(handle);ClientTask* task = new ClientTask(socket); // 任务封装threadPool.start(task); // 线程池执行任务
}
9. 信号槽与对象树
信号槽(Signal & Slot)
作用:实现对象间通信,无需显式调用函数(如按钮点击触发文件上传)。
连接类型(第五个参数):
· Qt::DirectConnection:直接调用(同一线程);
· Qt::QueuedConnection:跨线程通信,通过事件队列异步执行(如UI线程与上传线程)。
· 原理:基于Qt元对象系统(”Q_OBJECT”宏),编译时生成元数据,运行时通过QMetaObject解析并触发槽函数。
对象树
机制:Qt对象通过父指针形成树状结构,父对象销毁时自动删除所有子对象,避免内存泄漏。
示例:QWidget的子控件(如按钮、输入框)自动加入对象树,随窗口销毁而释放。
10. 事件处理
机制:Qt通过事件循环(QApplication::exec())接收并分发事件(如鼠标点击、网络数据到达)。
示例:MyTcpSocket通过QTcpSocket的readyRead事件触发recvMsg法,处理接收数据。
四、总结
项目基于Qt框架,通过TCP协议、自定义PDU协议、数据库、多线程等技术,实现了一个功能完整的C/S应用。核心亮点包括:
· 采用单例模式与设计模式优化架构,提高代码复用性;
· 自定义协议解决网络通信中的粘包/半包问题;
· 多线程与线程池平衡性能与资源消耗;
· 信号槽与对象树简化内存管理与跨线程通信。