文章目录
项目源码 -> 点这里
项目演示视频 ->
一、项目概述
- 项目名称:语音直播聊天室
- 核心定位:基于 C/S 架构的实时互动直播平台
- 技术架构:TCP 协议(客户端 - 服务器通信)+ UDP 协议(音视频传输)+ 数据库存储
- 核心价值:支持多用户实时直播、互动交流,满足直播观看与社交需求
二、核心功能模块
- 用户管理模块:注册(查重防重复)、登录(防重复登录)、注销、充值
- 直播房间模块:创建直播间(主播权限)、加入直播间(游客权限)、房间列表刷新
- 音视频传输模块:主播摄像头 / 麦克风采集、UDP 组播传输、游客实时接收
- 互动功能模块:文字群聊、弹幕发送、礼物赠送(积分 / 金钱联动)
- 列表展示模块:直播房间列表、直播间游客列表实时刷新
三、技术实现亮点
- 协议设计:自定义协议包封装,实现数据统一传输与解析
- 多线程支持:多客户端并发连接,独立线程处理通信逻辑
- 数据存储:DAO 层封装,用户信息、积分等数据安全存储于数据库
- 音视频优化:UDP 组播避免串房,视频压缩适配网络传输,音频 G.711 编码保障音质
- 设计模式:采用抽象工厂、策略模式等,实现功能模块解耦
四、 架构框图

五、 音视频模块
音频、视频、弹幕都采用udp组播 因udp组播可分组通信,且可以在广域网上传输,而udp广播只适用于局域网
可直接移植音视频模块:请参考附件《C++项目Qt视频、音频部分整理》
六、 应用层协议
理解JSON,JS对象,QJSON的区别和联系
JSON和JS对象的概念:

为什么用JSON:

JSON和QJSON的概念?为什么在Qt中要转成QBtyeArray?

| 场景 | 数据(要寄的东西) | 工具(用来写 / 读的) | 发送工具(邮局 / 快递员) | 必须做的事 |
|---|---|---|---|---|
| Web 开发 | 用户名、年龄 | JSON.stringify/parse | AJAX(邮局) | 直接把 JSON 文字交给邮局,邮局收 |
| Qt 开发 | 用户名、年龄 | QJSON 工具包 | Qt 网络模块(挑剔快递员) | 先把 JSON 文字装进 QByteArray 快递袋,再给快递员 |
自定义协议的代码实现:
cpp
class Protocol{
public:
enum Type{
none,
rgst, //账户注册
login, //账户登录
ClientExit, //账户退出
ClientDelete, //账户注销
showUserInfo, //显示信息
chargeMoney, //账户充值
RoomCreate, //创建房间
JoinRoom, //加入房间
refreshRoomList, //房间列表
refreshAudienceList,//游客列表
closeRoom, //关闭房间
sendMsg, //用户群聊
backToHall, //退出直播
AudienceBackToHall, //退出直播
sendGift //送出礼物
};
Protocol(Type type = none):type(type){}
void setType(Type type){this->type = type;}
QJsonValueRef operator[](const QString& key){return m_body[key];} //set值时返回值必须是引用或指针,否则操作的只是副本,值无法改变
Type getType()const{return type;}
QJsonValue operator[](const QString& key)const{return m_body[key];}
//打包
QByteArray pack(){
//数据包 = head + body(可变长数据部分)
QByteArray body = QJsonDocument(m_body).toJson();
QByteArray head(8,0);
//head的前四个字节为可变长数据部分的大小
*(int*)head.data() = body.size();
//head的后四个字节为数据包的类型(固定数据部分的大小)
*(Type*)(head.data()+4) = type; //??
return head+body;
}
//拆包
int unpack(const QByteArray& dataPack){
if(dataPack.isEmpty()){return 0;}
//可边长数据部分的长度
int len = *(int*)dataPack.data();
if(len<=0){return 0;}
//取出类型
this->type = *(Type*)(dataPack.data()+4);
//取出可边长数据部分
QByteArray body = dataPack.mid(8,len);
//保存到json类里
m_body = QJsonDocument::fromJson(body).object();
return 8+len;
}
private:
Type type;
QJsonObject m_body;
};
七、 多用户并发
7.1 代码中 "多用户并发" 的核心实现逻辑
项目基于 Qt 的事件循环机制(而非多线程),通过 "为每个客户端创建独立套接字 + 独立消息处理对象",实现多用户同时连接与通信,具体流程如下:
1. 服务端:为每个客户端分配独立套接字(关键代码:Server_w1.cpp)
当新客户端连接时,服务端通过QTcpServer的newConnection()信号触发newConnection_slot(),为每个客户端创建独立的QTcpSocket对象 ,并将其存入SockContainer容器管理:
cpp
// Server_w1.cpp 核心代码
void Server_w1::newConnection_slot(){
// 为新客户端创建独立套接字
QTcpSocket* tcpSocket = tcpServer->nextPendingConnection();
// 将套接字存入单例容器(SockContainer),避免内存泄漏
SockContainer* sockContainer = SockContainer::getInstance();
sockContainer->insertInto_SockContainer(tcpSocket);
// 为该套接字创建独立的消息处理器(SockHandle)
new SockHandle(this,tcpSocket);
}
- 关键作用:每个客户端的通信通过 "专属套接字" 完成,不同客户端的数据流互不干扰,是并发的基础。
2. 独立消息处理:SockHandle类隔离客户端请求(关键代码:SockHandle.cpp)
为每个客户端的QTcpSocket创建独立的SockHandle对象,通过readyRead()信号(Qt 事件驱动)处理该客户端的消息,避免多用户请求相互阻塞:
cpp
// SockHandle.cpp 核心代码
SockHandle::SockHandle(QObject* parent,QTcpSocket* tcpSocket)
:QObject(parent),tcpSocket(tcpSocket)
{
// 绑定"客户端发消息"信号到专属处理槽函数
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_slot()));
}
void SockHandle::readyRead_slot(){
QByteArray byte = tcpSocket->readAll(); // 仅读取当前客户端的数据流
Protocol dataPack;
if(dataPack.unpack(byte) >0){
// 为当前客户端的请求分配独立处理器(DataPack_Handle)
DataPack_Handle* handle = new DataPack_Handle;
handle->strategy(tcpSocket,dataPack); // 处理当前客户端的业务(注册/登录/进房等)
delete handle;
}
}
- 关键逻辑 :每个
SockHandle仅关联一个客户端的套接字,readyRead_slot()只处理该客户端的消息,通过 Qt 的事件循环机制,实现 "多客户端请求的并行处理"(本质是事件的异步调度,而非 CPU 多线程并行)。
3. 广播机制:支持多客户端同步接收消息(关键代码:RoomManage.cpp)
当需要向多客户端同步消息(如刷新房间列表、同步聊天记录)时,通过SockContainer遍历所有客户端套接字,逐一发送消息,确保多用户同时接收:
cpp
// RoomManage.cpp 刷新房间列表核心代码
void RoomManage::refreshRoomList(){
RoomContainer* roomSingle = RoomContainer::getInstance();
SockContainer* sockSingle = SockContainer::getInstance();
QVector<QTcpSocket*> allSockets = sockSingle->get_allSockets(); // 获取所有在线客户端套接字
Protocol dataPack2(Protocol::refreshRoomList);
// 打包所有房间名到协议包
int count = 0;
for(auto& room : roomSingle->get_allRooms()){
dataPack2[QString::number(count++)] = room.get_roomName();
}
// 向所有客户端发送刷新指令(并发同步)
for(auto& sock : allSockets){
sock->write(dataPack2.pack());
}
}
- 效果:多客户端可同时收到 "房间列表更新""弹幕""礼物提示" 等消息,实现并发场景下的同步交互。
7.2 与 "多线程" 的区别:事件驱动 vs 多线程并行
你的项目确实没有使用多线程,但通过 Qt 的事件循环实现了 "伪并发",二者核心差异如下:
| 对比维度 | 你的项目(事件驱动) | 多线程实现(如QThread) |
|---|---|---|
| 核心原理 | 基于 Qt 事件循环,异步调度多个客户端的事件(如readyRead),同一时间仅处理一个事件,但切换速度快,感知不到阻塞 |
基于 CPU 多线程,为每个客户端 / 任务分配独立线程,真正实现并行处理 |
| 资源占用 | 低(无需创建多线程栈、线程调度开销) | 高(每个线程占用独立内存栈,线程切换有 CPU 开销) |
| 代码复杂度 | 低(无需处理线程同步、互斥锁等问题) | 高(需解决线程安全、资源竞争,如数据库连接互斥) |
| 适用场景 | 轻量级并发(如 10-50 个客户端,无密集计算) | 高并发、密集计算场景(如百级以上客户端、音视频编解码) |
总结:你的项目通过 "事件驱动 + 独立套接字 + 异步处理",在不使用多线程的情况下,满足了中小型规模的多用户并发需求,且避免了多线程带来的复杂度(如线程安全问题)。
7.3 当前实现的优势与潜在局限
- 优势:轻量、稳定、易维护
- 无需处理多线程同步问题(如数据库连接冲突、容器访问竞争),代码故障率低;
- 资源占用少,适合轻量级直播场景(如几十人同时在线);
- 基于 Qt 原生事件机制,兼容性好,跨平台部署简单。
- 潜在局限:高并发下的性能瓶颈
当客户端数量大幅增加(如超过 100 个)或存在密集型操作(如高清音视频编码)时,单线程事件循环可能出现瓶颈:
- 所有客户端的消息处理都在主线程事件循环中调度,若某个请求处理耗时较长(如复杂数据库查询),会阻塞其他客户端的消息;
- 音视频传输通过 UDP 组播优化,但消息分发(如遍历所有套接字发送刷新指令)的效率会随客户端数量增加而下降。
7.4 若需提升并发能力:可选的优化方向(基于现有代码扩展)
若后续需要支持更多用户,可在现有架构基础上,局部引入多线程(无需重构整体代码),例如:
- 业务处理线程池 :将
DataPack_Handle::strategy()的业务逻辑(如注册登录、送礼积分计算)放入QThreadPool,避免耗时操作阻塞主线程; - 音视频独立线程:将主播端的音视频采集、编码放入独立线程,避免音视频处理占用主线程资源;
- 数据库操作异步化 :使用
QtConcurrent::run()将数据库查询 / 更新(如MyDB::createConnection()后的 SQL 执行)放入后台线程,避免 IO 等待阻塞主线程。
这些优化可在不破坏现有 "事件驱动 + 独立套接字" 架构的前提下,提升高并发场景下的稳定性。
八、总结与展望
- 项目亮点:跨协议协同、多用户并发支持、完整互动闭环
- 优化方向:界面美化、音视频延迟优化、更多互动玩法扩展
- 应用场景:在线直播、远程互动、兴趣社群交流