集群聊天室项目--muduo网络库中tcpserver中setConnectionCallback与setMessageCallback详解

Muduo TcpServer 中 setConnectionCallback 与 setMessageCallback 详解

setConnectionCallbacksetMessageCallback 是 Muduo 中 TcpServer 最核心的两个回调注册接口,分别用于处理TCP 连接状态变化数据读写事件,是 Muduo "事件驱动" 设计的核心体现 ------ 用户无需关注底层 epoll、非阻塞 I/O 细节,只需注册业务回调即可实现网络逻辑。

一、核心定位

函数 核心作用 触发场景
setConnectionCallback 注册 "连接回调函数",处理 TCP 连接的建立 / 断开 三次握手完成(连接建立)、连接关闭 / 出错
setMessageCallback 注册 "消息回调函数",处理 TCP 连接上收到的数据 底层检测到可读事件(EPOLLIN),且读取数据到 Buffer 后

两者的本质是:TcpServer 将用户注册的回调传递给底层的 TcpConnection,当对应事件发生时,由 TcpConnection 触发回调执行。

二、函数原型与回调类型

1. 先明确回调类型(Muduo 预定义)

Muduo 用 std::function 封装回调类型,定义在 muduo/net/TcpServer.h/TcpConnection.h 中:

cpp 复制代码
// 连接回调类型:参数是 TcpConnection 智能指针
using ConnectionCallback = std::function<void (const TcpConnectionPtr&)>;

// 消息回调类型:参数是 连接指针 + 数据缓冲区 + 数据接收时间戳
using MessageCallback = std::function<void (const TcpConnectionPtr&, Buffer*, Timestamp)>;

2. setConnectionCallback 原型

cpp 复制代码
// TcpServer 类的成员函数
void TcpServer::setConnectionCallback(const ConnectionCallback& cb) {
  connectionCallback_ = cb;  // 保存用户回调到 TcpServer 成员变量
}
  • 入参:ConnectionCallback 类型的函数对象(可传入普通函数、lambda、bind 绑定的成员函数);
  • 作用:将用户定义的 "连接处理逻辑" 保存到 TcpServer,后续传递给新创建的 TcpConnection

3. setMessageCallback 原型

cpp 复制代码
// TcpServer 类的成员函数
void TcpServer::setMessageCallback(const MessageCallback& cb) {
  messageCallback_ = cb;  // 保存用户回调到 TcpServer 成员变量
}
  • 入参:MessageCallback 类型的函数对象;
  • 作用:将用户定义的 "数据处理逻辑" 保存到 TcpServer,后续传递给 TcpConnection

三、回调触发时机与参数解析

1. setConnectionCallback:连接事件回调

触发时机
  • 连接建立 :Acceptor 调用 accept() 成功(三次握手完成),创建 TcpConnection 后,触发 connected() 状态,执行回调;

  • 连接断开

    • 主动关闭:调用 TcpConnection::shutdown() 完成四次挥手;

    • 被动关闭:对端关闭连接 / 网络异常 / 超时,底层检测到 EPOLLRDHUP/EPOLLERR 事件;

    • 触发 disconnected() 状态,执行回调。

核心参数:TcpConnectionPtr

TcpConnectionPtrstd::shared_ptr<TcpConnection> 的别名,封装了连接的所有核心信息和操作,常用接口:

接口 作用
conn->connected() 判断当前连接是否处于 "已建立" 状态(核心)
conn->peerAddress() 获取对端(客户端)的 IP:Port(InetAddress 类型,toIpPort() 转字符串)
conn->localAddress() 获取本地(服务器)的 IP:Port
conn->fd() 获取连接对应的文件描述符(fd)(一般无需直接操作)
conn->shutdown() 主动关闭连接(半关闭写端,触发后续断开回调)
conn->setContext()/getContext() 绑定自定义数据(如用户 ID、会话信息)到连接,实现连接级数据存储

2. setMessageCallback:数据读写回调

触发时机

  1. 底层 Channel 检测到连接的 fd 有 EPOLLIN 事件(数据可读);

  2. TcpConnection 调用 handleRead(),以非阻塞方式读取数据到 Buffer

  3. 读取完成后,触发用户注册的消息回调,传入 TcpConnectionPtrBuffer*Timestamp

核心参数解析
参数 作用
const TcpConnectionPtr& conn 当前收到数据的连接(可通过它回写数据、关闭连接等)
Buffer* buf 存储接收到的数据的缓冲区(Muduo 封装的 Buffer,解决粘包 / 拆包)
Timestamp time 数据接收完成的时间戳(Timestamp 类型,toString() 转字符串)
Buffer 核心操作(解决 TCP 粘包 / 拆包)

Muduo Buffer 是处理流式数据的核心,常用接口:

接口 作用
buf->retrieveAllAsString() 读取缓冲区所有数据并清空(适合 "整包处理",如回声服务器)
buf->retrieve(n) 读取 n 字节数据并移动读指针(适合 "定长包",如协议头固定 4 字节)
buf->peek() 查看缓冲区数据但不移动读指针(适合 "解析半包",如先读协议头再读包体)
buf->readableBytes() 获取缓冲区中可读数据的长度(字节数)

四、底层原理:回调如何传递与触发

1. 回调传递流程

复制代码
TcpServer::setXxxCallback() → 保存到 TcpServer 成员变量
       ↓
TcpServer::newConnection()(Acceptor 接收新连接时调用)
       ↓
创建 TcpConnection 对象 → 将 TcpServer 保存的回调传递给 TcpConnection
       ↓
TcpConnection 将回调绑定到 Channel 的事件处理(读事件/连接事件)

2. 回调执行线程模型

Muduo 是 "单 Reactor 多线程" 模型,回调执行线程有明确规则:

  • ConnectionCallback:在 主线程(Acceptor 所在的 EventLoop) 执行(因为新连接的创建由主线程的 Acceptor 处理);

  • MessageCallback:在 工作线程(EventLoopThreadPool 中的子线程) 执行(TcpConnection 的 IO 事件被分配到工作线程的 EventLoop)。

⚠️ 注意:若回调中操作共享资源(如全局计数器、数据库连接池),需加锁保证线程安全;若需跨线程操作(如工作线程回调中更新主线程的日志),需用 EventLoop::runInLoop() 提交任务。

五、关键注意事项

1. 回调函数不能阻塞

  • ConnectionCallback 阻塞会导致主线程(Reactor)无法处理新连接 / 其他事件;
  • MessageCallback 阻塞会导致工作线程无法处理其他连接的消息;

2. 线程安全

  • TcpConnection 的成员函数并非全部线程安全:
    • 安全的:connected()peerAddress()getLoop()send()(内部会通过 runInLoop 切换到 IO 线程);
    • 不安全的:直接操作 ChannelBuffer 等底层成员;
  • 若在非 IO 线程(如业务线程)操作 TcpConnection,必须通过 conn->getLoop()->runInLoop() 提交任务到 IO 线程执行。

3. 避免空回调

若未注册 MessageCallback,当有数据可读时,Muduo 会直接丢弃数据;若未注册 ConnectionCallback,连接事件会被忽略(无业务影响,但建议注册以监控连接状态)。

4. 数据发送的注意事项

  • conn->send() 是异步的:若数据无法一次性写入 fd,Muduo 会将数据存入写缓冲区,等待 EPOLLOUT 事件触发后继续发送;
  • 若需确认数据发送完成,可注册 WriteCompleteCallbackTcpServer::setWriteCompleteCallback)。

5.业务线程

Muduo 规定:一个 TcpConnection 的所有 IO 操作(send/shutdown/ 修改 Channel 等),必须在其所属的 IO 线程执行

  • 业务线程的职责是处理 "耗时逻辑",不允许直接操作 IO 组件(如 conn->send());
  • 若业务线程直接调用 conn->send(),即使 Muduo 内部做了线程安全封装(最终还是会切到 IO 线程),但显式通过 runInLoop 提交更高效、更符合规范,也能避免批量操作时的跨线程调度开销。
相关推荐
【建模先锋】1 小时前
高效对抗噪声!基于深度残差收缩网络(DRSN)的轴承故障诊断模型
网络·深度学习·信号处理·轴承故障诊断·降噪模型
Moonquake_www1 小时前
WSL2设置桥接网络至主机IP
网络·网络协议·tcp/ip
交换机路由器测试之路2 小时前
什么是CSMA/CD
网络·路由器·以太网·交换机
苏小瀚2 小时前
[JavaSE] 网络原理(HTTP_HTTPS)
网络·tcp/ip·http
无线图像传输研究探索2 小时前
国标28181平台与TCP对讲:从“不支持”到“实现路径”的完整解析(5G单兵图传、布控球)
运维·服务器·网络·5g·无人机·单兵图传·无人机图传
陌路202 小时前
集群聊天室项目--muduo网络库的搭建及测试
网络
cui_win2 小时前
HTTP协议:常见状态码(400/500 系列)
网络·网络协议·http
没有bug.的程序员2 小时前
GC日志解析:从日志看全流程
java·网络·jvm·spring·日志·gc
上海云盾安全满满3 小时前
入侵检测系统如何保障网络安全
网络·安全·web安全