QT 基础聊天应用项目文档

发布于:2025-08-19 ⋅ 阅读:(18) ⋅ 点赞:(0)

一、项目介绍

本项目是一个基于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.h
  class File : public QWidget {
     Q_OBJECT
  public:
      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.cpp
  void 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应用。核心亮点包括:

· 采用单例模式与设计模式优化架构,提高代码复用性;

· 自定义协议解决网络通信中的粘包/半包问题;

· 多线程与线程池平衡性能与资源消耗;

· 信号槽与对象树简化内存管理与跨线程通信。


网站公告

今日签到

点亮在社区的每一天
去签到