目录
[一、muduo 介绍](#一、muduo 介绍)
[1. TcpServer 构造函数](#1. TcpServer 构造函数)
[2. setConnectionCallback:设置连接回调](#2. setConnectionCallback:设置连接回调)
[3. setMessageCallback:设置消息回调](#3. setMessageCallback:设置消息回调)
[4. start ():启动服务器](#4. start ():启动服务器)
[5. EventLoop::loop ():启动事件循环](#5. EventLoop::loop ():启动事件循环)
[1. TcpClient 构造函数](#1. TcpClient 构造函数)
[2. connect ():发起连接](#2. connect ():发起连接)
[3. TcpConnection::send ():发送数据](#3. TcpConnection::send ():发送数据)
[1. onConnection:连接状态回调](#1. onConnection:连接状态回调)
[2. onMessage:收到消息回调](#2. onMessage:收到消息回调)
[3. onUnknownMessage:未知消息兜底回调](#3. onUnknownMessage:未知消息兜底回调)
[六、Buffer 类常用接口](#六、Buffer 类常用接口)
[1. retrieveAllAsString()](#1. retrieveAllAsString())
[2. append()](#2. append())
[3. retrieve()](#3. retrieve())
[七、基于 muduo 的中英互译示例](#七、基于 muduo 的中英互译示例)
[八、muduo 常见坑](#八、muduo 常见坑)
一、muduo 介绍
muduo 是陈硕大神基于 C++11 编写的高性能多线程 Reactor 模式网络库,专为 Linux 平台设计。
- 核心特点:one loop per thread(一个线程一个事件循环)、非阻塞 IO、线程安全、接口简洁
- 适用场景:TCP 服务器 / 客户端、RPC 框架、即时通讯、游戏服务器等高性能网络应用
二、核心概念
EventLoop:事件循环,muduo 的心脏。所有网络事件(连接、消息、断开)都在它里面处理,一个线程只能有一个 EventLoopTcpServer:TCP 服务器入口,负责监听端口、接受新连接TcpClient:TCP 客户端入口,负责发起连接TcpConnection:代表一个已建立的 TCP 连接,是收发数据的核心Buffer:muduo 自带的网络数据缓冲区,解决 TCP 粘包问题- 回调函数 :muduo 是事件驱动的,所有逻辑都通过回调函数实现,这是 muduo 编程的核心
三、服务器端常用接口
完整实例代码放在第七点
1. TcpServer 构造函数
作用:创建一个 TCP 服务器实例
cpp
// 完整签名
TcpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& nameArg,
Option option = kNoReusePort);
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
loop |
EventLoop* |
事件循环指针,服务器的所有事件都在这个 loop 中处理 |
listenAddr |
const InetAddress& |
监听的地址和端口 |
nameArg |
const string& |
服务器名称,用于日志区分多个实例 |
option |
Option |
端口选项,kReusePort开启端口复用 |
cpp// 端口选项枚举 enum Option { kNoReusePort, // 不开启端口复用 kReusePort // 开启端口复用(推荐) };
示例
cpp
// 翻译服务器构造函数
TranslateServer(int port)
// 监听0.0.0.0:port,服务器名"Translate",开启端口复用
: _server(&_baseloop, muduo::net::InetAddress("0.0.0.0", port),
"Translate", muduo::net::TcpServer::kReusePort)
{}
注意事项
- 必须传入非空的 EventLoop 指针
- 监听地址写
"0.0.0.0"表示监听所有网卡,写"127.0.0.1"表示仅本地可访问- 强烈建议开启
kReusePort,避免服务器重启时出现 "地址已被占用" 的错误- 不要在多个线程中共享同一个 TcpServer 实例
2. setConnectionCallback:设置连接回调
作用:注册连接状态变化的回调函数,当新连接建立或旧连接断开时自动调用
cpp
// 完整签名
void setConnectionCallback(const ConnectionCallback& cb);
// ConnectionCallback 类型定义
using ConnectionCallback = std::function<void(const TcpConnectionPtr&)>;
参数说明
cb:回调函数,参数是当前 TCP 连接的智能指针TcpConnectionPtr
示例
cpp
// 绑定类成员函数作为回调
_server.setConnectionCallback(
std::bind(&TranslateServer::onConnection, this, std::placeholders::_1)
);
// 回调函数实现
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if (conn->connected())
{
std::cout << "新连接建立成功:" << conn->peerAddress().toIpPort() << std::endl;
}
else
{
std::cout << "连接断开:" << conn->peerAddress().toIpPort() << std::endl;
}
}
注意事项
- 绑定类成员函数时,必须传入
this指针,_1是占位符,对应回调的第一个参数- 通过
conn->connected()可以判断是连接建立 还是连接断开- 回调函数会在 EventLoop 线程中执行,绝对不能执行耗时操作
- 不要手动 delete
TcpConnectionPtr指向的对象,它的生命周期由 muduo 自动管理
3. setMessageCallback:设置消息回调
作用:注册收到消息的回调函数,当对端发送数据到达时自动调用
cpp
// 完整签名
void setMessageCallback(const MessageCallback& cb);
// MessageCallback 类型定义
using MessageCallback = std::function<void(const TcpConnectionPtr&, Buffer*, Timestamp)>;
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
conn |
const TcpConnectionPtr& |
当前 TCP 连接 |
buffer |
Buffer* |
收到的数据缓冲区 |
receiveTime |
Timestamp |
消息到达的时间戳 |
示例
cpp
// 绑定消息回调
_server.setMessageCallback(
std::bind(&TranslateServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
);
// 回调函数实现
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buffer,
muduo::Timestamp)
{
// 1. 从缓冲区取出所有数据并转为字符串
std::string data = buffer->retrieveAllAsString();
// 2. 业务处理:中英互译
std::string resp = translate(data);
// 3. 向对端发送响应
conn->send(resp);
}
注意事项
- 必须调用
buffer->retrieve*()系列方法消费数据,否则缓冲区会累积数据,导致回调被重复触发receiveTime可用于统计网络延迟- 不要在这个回调中执行耗时操作(如数据库查询、复杂计算),否则会阻塞整个事件循环
- 不要手动修改 Buffer 的读写指针,使用 muduo 提供的方法操作
4. start ():启动服务器
作用:开始监听端口,接受新连接
cpp
// 完整签名
void start();
示例
cpp
void start()
{
_server.start(); // 启动服务器监听
_baseloop.loop(); // 启动事件循环(阻塞调用)
}
注意事项
start()是非阻塞调用,真正的事件处理在loop()中- 必须在
loop()之前调用start()- 不要多次调用
start()
5. EventLoop::loop ():启动事件循环
作用:进入无限循环,等待并处理所有网络事件
cpp
// 完整签名
void loop();
注意事项
- 这是一个阻塞调用 ,会一直运行直到调用
quit()- 必须在创建 EventLoop 的线程 中调用
loop()- 不要在回调函数中调用
loop(),会导致死循环
四、客户端常用接口
完整示例在第七点
1. TcpClient 构造函数
作用:创建一个 TCP 客户端实例
cpp
// 完整签名
TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const string& nameArg);
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
loop |
EventLoop* |
客户端的事件循环 |
serverAddr |
const InetAddress& |
服务器的地址和端口 |
nameArg |
const string& |
客户端名称,用于日志 |
示例
cpp
// 客户端构造函数
TranslateClient(const std::string& sid, int port)
: _latch(1),
// 用EventLoopThread单独启动一个事件循环线程
_client(&_loopthread.startLoop(),
muduo::net::InetAddress(sid, port),
"TranslateClient")
{}
注意事项
- 客户端通常用
EventLoopThread单独启动一个事件循环线程,避免阻塞主线程_loopthread.startLoop()会创建一个新线程,并返回该线程的 EventLoop 指针
2. connect ():发起连接
作用:异步向服务器发起 TCP 连接
cpp
// 完整签名
void connect();
示例
cpp
void connect()
{
_client.connect(); // 异步发起连接,立即返回
_latch.wait(); // 阻塞等待连接成功(用CountDownLatch同步)
}
// 在onConnection回调中唤醒等待
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if (conn->connected())
{
_conn = conn; // 保存连接指针
_latch.countDown(); // 计数减1,唤醒wait()
}
else
{
_conn.reset();
}
}
注意事项
connect()是异步调用 ,不会阻塞,连接成功 / 失败会通过onConnection回调通知- 如果需要同步等待连接结果,使用
CountDownLatch(如你的代码所示)- 不要在主线程中循环轮询连接状态,会浪费 CPU
3. TcpConnection::send ():发送数据
作用:向对端发送数据
cpp
// 常用签名
void send(const std::string& message);
参数说明
message:要发送的字符串数据
示例
cpp
bool send(const std::string& msg)
{
if (_conn && _conn->connected())
{
_conn->send(msg); // 发送数据
return true;
}
return false;
}
注意事项
send()是线程安全的,可以在任意线程调用- 数据会被拷贝到 muduo 的输出缓冲区,由 EventLoop 线程负责实际发送
- 如果连接断开,
send()会静默丢弃数据,所以发送前最好判断connected()
五、核心回调函数
muduo 的编程模型完全基于回调,掌握这几个回调,就掌握了 muduo 的 基础。
1. onConnection:连接状态回调
触发时机:
- 新连接三次握手完成,连接建立成功
- 连接四次挥手完成,连接断开
核心用途:
- 记录连接日志
- 初始化连接上下文
- 同步连接状态(如客户端的 CountDownLatch)
注意事项:
- 连接断开时,
TcpConnectionPtr会自动释放资源,不要手动处理
2. onMessage:收到消息回调
触发时机:
当对端发送的数据到达本端的内核缓冲区,并被 muduo 读取到用户态 Buffer 中时
核心用途:
- 解析网络数据
- 处理业务逻辑
- 发送响应数据
注意事项:
- 必须消费 Buffer 中的数据,否则会导致内存泄漏和重复回调
- 耗时操作要放到线程池中处理,不要阻塞事件循环
3. onUnknownMessage:未知消息兜底回调
作用:消息分发器的兜底处理,当收到未注册对应处理逻辑的未知类型消息时调用
cpp
// 完整签名
void onUnknownMessage(const TcpConnectionPtr& conn,
const MessagePtr& message,
Timestamp);
示例
cs
void onUnknownMessage(const muduo::net::TcpConnectionPtr& conn,
const MessagePtr& message,
muduo::Timestamp)
{
// 记录未知消息类型,方便排查问题
LOG_INFO << "收到未知消息:" << message->GetTypeName();
// 关闭连接,防御性编程
conn->shutdown();
}
注意事项
- 这是防御性编程的重要手段,避免非法消息导致程序崩溃
- 建议所有使用自定义消息协议的项目都实现这个回调
- 优先使用
shutdown()优雅关闭连接,而不是forceClose()
六、Buffer 类常用接口
muduo 的 Buffer 常用来解决 TCP 粘包问题,最常用的接口有:
1. retrieveAllAsString()
作用:取出缓冲区中所有可读数据,并转为字符串,同时清空缓冲区
cpp
std::string retrieveAllAsString();
示例:
cpp
std::string data = buffer->retrieveAllAsString();
2. append()
作用:向缓冲区追加数据
cpp
void append(const std::string& data);
3. retrieve()
作用:移动读指针,消费指定长度的数据
cpp
void retrieve(size_t len);
七、基于 muduo 的中英互译示例
服务器端
cpp
#include "../include/muduo/net/EventLoop.h"
#include "../include/muduo/net/TcpServer.h"
#include "../include/muduo/net/TcpConnection.h"
#include <functional>
#include <unordered_map>
#include <iostream>
#include <string>
class TranslateServer
{
public:
TranslateServer(int port)
:_server(&_baseloop,muduo::net::InetAddress("0.0.0.0",port),"Translate",muduo::net::TcpServer::kReusePort)
{
// 将onConnection成员函数设置为回调函数,采用bind进行参数绑定以解决参数数量不匹配问题
_server.setConnectionCallback(std::bind(&TranslateServer::onConnection,this,std::placeholders::_1));
// 将onMessage成员函数设置为回调函数
_server.setMessageCallback(std::bind(&TranslateServer::onMessage,this,std::placeholders:: _1,std::placeholders::_2,std::placeholders::_3));
}
// 启动服务器
void start()
{
_server.start(); // 启动事件监听
_baseloop.loop(); // 启动事件监控,是死循环式的阻塞接口
}
private:
std::string translate(const std::string& str)
{
static std::unordered_map<std::string,std::string> dict_map = {
{"hello","你好"},
{"apple","苹果"},
{"你好","hello"},
{"苹果","apple"}
};
auto it = dict_map.find(str);
if(it == dict_map.end())
{
return "未找到词义";
}
return it->second;
}
// 新连接建立成功时的回调函数,在连接建立成功和连接释放时自动调用
void onConnection(const muduo::net::TcpConnectionPtr& conn)
{
if(conn->connected() == true)
{
std::cout << "连接建立成功" << std::endl;
}
else
{
std::cout << "连接建立失败" << std::endl;
}
}
// 通信连接收到请求时的回调函数
void onMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buffer,muduo::Timestamp)
{
// 1.从buffer中读取数据
std::string data = buffer->retrieveAllAsString();
// 2.处理数据
std::string resp = translate(data);
// 3.给客户端返回响应结果
conn->send(resp);
}
private:
muduo::net::EventLoop _baseloop;// 是epoll的事件监控,会进行描述符的事件监控,在事件触发后进行IO操作
muduo::net::TcpServer _server; // 主要用于设置回调函数,告诉服务器收到请求该如何处理
};
int main()
{
TranslateServer server(8085);
server.start();
return 0;
}
客户端
cpp
#include "../include/muduo/net/EventLoopThread.h"
#include "../include/muduo/net/TcpClient.h"
#include "../include/muduo/net/TcpConnection.h"
#include <functional>
#include <iostream>
#include <string>
class TranslateClient
{
public:
TranslateClient(const std::string& sid,int port)
:_latch(1),_client(_loopthread.startLoop(),muduo::net::InetAddress(sid,port),"TranslateClient")
{
// 将onConnection成员函数设置为回调函数,采用bind进行参数绑定以解决参数数量不匹配问题
_client.setConnectionCallback(std::bind(&TranslateClient::onConnection,this,std::placeholders::_1));
// 将onMessage成员函数设置为回调函数
_client.setMessageCallback(std::bind(&TranslateClient::onMessage,this,std::placeholders:: _1,std::placeholders::_2,std::placeholders::_3));
}
// 负责连接服务器(本身是非阻塞的),需要等待连接成功才能返回,通过CountDownLatch类实现
void connect()
{
_client.connect();
// 阻塞等待,直到连接成功(由连接成功时的回调函数来判断)
_latch.wait();
}
// 负责发送数据
bool send(const std::string& msg)
{
if(_conn->connected())
{
// 连接状态正常才发送
_conn->send(msg);
return true;
}
return false;
}
private:
// 新连接建立成功时的回调函数,连接成功后,唤醒上面的阻塞
void onConnection(const muduo::net::TcpConnectionPtr& conn) // 参数需要const修饰
{
if(conn->connected())
{
_latch.countDown(); // 唤醒阻塞
_conn = conn;
}
else
{
// 连接建立失败
_conn.reset();
}
}
// 通信连接收到消息时的回调函数
void onMessage(const muduo::net::TcpConnectionPtr& conn,muduo::net::Buffer* buffer,muduo::Timestamp)
{
std::cout << "翻译结果:" << buffer->retrieveAllAsString() << std::endl;
}
private:
muduo::CountDownLatch _latch; // 负责同步控制
muduo::net::EventLoopThread _loopthread; // 事件监控采用单独线程,以免阻塞主线程
muduo::net::TcpClient _client;
muduo::net::TcpConnectionPtr _conn;
};
int main()
{
TranslateClient client("127.0.0.1",8085);
client.connect();
while(1)
{
std::string buffer;
std::cin >> buffer;
client.send(buffer);
}
return 0;
}
编译运行
bash
# 编译服务器
g++ server.cpp -o server -lmuduo_net -lmuduo_base -lpthread
# 编译客户端
g++ client.cpp -o client -lmuduo_net -lmuduo_base -lpthread
# 运行服务器
./server
# 运行客户端
./client
关于示例中绑定bind的理解
绑定 = 给「成员函数」找个家
C++ 里有两种函数:
- 全局函数 / 静态函数:无家可归,谁都能直接调用
- 成员函数 :有家(属于某个对象),必须知道「是哪个对象的函数」才能调用
而 muduo 网络库的回调接口,只接受「无家可归」的函数,不认识「有家的成员函数」。
std::bind的作用就是:把「成员函数 + 它所属的对象(this)」打包成一个 muduo 能识别的回调函数。
为什么成员函数不能直接当回调?
// 错误!编译直接报错
_server.setConnectionCallback(&Server::onConnection);
为什么错?Server::onConnection 是成员函数 ,它属于 Server 对象,调用它必须知道:
是「哪个 Server 实例」的
onConnection?
单独传函数地址,没有 this 指针,编译器根本不知道调用谁,直接报错。
绑定到底做了什么
以这行为例:
cpp
// 正确:绑定 this + 成员函数
_server.setConnectionCallback(
std::bind(&Server::onConnection, this, std::placeholders::_1)
);
std::bind 一次性做了 3 个操作:
1. 固定「调用对象」(核心)
this= 当前正在运行的Server对象告诉编译器:调用
onConnection时,就用这个 Server 对象2. 预留「回调参数」
std::placeholders::_1= 占位符muduo 收到新连接时,会自动把「连接对象」传给这个位置。3. 生成「通用回调」
把上面两个打包,生成一个 muduo 能直接调用的函数,完美适配回调接口。
为什么要绑定?
为了让「属于对象的成员函数」,能被 muduo 当成普通回调函数使用
- 不绑定:成员函数缺
this,无法调用 → 编译报错- 绑定后:函数有了归属对象,参数位置留好 → muduo 可以正常触发回调
这里绑定的 2 个核心作用
作用 1:让回调能访问业务对象的成员变量
绑定
this后,onConnection里才能访问对应的成员变量没有绑定,这些成员变量根本访问不到
作用 2:统一回调格式,适配 muduo 接口
muduo 的所有回调都有固定格式,
bind能把业务函数强行适配成 muduo 要的格式。
八、muduo 常见坑
- 绝对不要在回调函数中执行耗时操作:会阻塞整个事件循环,导致所有连接都卡住
- TcpConnection 的生命周期由 muduo 管理:不要手动 delete,使用智能指针即可
- EventLoop 有严格的线程归属:所有 EventLoop 的方法都必须在创建它的线程中调用
- 关闭连接 :优先使用
conn->shutdown()(半关闭),只有在异常情况下才用conn->forceClose()- 处理 TCP 粘包:不要假设一次 recv 就能收到完整的消息,使用 Buffer 和自定义协议头来分包
- 日志排查问题:muduo 的日志系统非常完善,遇到问题先看日志
九、总结
muduo 的接口设计非常简洁,核心就是三个回调 + 两个入口:
- 服务器端:
TcpServer+onConnection+onMessage- 客户端:
TcpClient+onConnection+onMessage
muduo其它接口
https://blog.csdn.net/2301_81298637/article/details/159752471?spm=1001.2014.3001.5502
muduo核心类https://blog.csdn.net/2301_81298637/article/details/159755916?spm=1001.2014.3001.5502
感谢阅读,本文如有错漏之处,烦请斧正。