【C++】muduo基础使用

目录

[一、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 框架、即时通讯、游戏服务器等高性能网络应用

二、核心概念

  1. EventLoop:事件循环,muduo 的心脏。所有网络事件(连接、消息、断开)都在它里面处理,一个线程只能有一个 EventLoop
  2. TcpServer:TCP 服务器入口,负责监听端口、接受新连接
  3. TcpClient:TCP 客户端入口,负责发起连接
  4. TcpConnection:代表一个已建立的 TCP 连接,是收发数据的核心
  5. Buffer:muduo 自带的网络数据缓冲区,解决 TCP 粘包问题
  6. 回调函数 :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 线程中执行,绝对不能执行耗时操作
  • 不要手动 deleteTcpConnectionPtr指向的对象,它的生命周期由 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++ 里有两种函数:

  1. 全局函数 / 静态函数:无家可归,谁都能直接调用
  2. 成员函数 :有家(属于某个对象),必须知道「是哪个对象的函数」才能调用

而 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 常见坑

  1. 绝对不要在回调函数中执行耗时操作:会阻塞整个事件循环,导致所有连接都卡住
  2. TcpConnection 的生命周期由 muduo 管理:不要手动 delete,使用智能指针即可
  3. EventLoop 有严格的线程归属:所有 EventLoop 的方法都必须在创建它的线程中调用
  4. 关闭连接 :优先使用conn->shutdown()(半关闭),只有在异常情况下才用conn->forceClose()
  5. 处理 TCP 粘包:不要假设一次 recv 就能收到完整的消息,使用 Buffer 和自定义协议头来分包
  6. 日志排查问题: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


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

相关推荐
Mem0rin2 小时前
[Java/数据结构]线性表之栈与队列
java·开发语言·数据结构
上天_去_做颗惺星 EVE_BLUE2 小时前
Go 语言入门实战指南
开发语言·后端·golang
平安的平安2 小时前
Python + AI Agent 智能体:从原理到实战,构建自主决策的 AI 助手
开发语言·人工智能·python
Mr_Xuhhh2 小时前
深入理解Java数组:从定义到高阶应用
开发语言·python·算法
古城码农2 小时前
Windows平台MSVC编译的FFmpeg库
开发语言·qt
冰暮流星2 小时前
javascript之dom查询操作2
开发语言·javascript·ecmascript
小陈工2 小时前
Python Web开发入门(九):权限管理与角色控制实战
服务器·开发语言·前端·数据库·python·安全·sqlite
孙华贵2 小时前
python编程怎么赚钱
开发语言·python
tryCbest2 小时前
Python之Falsk开发框架(第四篇)- Flask 知识总结与完整博客系统实战
开发语言·python·flask