项目地址:https://github.com/elaysia-feng/NebulaChat.git
基于 C++17 的高并发聊天室服务端,是一个涵盖现代 C++、Linux 网络编程、MySQL、Redis 的综合项目。
一、功能概览
1. 用户系统
-
用户名 + 密码登录
-
手机号 + 短信验证码登录(模拟短信服务)
-
用户注册(手机号 + 验证码 + 用户名 + 密码)
-
忘记密码(手机号 + 验证码重置密码)
-
登录后修改昵称
2. 聊天系统
-
多聊天室房间(
roomId) -
登录后自动加入默认房间(如 1 号房)
-
房间容量限制(如
MAX_ROOM_SIZE = 100) -
房间内广播消息:一人发言,房间内所有在线用户收到
-
拉取历史消息,支持指定条数
limit
3. 存储与缓存
-
MySQL 持久化:
-
用户信息
-
聊天消息
-
-
Redis 缓存:
-
用户缓存(用户名 / 手机号 → 用户信息或 null)
-
短信验证码
-
聊天历史缓存(按房间 + limit 维度)
-
-
缓存策略:
-
空值缓存:不存在的用户结果会缓存
"null" -
历史消息:互斥锁 + double-check,避免热点 key 失效时的缓存击穿
-
历史缓存 TTL 加随机抖动,减轻缓存雪崩
-
4. 并发模型
-
单 Reactor(
epoll)+ 多线程线程池 -
非阻塞 socket,支持边缘触发和水平触发
-
使用
eventfd唤醒 Reactor -
线程安全任务队列
SafeQueue -
会话结构
Connection:记录用户登录状态、房间号、输入输出缓冲等
二、技术栈
-
语言:C++17
-
平台:Linux(如 Ubuntu 20.04+)
-
构建工具:CMake
-
网络编程:
-
socket/bind/listen/accept -
epoll多路复用 -
非阻塞 I/O
-
TCP_NODELAY
-
-
并发相关:
-
std::thread -
std::mutex/std::unique_lock/std::condition_variable -
std::atomic<bool>控制连接写状态、短关标记等
-
-
存储:
-
MySQL(
libmysqlclient) -
Redis(
hiredis)
-
-
第三方库:
nlohmann::json处理 JSON 协议
三、项目目录结构示例
bash
.
├── CMakeLists.txt
├── src
│ ├── main.cpp
│ ├── core
│ │ ├── Reactor.cpp
│ │ ├── Server.cpp
│ │ ├── ThreadPool.cpp
│ ├── chat
│ │ ├── MessageHandler.cpp
│ │ ├── AuthService.cpp
│ │ ├── SmsService.cpp
│ │ ├── ChatHistory.cpp
│ │ ├── RoomManager.cpp
│ ├── db
│ │ ├── DBconnection.cpp
│ │ ├── DBpool.cpp
│ │ ├── RedisConnection.cpp
│ │ ├── RedisPool.cpp
│ ├── utils
│ ├── Random.cpp
│ └── ...
└── include
├── core/*.h
├── chat/*.h
├── db/*.h
└── utils/*.h
四、核心设计
1. 网络层(core)
Reactor
-
封装
epoll -
负责:
-
注册 / 修改 / 删除 fd 事件
-
主循环
loop() -
处理
eventfd唤醒 -
分发回调(
dispatcher_)
-
Server
-
管理监听 fd 和所有客户端连接
-
主要职责:
-
start():创建 socket,设置非阻塞和端口复用,加入 Reactor -
onAccept():接受新连接,创建Connection,放入conns_容器,注册读事件 -
onConnRead():-
非阻塞读取数据到
Connection::inbuf -
按
\n拆包(行协议) -
将每条 JSON 请求放入线程池异步处理
-
-
postWrite(fd, data):-
线程安全地将响应追加到
Connection::outbuf -
打开该 fd 的
EPOLLOUT事件 -
调用
reactor_.wakeup()唤醒事件循环
-
-
onConnWrite():-
非阻塞写
outbuf到 socket -
写完关闭
EPOLLOUT -
若标记
shortClose,则在写完后关闭连接
-
-
broadcastToRoom(roomId, data):-
遍历
conns_中所有连接 -
按
Connection.roomId筛选房间内用户,逐个调用postWrite
-
-
Connection(namespace utils)
-
int fd:socket 描述符 -
std::string inbuf:输入缓冲区 -
std::string outbuf:输出缓冲区 -
I/O 状态:
-
std::atomic<bool> wantWrite -
std::atomic<bool> shortClose
-
-
会话状态:
-
bool authed:是否已登录 -
int userId:用户 ID -
std::string name:用户名 -
int roomId:所在房间号
-
ThreadPool 与 SafeQueue
-
SafeQueue<T>:-
使用
std::mutex和std::condition_variable实现的阻塞队列 -
提供
Safepush/Safepop接口
-
-
ThreadPool:-
维护若干工作线程
-
从任务队列中取任务执行
-
Server::onConnRead中将业务处理逻辑投递到线程池
-
2. 业务层(chat)
MessageHandler
-
根据请求 JSON 中的
cmd字段分发业务逻辑:-
login:用户名密码登录或短信登录 -
register:短信校验后注册用户 -
reset_pass:手机号 + 验证码重置密码 -
update_name:修改昵称 -
join_room:加入或切换聊天室(带房间容量限制) -
send_msg:发送消息(房间广播 + 写入历史) -
get_history:拉取历史消息(使用缓存)
-
-
通信协议为一行 JSON,以
\n结尾。
AuthService
-
登录相关逻辑:
-
用户名 + 密码登录
-
手机号 + 短信验证码登录
-
注册用户
-
修改用户名
-
重置密码
-
-
依赖:
-
DBPool操作 MySQL -
**
RedisPool**做用户缓存与空值缓存,减少 DB 压力
-
SmsService
-
模拟短信验证码服务:
-
生成随机验证码
-
将验证码写入 Redis,key 类似
sms:phone:<手机号> -
同时写日志,方便在终端查看验证码
-
-
校验逻辑:
-
从 Redis 中读取验证码并比对
-
可设置一次性使用和过期时间
-
RoomManager
-
管理房间在线人数:
-
tryEnterRoom(roomId, maxSize):若当前人数小于maxSize,则人数加一并返回 true -
leaveRoom(roomId):用户离开房间时人数减一 -
getRoomSize(roomId):查询当前房间在线人数
-
-
内部使用:
-
std::mutex -
std::unordered_map<int, int>存储房间人数
-
ChatHistory
-
SaveMessage():-
将单条消息写入 MySQL
messages表,字段包括:-
room_id -
user_id -
username -
content -
created_at
-
-
使用
mysql_real_escape_string转义字符串,避免 SQL 注入和语法错误
-
-
GetHistoryWithCache(roomId, limit, historyOut):-
Redis 正常:
-
尝试
GET room:history:<roomId>:<limit> -
命中缓存则直接解析返回
-
未命中:
-
使用互斥锁 + double-check 避免多个线程同时回源
-
从 DB 拉取最近
limit条消息 -
写回 Redis,TTL 基础值加随机抖动
-
-
-
Redis 不可用:
-
进入降级模式
-
使用简单限流及互斥锁串行访问 DB,避免瞬间大量请求压垮数据库
-
-
3. 基础设施层(db 与 utils)
DBPool / DBConnection
-
提供简单的 MySQL 连接池
-
封装:
-
MYSQL_RES* query(const std::string& sql) -
bool update(const std::string& sql)
-
-
连接参数(host、user、password、db、port)在代码中配置
RedisPool / RedisConnection
-
基于
hiredis封装的 Redis 连接池 -
提供:
-
bool get(const std::string& key, std::string& out) -
bool setEX(const std::string& key, const std::string& value, int ttl)
-
utils::Random
-
随机数工具:
int RandInt(int min, int max):使用线程本地mt19937和均匀分布生成区间随机整数
五、通信协议示例(JSON 行协议)
所有请求和响应统一为:一行 JSON 字符串 + 换行符 \n。
1. 用户名 + 密码登录
请求:
bash
{"cmd": "login", "mode": "password", "user": "elias", "pass": "123456"}
成功响应示例:
bash
{
"ok": true,
"roomId": 1
}
2. 发送消息
请求:
bash
{"cmd": "send_msg", "text": "hello, everyone"}
广播给房间内所有用户的响应示例:
bash
{
"ok": true,
"broadcast": true,
"roomId": 1,
"fromId": 1001,
"fromName": "elias",
"text": "hello, everyone",
"ts": 1732250000
}
3. 加入或切换房间
请求:
bash
{"cmd": "join_room", "roomId": 2}
成功响应:
bash
{"ok": true, "roomId": 2, "msg": "join room success"}
房间已满时的响应:
bash
{"ok": false, "roomId": 1, "msg": "room is full"}
4. 拉取历史消息
请求:
bash
{"cmd": "get_history", "limit": 20}
响应示例:
bash
{
"ok": true,
"roomId": 1,
"history": [
{
"id": 123,
"roomId": 1,
"fromId": 1001,
"fromName": "elias",
"text": "hello",
"ts": 1732250000
}
]
}
六、构建与运行
1. 环境依赖
-
Linux(推荐 Ubuntu 20.04 及以上)
-
g++(支持 C++17)
-
CMake 3.10 及以上
-
MySQL 服务器和开发库:
-
示例安装命令:
bashsudo apt install mysql-server libmysqlclient-dev
-
-
Redis 服务器和 hiredis:
-
示例安装命令:
bashsudo apt install redis-server libhiredis-dev
-
2. 配置数据库与 Redis
在 DBpool.cpp 和 RedisPool.cpp 中配置:
-
host -
user -
password -
database -
port
确保:
-
MySQL 中已创建所需数据库和数据表(例如
users、messages) -
Redis 已启动,密码和端口与代码配置一致
3. 编译与运行
bash
mkdir build
cd build
cmake ..
make -j
编译成功后执行:
bash
./NebulaChat