【C++】muduo接口补充

目录

[一、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);

参数说明

  • ontrue禁用 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 线程池

感谢阅读,本文如有错漏之处,烦请斧正。

相关推荐
zk_one2 小时前
【无标题】
开发语言·前端·javascript
阿里嘎多学长2 小时前
2026-04-05 GitHub 热点项目精选
开发语言·程序员·github·代码托管
xiaoye-duck2 小时前
《算法题讲解指南:递归,搜索与回溯算法--综合练习》--14.找出所有子集的异或总和再求和,15.全排列Ⅱ,16.电话号码的字母组合,17.括号生成
c++·算法·深度优先·回溯
OOJO2 小时前
c++---vector介绍
c语言·开发语言·数据结构·c++·算法·vim·visual studio
Makoto_Kimur3 小时前
Java 打印模板大全
java·开发语言·排序算法
程序员榴莲3 小时前
Java(十)super关键字
java·开发语言
Tanecious.3 小时前
蓝桥杯备赛:Day5-P1706 全排列问题
c++·蓝桥杯
胖咕噜的稞达鸭3 小时前
C++技术岗面试经验总结
开发语言·网络·c++·网络协议·tcp/ip·面试
Wild_Pointer.3 小时前
高效工具实战指南:从零开始编写CMakeLists
c++