整体架构流程
客户端 → Nginx(负载均衡)→ ChatServer集群 → Redis(消息队列)→ 其他ChatServer → MySQL(数据持久化)
数据流向
-
用户注册/登录 → MySQL
-
在线消息 → Redis发布/订阅 → 目标服务器
-
离线消息 → MySQL → 用户登录时推送
-
好友/群组关系 → MySQL
技术栈
-
Json序列化和反序列化
-
muduo网络库开发
-
nginx源码编译安装和环境部署
-
nginx的tcp负载均衡器配置
-
redis缓存服务器编程实践
-
基于发布-订阅的服务器中间件redis消息队列编程实践
-
MySQL数据库编程
-
CMake构建编译环境
-
Github托管项目
项目需求
-
客户端新用户注册
-
客户端用户登录
-
添加好友和添加群组
-
好友聊天
-
群组聊天
-
离线消息
-
nginx配置tcp负载均衡
-
集群聊天系统支持客户端跨服务器通信
项目目标
-
掌握服务器的网络I/O模块,业务模块,数据模块分层的设计思想
-
掌握c++ muduo网络库的编程以及实现原理
-
掌握Json的编程应用
-
掌握nginx配置部署tcp负载均衡器的应用以及原理
-
掌握服务器中间件的应用场景和基于发布-订阅的redis编程实践以及应用原理
-
掌握CMake构建自动化编译环境
-
掌握Github管理项目
开发环境
-
ubuntu linux环境
-
安装Json开发库
-
安装boost + muduo网络库开发环境
-
安装redis环境
-
安装mysql数据库环境
-
安装nginx
-
安装CMake环境
Muduo 网络库完整搭建教程(Linux 环境)
Muduo 是基于 Reactor 模式的 Linux 专属 C++ 网络库,搭建核心是安装依赖→编译源码→验证安装,以下是 Ubuntu/CentOS 通用步骤,包含环境配置、编译细节、验证测试和常见问题解决。
一、搭建前提
操作系统:Linux(推荐 Ubuntu 18.04+/CentOS 7+,muduo 依赖 epoll,不支持 Windows/macOS);
核心依赖:C++ 编译器(gcc 4.8+)、CMake(3.0+)、Boost 库(1.58+)、Git(拉取源码)。
二、分步搭建流程
步骤 1:安装基础编译工具与依赖
更新软件源
sudo apt update
安装基础编译工具(gcc/g++/make/cmake)
sudo apt install -y build-essential cmake
安装 Boost 库(muduo 依赖 boost-thread/boost-system)
sudo apt install -y libboost-dev libboost-thread-dev
安装 Git(拉取源码)
sudo apt install -y git
步骤 2:拉取 Muduo 源码
克隆官方仓库(若网络慢,可换国内镜像,如 gitee 镜像)
git clone https://github.com/chenshuo/muduo.git
进入源码目录
cd muduo
步骤 3:编译 Muduo 源码
Muduo 采用 CMake 构建,编译前需创建构建目录,避免污染源码:
创建 build 目录(规范编译目录)
mkdir build && cd build
配置 CMake(指定安装路径为 /usr/local,方便系统查找)
cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local
编译(-j 后接线程数,建议等于 CPU 核心数,加速编译)
例如 4 线程:make -j4
make -j$(nproc)
步骤 4:安装 Muduo 库(关键)
编译完成后,将库文件和头文件安装到系统目录(/usr/local),方便后续项目链接:
sudo make install
步骤 5:验证安装是否成功
安装后会生成两类文件,检查是否存在即可确认:
检查头文件(muduo 核心头文件)
ls /usr/local/include/muduo/
检查库文件(静态库,muduo_base 基础库 + muduo_net 网络库)
ls /usr/local/lib/libmuduo_*.a
正常输出应包含:
头文件:muduo/base/、muduo/net/ 等目录;
库文件:libmuduo_base.a、libmuduo_net.a。
json
一、JSON 是什么?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式。它采用纯文本格式,结构清晰、易于阅读和编写,也易于机器解析和生成。在 C++ 项目中,常用于:
-
数据序列化:将对象或数据结构转换为字符串,便于网络传输或存储。
-
数据反序列化:将接收到的 JSON 字符串还原为程序中的对象或数据结构。
二、为什么在项目中使用 JSON?
在这个集群聊天服务器项目中,JSON 用于:
-
客户端与服务器之间的通信协议(如登录、注册、发送消息等)
-
存储和传输离线消息
-
配置信息存储
-
日志结构化输出
它比 XML 更轻量,比纯二进制协议更易调试。
三、JSON 的基本结构
JSON 支持以下几种数据类型:
json
{
"name": "张三",
"age": 25,
"isStudent": false,
"hobbies": ["编程", "读书", "音乐"],
"address": {
"city": "北京",
"zip": "100000"
}
}
-
对象:
{ key: value } -
数组:
[ value1, value2 ] -
字符串:
"string" -
数字:
123或12.34 -
布尔值:
true/false -
空值:
null
四、项目中使用的 JSON 库
项目使用的是 JSON for Modern C++(作者:nlohmann),特点是:
-
只有一个头文件
json.hpp -
支持 C++11 及以上
-
语法直观,类似操作 STL 容器
-
支持序列化/反序列化 STL 容器
五、项目中 JSON 的使用示例
1. 序列化(对象 → JSON 字符串)
cpp
#include "json.hpp"
using json = nlohmann::json;
json js;
js["id"] = 123;
js["name"] = "张三";
js["msg"]["zhang san"] = "hello world";
std::string jsonStr = js.dump(); // 序列化为字符串
2. 反序列化(JSON 字符串 → 对象)
cpp
std::string receivedStr = R"({"id":123,"name":"张三"})";
json js = json::parse(receivedStr);
int id = js["id"];
std::string name = js["name"];
3. 直接序列化 STL 容器
cpp
std::vector<int> vec = {1, 2, 3, 4};
json js;
js["list"] = vec;
std::map<int, std::string> m = {{1, "黄山"}, {2, "华山"}};
js["path"] = m;
六、JSON 在聊天服务器中的典型应用场景
假设你要发送一条聊天消息:
cpp
// 构造消息对象
json msg;
msg["msgid"] = 1; // 消息类型:聊天
msg["id"] = 1001;
msg["name"] = "张三";
msg["to"] = 1002;
msg["msg"] = "你好,李四!";
msg["time"] = "2023-10-01 12:00:00";
// 序列化并发送
std::string sendStr = msg.dump();
send(conn, sendStr.c_str(), sendStr.size(), 0);
服务器收到后解析:
cpp
std::string recvStr = receive(conn);
json recvMsg = json::parse(recvStr);
int msgid = recvMsg["msgid"];
std::string content = recvMsg["msg"];
// 处理消息...
基于muduo的客户端服务器编程
代码
cpp
//muduo网络库给用户提供了两个主要的类
//tcpserver 用于编写服务器程序的类
//tcpclient 用于编写客户端程序的类
//epoll+线程池
//好处:能够把网络I/O代码和业务代码区分开
//用户的连接和断开 用户的可读写事件
#include<muduo/net/TcpServer.h>
#include<muduo/net/EventLoop.h>
#include<functional>
#include<string>
#include<iostream>
using namespace std;
using namespace muduo;
using namespace muduo::net;
//基于muduo网络库开发服务器程序
//1.组合TcpServer对象
//2.创建EventLoop事件循环对象的指针
//3.明确服务器的构造函数需要什么参数,输出chatServer的构造函数
//4.在当前服务器类的构造函数当中,注册用户连接和断开回调函数,注册用户读写事件回调函数
//5.设置合适的服务端线程数量,muduo库会自动分配I/O线程和worker线程
class chatServer{
private:
//专门处理用户连接创建和断开 epoll listenfd accept
void onConnection(const TcpConnectionPtr& conn){// TcpConnectionPtr是muduo封装的智能指针,指向一个TCP连接对象
if(conn->connected()){
// 输出客户端上线信息:客户端IP端口 --> 服务器IP端口
// peerAddress():获取对等方(客户端)的地址信息
// localAddress():获取本地(服务器)的地址信息
// toIpPort():将地址信息转换为 "IP:Port" 字符串格式
cout<<conn->peerAddress().toIpPort()<<"-->"<<conn->localAddress().toIpPort()<<" online " <<endl;
}
else{
cout<<conn->peerAddress().toIpPort()<<"-->"<<conn->localAddress().toIpPort()<<" offline " <<endl;
conn->shutdown();//等价于 close(fd)
//loop_->quit(); // 可选:如果需要在客户端断开时退出事件循环,可启用
}
}
// conn:当前通信的TCP连接对象
// buf:muduo封装的缓冲区,存储客户端发送的数据
// time:接收到数据的时间戳
void onMessage(const TcpConnectionPtr& conn,Buffer* buf,Timestamp time){
string buffer=buf->retrieveAllAsString();
cout<<"recv data:"<<buffer<<" time:"<<time.toString()<<endl;
conn->send(buffer);
}
TcpServer server_; //1
EventLoop* loop_; //2 epoll
public:
chatServer(EventLoop* loop, //事件循环
const InetAddress& listenAddr,//IP+port
const string& nameArg) //服务器的名字
:server_(loop,listenAddr,nameArg),loop_(loop) {
//给服务器注册用户连接的创建和断开回调函数 当有新连接/断开连接时,触发onConnection
// bind作用:将类成员函数onConnection绑定到当前对象(this),_1是占位符(接收TcpConnectionPtr参数)
server_.setConnectionCallback(bind(&chatServer::onConnection,this,_1));
//给服务器注册用户读写事件回调函数
server_.setMessageCallback(bind(&chatServer::onMessage,this,_1,_2,_3));// _1/_2/_3分别对应onMessage的三个参数:conn, buf, time
//设置服务器的线程数量 1个I/O线程 3个worker线程
server_.setThreadNum(4);
}
//开启事件循环
void start(){
server_.start();
}
};
int main(){
EventLoop loop; //epoll
InetAddress addr("127.0.0.1",6000);
chatServer server(&loop,addr,"chatServer");
server.start();//listenfd epoll_ctl => epoll
loop.loop();//epoll_wait以阻塞方式等待新用户连接,已连接用户的读写事件等
return 0;
}

1. 头文件与命名空间(基础依赖)
cpp
#include<muduo/net/TcpServer.h> // Muduo 服务器核心类,封装监听/连接管理/线程池
#include<muduo/net/EventLoop.h> // Reactor 核心,负责 epoll 事件循环
#include<functional> // 用于 std::bind 绑定类成员函数为回调
#include<string>
#include<iostream>
using namespace muduo; // 简化 muduo 库命名
using namespace muduo::net; // 简化 muduo 网络模块命名
using namespace std; // 简化标准库命名
- TcpServer:Muduo 封装的 TCP 服务器,无需手动处理 epoll/accept,只需注册回调即可;
- EventLoop:Reactor 模式的核心,每个线程最多一个,负责事件监听、分发;
- functional:因为类的成员函数有隐含的 this 指针,需用 std::bind 绑定后才能适配 Muduo 的回调类型。
2. ChatServer 类定义(核心封装)
cpp
class ChatServer
{
// 成员变量:
TcpServer server_; // Muduo 服务器对象(核心)
EventLoop* loop_; // Reactor 事件循环指针(指向主线程的 EventLoop)
// 私有成员函数(回调函数):
void onConnection(const TcpConnectionPtr&); // 连接状态变化回调
void onMessage(const TcpConnectionPtr&,Buffer*,Timestamp); // 数据读写回调
public:
// 构造函数:初始化服务器对象、绑定回调、设置线程池
ChatServer(EventLoop*loop, const InetAddress& listenAddr,const string& nameArg);
void start(); // 启动服务器
};
- 成员变量设计:
server_:TcpServer 是服务器的核心载体,封装了监听、连接管理、线程调度;
loop_:指向主线程的 EventLoop,传递给 TcpServer 后,服务器的 Acceptor(监听模块)会运行在该 EventLoop 中。
- 回调函数声明:
onConnection:处理 "连接建立 / 断开" 事件;
onMessage:处理 "数据接收" 事件,参数包含连接对象、数据缓冲区、接收时间戳(Muduo 预定义的回调参数格式)。
3. onConnection 回调函数(连接事件处理)
cpp
void ChatServer::onConnection(const TcpConnectionPtr &conn)
{
if (conn->connected()) // 判断连接是否建立(三次握手完成)
{
printf("ChatServer - %s connected\n", conn->peerAddress().toIpPort().c_str());
}
else // 连接断开(四次挥手/网络异常)
{
printf("ChatServer - %s disconnected\n", conn->peerAddress().toIpPort().c_str());
conn->shutdown(); // 主动关闭连接(半关闭写端,释放资源)
}
}
- 核心参数 TcpConnectionPtr:是 std::shared_ptr<TcpConnection> 的别名,Muduo 用智能指针管理连接生命周期,避免野指针;
- 关键接口:
conn->connected():判断当前连接是否处于 "已建立" 状态;
conn->peerAddress():获取客户端的 IP:Port(InetAddress 类型),toIpPort() 转为字符串;
conn->shutdown():主动关闭连接(关闭写端,触发 TCP 四次挥手,避免资源泄漏)。
4. onMessage 回调函数(数据读写处理)
cpp
void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time)
{
string buff=buffer->retrieveAllAsString(); // 读取缓冲区所有数据并清空
printf("ChatServer received message:%s\n", buff.c_str());
conn->send(buff); // 回显数据给客户端(异步发送)
}
- 参数解析:
Buffer* buffer:Muduo 封装的缓冲区,解决 TCP 粘包 / 拆包问题(流式协议无消息边界);
Timestamp time:数据接收完成的时间戳(示例中未使用,可用于日志 / 性能统计);
- 核心操作:
buffer->retrieveAllAsString():读取缓冲区中所有可读数据,转为字符串,并移动读指针(清空缓冲区);
conn->send(buff):异步发送数据给客户端 ------ 若数据能一次性写入 socket,则直接发送;若写满,则存入写缓冲区,等待 EPOLLOUT 事件触发后继续发送(Muduo 自动处理,无需手动关注非阻塞写)。
5. ChatServer 构造函数(初始化核心逻辑)
cpp
ChatServer::ChatServer(EventLoop *loop, const InetAddress &listenAddr, const string &nameArg)
: loop_(loop), server_(loop, listenAddr, nameArg) // 初始化列表
{
// 绑定连接回调:将类成员函数 onConnection 转为 Muduo 可识别的回调类型
server_.setConnectionCallback(
std::bind(&ChatServer::onConnection,this,_1)
);
// 绑定消息回调:将类成员函数 onMessage 转为 Muduo 可识别的回调类型
server_.setMessageCallback(
std::bind(&ChatServer::onMessage,this,_1,_2,_3)
);
server_.setThreadNum(4); // 设置工作线程池大小为 4
}
- 初始化列表注意点:类中 server_ 声明在 loop_ 之前,但初始化列表先初始化 loop_、后初始化 server_------ 编译器会触发 "成员初始化顺序不匹配" 警告(不影响功能,但建议调整类成员声明顺序:先 loop_,后 server_)。
- 回调绑定核心(std::bind):Muduo 的回调类型是 std::function,而类的成员函数有隐含的 this 指针,需用 std::bind 绑定:
this:指向当前 ChatServer 对象,保证回调能调用到当前对象的成员函数;
_1/_2/_3:占位符,对应回调函数的参数(onConnection 有 1 个参数,onMessage 有 3 个参数)。
- setThreadNum(4):设置 Muduo 的工作线程池大小为 4------Muduo 采用 "单 Reactor 多线程" 模型:
主线程(Reactor 线程):处理 Acceptor 监听事件(新连接建立);
4 个工作线程:处理已建立连接的读写事件(onMessage 运行在工作线程)。
6. start 成员函数(启动服务器)
cpp
void ChatServer::start()
{
server_.start(); // 调用 TcpServer::start(),底层启动 Acceptor 监听端口 + 启动工作线程池
}
TcpServer::start()做两件核心事:- 启动
Acceptor:调用listen()监听指定端口,开始接收新连接; - 启动线程池:创建 4 个工作线程,每个线程绑定一个
EventLoop,用于处理连接的读写事件。
- 启动
7. main 函数(程序入口)
cpp
int main(){
EventLoop loop; // 创建主线程的 EventLoop(Reactor 核心,阻塞运行)
InetAddress addr("127.0.0.1", 6000); // 监听地址:127.0.0.1:6000
ChatServer server(&loop, addr, "ChatServer"); // 创建服务器对象
server.start(); // 启动服务器
loop.loop(); // 启动 Reactor 事件循环(阻塞,直到退出)
return 0;
}
- 核心流程:
- EventLoop loop:主线程创建 EventLoop,是整个服务器的 "心脏",负责 epoll 事件监听 / 分发;
- InetAddress addr:指定服务器监听的 IP 和端口(127.0.0.1 表示仅本地可访问,若需外网访问改为 0.0.0.0);
- ChatServer server:初始化服务器,绑定回调、设置线程池;
- server.start():启动监听和线程池;
- loop.loop():进入无限循环,调用 epoll_wait 等待事件(新连接、数据读写、信号等),并分发事件到对应回调 ------这行代码会阻塞主线程,直到服务器退出。
连接测试
