目录
[一、EventLoop 补充接口](#一、EventLoop 补充接口)
[1. runInLoop ():在 EventLoop 线程中执行任务](#1. runInLoop ():在 EventLoop 线程中执行任务)
[2. queueInLoop ():将任务放入队列异步执行](#2. queueInLoop ():将任务放入队列异步执行)
[3. quit ():退出事件循环](#3. quit ():退出事件循环)
[二、TcpConnection 补充接口](#二、TcpConnection 补充接口)
[1. forceClose ():强制关闭连接](#1. forceClose ():强制关闭连接)
[2. setTcpNoDelay ():禁用 Nagle 算法](#2. setTcpNoDelay ():禁用 Nagle 算法)
[3. getLoop ():获取所属 EventLoop](#3. getLoop ():获取所属 EventLoop)
[4. peerAddress () & localAddress ():获取连接地址](#4. peerAddress () & localAddress ():获取连接地址)
[三、Buffer 补充接口](#三、Buffer 补充接口)
[1. peek ():查看可读数据(不消费)](#1. peek ():查看可读数据(不消费))
[2. readableBytes () & writableBytes ():获取缓冲区大小](#2. readableBytes () & writableBytes ():获取缓冲区大小)
[3. retrieveAsString ():读取指定长度数据](#3. retrieveAsString ():读取指定长度数据)
[4. prepend ():在可读数据前追加数据](#4. prepend ():在可读数据前追加数据)
[1. runAfter ():一次性定时器](#1. runAfter ():一次性定时器)
[2. runEvery ():周期性定时器](#2. runEvery ():周期性定时器)
[3. cancel ():取消定时器](#3. cancel ():取消定时器)
[1. EventLoopThread:单独的 IO 线程](#1. EventLoopThread:单独的 IO 线程)
[2. EventLoopThreadPool:IO 线程池](#2. EventLoopThreadPool:IO 线程池)
[六、InetAddress 网络地址接口](#六、InetAddress 网络地址接口)
[1. 构造函数](#1. 构造函数)
[2. toIpPort() & toIp() & port()](#2. toIpPort() & toIp() & port())
[八、muduo 常用接口总结](#八、muduo 常用接口总结)
一、EventLoop 补充接口
EventLoop 是 muduo 的核心,除了基础的
loop()外,还有几个非常实用的接口。
1. runInLoop ():在 EventLoop 线程中执行任务
作用:将一个回调函数投递到 EventLoop 线程中执行,是 muduo 线程安全的核心机制
// 完整签名
void runInLoop(Functor cb);
参数说明
cb:要执行的回调函数,类型为std::function<void()>
示例
cpp
// 场景:在非IO线程中向连接发送数据
void sendDataInLoop(const TcpConnectionPtr& conn, const std::string& msg)
{
// 获取该连接所属的EventLoop
EventLoop* loop = conn->getLoop();
// 将发送操作投递到IO线程执行
loop->runInLoop(
[conn, msg]()
{
conn->send(msg);
}
);
}
注意事项
- 线程安全:这是 muduo 中跨线程操作的标准做法
- 如果当前线程就是 EventLoop 线程,回调会立即同步执行
- 如果是其他线程,回调会被放入队列,等待 EventLoop 线程异步执行
- 不要在回调中执行耗时操作,会阻塞事件循环
2. queueInLoop ():将任务放入队列异步执行
作用 :将回调函数放入 EventLoop 的任务队列,总是异步执行,不会立即调用
// 完整签名
void queueInLoop(Functor cb);
与 runInLoop 的区别
runInLoop():如果在当前线程,立即执行;否则放入队列queueInLoop():总是放入队列,异步执行
示例
cpp
void asyncTask(EventLoop* loop)
{
loop->queueInLoop(
[]()
{
std::cout << "这个任务会异步执行" << std::endl;
}
);
std::cout << "queueInLoop调用立即返回" << std::endl;
}
注意事项
- 适用于不需要立即执行的任务
- 可以避免在当前调用栈中执行复杂逻辑
3. quit ():退出事件循环
作用 :停止 EventLoop 的
loop()循环
cpp
// 完整签名
void quit();
示例
cpp
// 场景:运行10秒后自动退出服务器
void autoExit(EventLoop* loop)
{
std::this_thread::sleep_for(std::chrono::seconds(10));
loop->quit(); // 线程安全地退出事件循环
}
注意事项
- 线程安全:可以在任意线程调用
- 调用后,
loop()会在处理完当前事件后退出- 不要在 EventLoop 线程的回调中调用
quit()后继续执行耗时操作
二、TcpConnection 补充接口
TcpConnection 代表一个 TCP 连接,除了
send()和shutdown()外,还有这些常用接口。
1. forceClose ():强制关闭连接
作用:立即强制关闭 TCP 连接,发送 RST 包
// 完整签名
void forceClose();
与 shutdown () 的区别
shutdown():优雅关闭,先发送 FIN 包,等待对端确认forceClose():强制关闭,直接发送 RST 包,立即断开
示例
cpp
void onMessage(const TcpConnectionPtr& conn, Buffer* buffer, Timestamp)
{
std::string data = buffer->retrieveAllAsString();
if (data == "bad_request")
{
// 收到非法请求,强制关闭连接
conn->forceClose();
}
}
注意事项
- 适用于异常情况(如收到恶意数据、协议错误)
- 正常情况下优先使用
shutdown()优雅关闭
2. setTcpNoDelay ():禁用 Nagle 算法
作用:设置 TCP_NODELAY 选项,禁用 Nagle 算法,降低延迟
// 完整签名
void setTcpNoDelay(bool on);
参数说明
on:true禁用 Nagle 算法(低延迟),false启用(高吞吐)
示例
cpp
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
conn->setTcpNoDelay(true); // 禁用Nagle算法,降低消息延迟
std::cout << "连接建立,已禁用Nagle算法" << std::endl;
}
}
注意事项
- 适用于对延迟敏感的场景(如游戏、即时通讯)
- 禁用后会增加小包数量,可能降低吞吐量
- 不要在大文件传输场景禁用 Nagle 算法
3. getLoop ():获取所属 EventLoop
作用:获取该连接所属的 EventLoop 指针,用于跨线程操作
cpp
// 完整签名
EventLoop* getLoop() const;
示例
cpp
// 场景:在工作线程中处理完业务后,在IO线程发送响应
void processBusiness(const TcpConnectionPtr& conn, const std::string& req)
{
// 模拟耗时的业务处理(在工作线程执行)
std::string resp = process(req);
// 获取IO线程的EventLoop
EventLoop* loop = conn->getLoop();
// 将发送操作投递回IO线程
loop->runInLoop(
[conn, resp]()
{
conn->send(resp);
}
);
}
注意事项
- 这是 muduo 多线程编程的标准模式:one loop per thread + thread pool
- 所有对 TcpConnection 的操作都必须在其所属的 EventLoop 线程中执行
4. peerAddress () & localAddress ():获取连接地址
作用:获取对端和本端的网络地址
cpp
// 完整签名
const InetAddress& peerAddress() const;
const InetAddress& localAddress() const;
示例
cpp
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
std::cout << "客户端连接:"
<< conn->peerAddress().toIpPort() // 对端地址
<< " -> "
<< conn->localAddress().toIpPort() // 本端地址
<< std::endl;
}
}
三、Buffer 补充接口
Buffer 是 muduo 解决 TCP 粘包问题的核心,除了基础接口外,还有这些实用方法。
1. peek ():查看可读数据(不消费)
作用 :获取可读数据的指针,但不移动读指针,数据不会被消费
cpp
// 完整签名
const char* peek() const;
示例
cpp
// 场景:先查看数据长度,再决定如何读取
void onMessage(const TcpConnectionPtr& conn, Buffer* buffer, Timestamp)
{
// 假设协议是:4字节长度 + 数据
while (buffer->readableBytes() >= 4)
{
// 先查看前4字节(不消费)
const char* lenPtr = buffer->peek();
int32_t len = *reinterpret_cast<const int32_t*>(lenPtr);
// 检查数据是否足够
if (buffer->readableBytes() >= 4 + len)
{
// 数据足够,消费4字节长度
buffer->retrieve(4);
// 读取实际数据
std::string data = buffer->retrieveAsString(len);
// 处理数据
processData(data);
}
else
{
break; // 数据不够,等待下次回调
}
}
}
注意事项
- 适用于需要先解析协议头,再决定读取多少数据的场景
- 不会修改 Buffer 的状态
2. readableBytes () & writableBytes ():获取缓冲区大小
作用:获取当前可读和可写的字节数
cpp
// 完整签名
size_t readableBytes() const;
size_t writableBytes() const;
示例
cpp
void checkBufferStatus(Buffer* buffer)
{
std::cout << "可读字节数:" << buffer->readableBytes() << std::endl;
std::cout << "可写字节数:" << buffer->writableBytes() << std::endl;
}
3. retrieveAsString ():读取指定长度数据
作用:读取指定长度的数据并转为字符串,同时移动读指针
cpp
// 完整签名
std::string retrieveAsString(size_t len);
示例
cpp
// 读取前100个字节
std::string data = buffer->retrieveAsString(100);
4. prepend ():在可读数据前追加数据
作用 :在缓冲区的可读区域开头插入数据,用于在数据前添加协议头
cpp
// 完整签名
void prepend(const void* data, size_t len);
示例
cpp
// 场景:发送数据前,在前面添加4字节长度头
void sendWithHeader(const TcpConnectionPtr& conn, const std::string& data)
{
Buffer buffer;
// 先写入数据
buffer.append(data);
// 在数据前插入4字节长度
int32_t len = static_cast<int32_t>(data.size());
buffer.prepend(&len, sizeof(len));
// 发送
conn->send(&buffer);
}
注意事项
- 这是构建带长度头协议的标准做法
- 比先写长度再写数据更高效,因为只需要一次内存拷贝
四、定时器接口
muduo 提供了强大的定时器功能,可以在 EventLoop 中注册定时任务。
1. runAfter ():一次性定时器
作用:在指定时间后执行一次回调
cpp
// 完整签名
TimerId runAfter(double delay, TimerCallback cb);
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
delay |
double |
延迟时间,单位:秒 |
cb |
TimerCallback |
定时器回调函数 |
返回值
TimerId:定时器 ID,可用于取消定时器
示例
cpp
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
EventLoop* loop = conn->getLoop();
// 5秒后发送一条心跳消息
loop->runAfter(5.0,
[conn]()
{
conn->send("heartbeat");
}
);
}
}
2. runEvery ():周期性定时器
作用:每隔指定时间执行一次回调,周期性执行
// 完整签名
TimerId runEvery(double interval, TimerCallback cb);
参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
interval |
double |
间隔时间,单位:秒 |
cb |
TimerCallback |
定时器回调函数 |
示例
cpp
// 场景:服务器启动后,每隔10秒打印一次连接数
class Server
{
public:
Server(EventLoop* loop, const InetAddress& addr)
: _server(loop, addr, "TimerServer"), _loop(loop), _connectionCount(0)
{
_server.setConnectionCallback(
std::bind(&Server::onConnection, this, std::placeholders::_1)
);
}
void start()
{
_server.start();
// 启动周期性定时器,每10秒执行一次
_loop->runEvery(10.0,
[this]()
{
printConnectionCount();
}
);
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
_connectionCount++;
}
else
{
_connectionCount--;
}
}
void printConnectionCount()
{
std::cout << "当前连接数:" << _connectionCount << std::endl;
}
TcpServer _server;
EventLoop* _loop;
int _connectionCount;
};
3. cancel ():取消定时器
作用:取消之前注册的定时器
// 完整签名
void cancel(TimerId timerId);
示例
cpp
class HeartbeatManager
{
public:
HeartbeatManager(EventLoop* loop) : _loop(loop) {}
void startHeartbeat(const TcpConnectionPtr& conn)
{
// 启动周期性心跳定时器
_timerId = _loop->runEvery(5.0,
[conn]()
{
conn->send("heartbeat");
}
);
}
void stopHeartbeat()
{
// 取消定时器
_loop->cancel(_timerId);
}
private:
EventLoop* _loop;
TimerId _timerId;
};
注意事项
- 只能取消还未执行的一次性定时器,或正在运行的周期性定时器
- 如果定时器已经执行,取消操作无效
五、线程相关接口
muduo 的多线程模型是 "one loop per thread",提供了以下线程工具类。
1. EventLoopThread:单独的 IO 线程
作用:创建一个新线程,在该线程中运行 EventLoop
// 核心接口
EventLoop* startLoop(); // 启动线程并返回EventLoop指针
示例
cpp
// 场景:客户端使用单独的IO线程
class Client
{
public:
Client(const InetAddress& serverAddr)
: _client(_loopThread.startLoop(), serverAddr, "Client")
{
// _loopThread.startLoop() 会创建新线程,并返回该线程的EventLoop
_client.setConnectionCallback(
std::bind(&Client::onConnection, this, std::placeholders::_1)
);
}
void connect()
{
_client.connect();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
std::cout << "连接成功" << std::endl;
}
}
EventLoopThread _loopThread;
TcpClient _client;
};
注意事项
- 这是客户端的标准做法:主线程处理用户输入,IO 线程处理网络事件
startLoop()会阻塞直到 EventLoop 启动完成
2. EventLoopThreadPool:IO 线程池
作用:创建一个 IO 线程池,用于多线程服务器
cpp
// 核心接口
void setThreadNum(int numThreads); // 设置线程数
void start(ThreadInitCallback cb = ThreadInitCallback()); // 启动线程池
EventLoop* getNextLoop(); // 轮询获取下一个EventLoop
示例
cpp
// 场景:多线程TCP服务器
class MultiThreadServer
{
public:
MultiThreadServer(EventLoop* loop, const InetAddress& addr)
: _server(loop, addr, "MultiThreadServer")
{
// 设置线程数为4(主线程 + 4个IO线程,共5个线程)
_server.setThreadNum(4);
_server.setConnectionCallback(
std::bind(&MultiThreadServer::onConnection, this, std::placeholders::_1)
);
_server.setMessageCallback(
std::bind(&MultiThreadServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
);
}
void start()
{
_server.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
// 每个新连接会被分配到线程池中的一个EventLoop
std::cout << "连接分配到线程:" << conn->getLoop() << std::endl;
}
void onMessage(const TcpConnectionPtr& conn, Buffer* buffer, Timestamp)
{
std::string data = buffer->retrieveAllAsString();
conn->send(data); // 回声服务器
}
TcpServer _server;
};
注意事项
- 这是 muduo 多线程服务器的标准做法
- 线程数通常设置为 CPU 核心数
- 新连接会通过轮询(Round-Robin) 方式分配到不同的 IO 线程
六、InetAddress 网络地址接口
InetAddress 用于表示网络地址(IP + 端口)。
1. 构造函数
cpp
// 构造函数1:通过端口号构造(用于服务器监听)
InetAddress(uint16_t port, bool loopbackOnly = false);
// 构造函数2:通过IP字符串和端口号构造(用于客户端连接)
InetAddress(StringArg ip, uint16_t port);
示例
cpp
// 服务器:监听所有网卡的8080端口
InetAddress serverAddr(8080);
// 服务器:仅监听本地回环地址的8080端口
InetAddress localAddr(8080, true);
// 客户端:连接到192.168.1.100的8080端口
InetAddress clientAddr("192.168.1.100", 8080);
2. toIpPort() & toIp() & port()
作用:获取地址的字符串表示和端口号
cpp
// 完整签名
std::string toIpPort() const; // 返回 "IP:端口" 格式字符串
std::string toIp() const; // 仅返回IP字符串
uint16_t port() const; // 返回端口号
示例
cpp
void printAddress(const InetAddress& addr)
{
std::cout << "完整地址:" << addr.toIpPort() << std::endl;
std::cout << "IP地址:" << addr.toIp() << std::endl;
std::cout << "端口号:" << addr.port() << std::endl;
}
七、带定时器和多线程的回声服务器
cpp
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThreadPool.h>
#include <muduo/net/InetAddress.h>
#include <iostream>
using namespace muduo;
using namespace muduo::net;
class AdvancedEchoServer
{
public:
AdvancedEchoServer(EventLoop* loop, const InetAddress& addr)
: _server(loop, addr, "AdvancedEchoServer"), _loop(loop), _connectionCount(0)
{
// 设置4个IO线程
_server.setThreadNum(4);
_server.setConnectionCallback(
std::bind(&AdvancedEchoServer::onConnection, this, std::placeholders::_1)
);
_server.setMessageCallback(
std::bind(&AdvancedEchoServer::onMessage, this,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)
);
}
void start()
{
_server.start();
std::cout << "服务器启动,监听端口:"
<< _server.ipPort() << std::endl;
// 启动周期性定时器,每5秒打印连接数
_loop->runEvery(5.0,
[this]()
{
printConnectionCount();
}
);
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
_connectionCount++;
// 禁用Nagle算法
conn->setTcpNoDelay(true);
std::cout << "新连接:" << conn->peerAddress().toIpPort()
<< ",分配到线程:" << conn->getLoop()
<< ",当前连接数:" << _connectionCount << std::endl;
// 10秒后检查连接是否还在,如果不在则清理
EventLoop* ioLoop = conn->getLoop();
ioLoop->runAfter(10.0,
[conn, this]()
{
if (!conn->connected())
{
std::cout << "连接已断开:" << conn->peerAddress().toIpPort() << std::endl;
}
}
);
}
else
{
_connectionCount--;
std::cout << "连接断开:" << conn->peerAddress().toIpPort()
<< ",当前连接数:" << _connectionCount << std::endl;
}
}
void onMessage(const TcpConnectionPtr& conn, Buffer* buffer, Timestamp)
{
std::string data = buffer->retrieveAllAsString();
std::cout << "收到来自 " << conn->peerAddress().toIpPort()
<< " 的数据:" << data << std::endl;
// 回声:将数据发回
conn->send(data);
// 如果收到"quit",强制关闭连接
if (data == "quit")
{
conn->forceClose();
}
}
void printConnectionCount()
{
std::cout << "定时报告 - 当前连接数:" << _connectionCount << std::endl;
}
TcpServer _server;
EventLoop* _loop;
int _connectionCount;
};
int main()
{
EventLoop loop;
InetAddress addr(8085);
AdvancedEchoServer server(&loop, addr);
server.start();
loop.loop();
return 0;
}
八、muduo 常用接口总结
| 类别 | 接口 | 作用 |
|---|---|---|
| EventLoop | runInLoop() |
在 IO 线程中执行任务 |
queueInLoop() |
将任务放入队列异步执行 | |
quit() |
退出事件循环 | |
runAfter() |
一次性定时器 | |
runEvery() |
周期性定时器 | |
cancel() |
取消定时器 | |
| TcpConnection | forceClose() |
强制关闭连接 |
setTcpNoDelay() |
禁用 Nagle 算法 | |
getLoop() |
获取所属 EventLoop | |
peerAddress() |
获取对端地址 | |
localAddress() |
获取本端地址 | |
| Buffer | peek() |
查看可读数据(不消费) |
readableBytes() |
获取可读字节数 | |
retrieveAsString() |
读取指定长度数据 | |
prepend() |
在开头插入数据 | |
| 线程 | EventLoopThread |
单独的 IO 线程 |
EventLoopThreadPool |
IO 线程池 |
感谢阅读,本文如有错漏之处,烦请斧正。