Cluster-Chat-Server
   
Github链接:
https://github.com/Shangyizhou/Cluster-Chat-Server
在 Linux 环境下基于 muduo 开发的集群聊天服务器。实现新用户注册、用户登录、添加好友、添加群组、好友通信、群组聊天、保持离线消息等功能。
    
    
    项目特点
   
- 基于 muduo 网络库开发网络核心模块,实现高效通信
- 使用第三方 JSON 库实现通信数据的序列化和反序列化
- 使用 Nginx 的 TCP 负载均衡功能,将客户端请求分派到多个服务器上,以提高并发处理能力
- 基于发布-订阅的服务器中间件redis消息队列,解决跨服务器通信难题
- 封装 MySQL 接口,将用户数据储存到磁盘中,实现数据持久化
- 基于 CMake 构建项目
    
    
    必要环境
   
- 
     安装
 
 boost
 
 库
- 
     安装
 
 muduo
 
 库
- 
     安装
 
 Nginx
 
- 
     安装
 
 redis
 
    
    
    构建项目
   
创建数据库
# 连接MySQL
mysql -u root -p your passward
# 创建数据库
create database chat;
# 执行数据库脚本创建表
source chat.sql
执行脚本构建项目
bash build.sh
    
    
    执行生成文件
   
# 启动服务端
cd ./bin
./ChatServer 6000 
# 启动客户端
./ChatClient 127.0.0.1 8000
    ![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lt3ivOzm-1663839437559)(https://syz-picture.oss-cn-shenzhen.aliyuncs.com/D:%5CPrograme%20Files(x86)]%5CPicGo1663830578650-52d58f18-370f-426a-b8fe-f07dfd06b116.png)](https://img-blog.csdnimg.cn/11cb78dfcde041dfb3fc8bddbaf80580.png) 
   
    
    
    项目讲解
   
    
    
    数据库表设计
   
    
     User表
    
   
| 字段名称 | 字段类型 | 字段说明 | 约束 | 
|---|---|---|---|
| id | INT | 用户id | PRIMARY KEY、AUTO_INCREMENT | 
| name | VARCHAR(50) | 用户名 | NOT NULL, UNIQUE | 
| password | VARCHAR(50) | 用户密码 | NOT NULL | 
| state | ENUM(‘online’, ‘offline’) | 当前登录状态 | DEFAULT ‘offline’ | 
    
     Friend表
    
   
| 字段名称 | 字段类型 | 字段说明 | 约束 | 
|---|---|---|---|
| userid | INT | 用户id | NOT NULL、联合主键 | 
| friendid | INT | 好友id | NOT NULL、联合主键 | 
    
     AllGroup表
    
   
| 字段名称 | 字段类型 | 字段说明 | 约束 | 
|---|---|---|---|
| id | INT | 组id | PRIMARY KEY、AUTO_INCREMENT | 
| groupname | VARCHAR(50) | 组名称 | NOT NULL, UNIQUE | 
| groupdesc | VARCHAR(200) | 组功能描述 | DEFAULT ‘’ | 
    
     GroupUser表
    
   
| 字段名称 | 字段类型 | 字段说明 | 约束 | 
|---|---|---|---|
| groupid | INT | 组id | NOT NULL、联合主键 | 
| userid | INT | 组员id | NOT NULL、联合主键 | 
| grouprole | ENUM(‘creator’, ‘normal’) | 组内角色 | DEFAULT ‘normal’ | 
    
     OfflineMessage表
    
   
| 字段名称 | 字段类型 | 字段说明 | 约束 | 
|---|---|---|---|
| userid | INT | 用户id | NOT NULL | 
| message | VARCHAR(50) | 离线消息(存储Json字符串) | NOT NULL | 
    
    
    网络模块设计
   
我们会使用 muduo 完成网络模块的代码,在这之前我们需要了解 muduo 的基本使用。
muduo 的线程模型为「one loop per thread + threadPool」模型。一个线程对应一个事件循环(EventLoop),也对应着一个 Reactor 模型。EventLoop 负责 IO 和定时器事件的分派。
    muduo 是主从 Reactor 模型,有
    
     mainReactor
    
    和
    
     subReactor
    
    。
    
     mainReactor
    
    通过
    
     Acceptor
    
    接收新连接,然后将新连接派发到
    
     subReactor
    
    上进行连接的维护。这样
    
     mainReactor
    
    可以只专注于监听新连接的到来,而从维护旧连接的业务中得到解放。同时多个
    
     Reactor
    
    可以并行运行在多核 CPU 中,增加服务效率。因此我们可以通过 muduo 快速完成网络模块。
   
     
   
    使用 muduo 注册消息事件到来的回调函数,并根据得到的
    
     MSGID
    
    定位到不同的处理函数中。以此实现业务模块和网络模块的解耦。
   
// 上报读写事件相关信息的回调函数
void ChatServer::onMessage(const TcpConnectionPtr &conn,
                           Buffer *buffer,
                           Timestamp time)
{
    string buf = buffer->retrieveAllAsString();
    json js = json::parse(buf);
    
	// 业务模块和网络模块解耦
    auto msgHandler = ChatService::instance()->getHandler(js["msgid"].get<int>());
    // 回调消息绑定好的事件处理器,来执行相应的业务处理
    msgHandler(conn, js, time);
}
    
    
    业务模块设计
   
    
    
    注册模块
   
    我们从网络模块接收数据,根据
    
     MSGID
    
    定位到注册模块。从传递过来的
    
     json
    
    对象中获取用户 ID 和用户密码。并以此生成
    
     User
    
    对象,调用 model 层方法将新生成的
    
     User
    
    插入到数据库中。
   
    
    
    登录模块
   
    从
    
     json
    
    对象中获取用户ID和密码,并在数据库中查询获取用户信息是否匹配。如果用户已经登录过,即
    
     state == "online"
    
    ,则返回错误信息。登录成功后需要在改服务端的用户表中记录登录用户,并显示该用户的好友列表和收到的离线消息。
   
    
    
    客户端异常退出模块
   
    如果客户端异常退出了,我们会从服务端记录用户连接的表中找到该用户,如果它断连了就从此表中删除,并设置其状态为
    
     offline
    
    。
   
    
    
    服务端异常退出模块
   
    如果服务端异常退出,它会将所有在线的客户的状态都设置为
    
     offline
    
    。即,让所有用户都下线。异常退出一般是
    
     CTRL + C
    
    时,我们需要捕捉信号。这里使用了 Linux 的信号处理函数,我们向信号注册回调函数,然后在函数内将所有用户置为下线状态。
   
    
    
    点对点聊天模块
   
    通过传递的
    
     json
    
    查找对话用户 ID:
   
- 用户处于登录状态:直接向该用户发送信息
- 用户处于离线状态:需存储离线消息
    
    
    添加好友模块
   
    从
    
     json
    
    对象中获取添加登录用户 ID 和其想添加的好友的 ID,调用 model 层代码在 friend 表中插入好友信息。
   
    
    
    群组模块
   
    创建群组需要描述群组名称,群组的描述,然后调用 model 层方法在数据库中记录新群组信息。
    
    加入群组需要给出用户 ID 和想要加入群组的 ID,其中会显示该用户是群组的普通成员还是创建者。
    
    群组聊天给出群组 ID 和聊天信息,群内成员在线会直接接收到。
   
    
    
    使用Nginx负载均衡模块
   
    
    
    负载均衡是什么
   
假设一台机器支持两万的并发量,现在我们需要保证八万的并发量。首先想到的是升级服务器的配置,比如提高 CPU 执行频率,加大内存等提高机器的物理性能来解决此问题。但是单台机器的性能毕竟是有限的,而且也有着摩尔定律也日已失效。
    这个时候我们就可以增加服务器的数量,将用户请求分发到不同的服务器上分担压力,这就是负载均衡。那我们就需要有一个第三方组件充当负载均衡器,由它负责将不同的请求分发到不同的服务器上。而本项目,我们选择
    
     Nginx
    
    的负载均衡功能。
   
    选择
    
     Nginx
    
    的
    
     tcp
    
    负载均衡模块的原因:
   
- 
     把
 
 client
 
 的请求按照负载算法分发到具体的业务服务器
 
 ChatServer
 
 上
- 
     能够
 
 ChantServer
 
 保持心跳机制,检测
 
 ChatServer
 
 故障
- 
     能够发现新添加的
 
 ChatServer
 
 设备,方便扩展服务器数量
    
    
    配置负载均衡
   
    
    
    选择Nginx的tcp负载均衡模块的原因:
   
- 把client的请求按照负载算法分发到具体的业务服务器ChatServer上
- 能够ChantServer保持心跳机制,检测ChatServer故障
- 能够发现新添加的ChatServer设备,方便扩展服务器数量
     
   
配置好后,重新加载配置文件启动。
/usr/local/nginx/sbin/nginx -s reload
    
    
    redis发布-订阅功能解决跨服务器通信问题
   
    
    
    如何保证支持跨服务器通信
   
    我们之前的
    
     ChatServer
    
    是维护了一个连接的用户表,每次向别的用户发消息都会从用户表中查看对端用户是否在线。然后再判断是直接发送,还是转为离线消息。
   
    但是现在我们是集群服务器,有多个服务器维护用户。我们的
    
     ChatServerA
    
    要聊天的对象在
    
     ChatServerB
    
    ,
    
     ChatServerA
    
    在自己服务器的用户表中找不到。那么可能对端用户在线,它却给对端用户发送了离线消息。因此,我们需要保证跨服务器间的通信!那我们如何实现,非常直观的想法,我们可以让后端的服务器之间互相连接。
   
    
    
    上面的设计,让各个ChatServer服务器互相之间直接建立TCP连接进行通信,相当于在服务器网络之间进行广播。这样的设计使得各个服务器之间耦合度太高,不利于系统扩展,并且会占用系统大量的socket资源,各服务器之间的带宽压力很大,不能够节省资源给更多的客户端提供服务,因此绝对不是一个好的设计。
   
集群部署的服务器之间进行通信,最好的方式就是引入中间件消息队列,解耦各个服务器,使整个系统松耦合,提高服务器的响应能力,节省服务器的带宽资源,如下图所示:
     
   
    
    
    详细记录
   
- 
     
 (208条消息) JSON For Modern C++使用_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (一)使用muduo编写网络模块ChatServer_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (二)网络模块和业务模块解耦,编写ChatService_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (三)MySQL模块封装_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (四)model数据层设计_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (五)用户注册模块讲解_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (六)用户登录模块讲解_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (七)客户端异常退出事件处理_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (八)点对点聊天业务处理_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (九)离线消息业务代码_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十)服务端异常退出_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十一)添加好友业务_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十二)群组业务_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十三)客户端实现_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十四)引入负载均衡器_Last-Week的博客-CSDN博客
 
- 
     
 (208条消息) (十五)使用Redis实现发布订阅功能_Last-Week的博客-CSDN博客
 
 
