一、先回忆“回调函数”的核心概念
回调函数是一种通过函数指针(或函数对象)传递给其他函数的函数,当特定事件发生时,被调用以响应事件。简单说就是:“你告诉我一个处理方法,等事情发生了我来调用它”。
在嵌入式/网络编程中,回调是处理异步事件的核心模式(如“连接建立后自动执行某逻辑”“收到数据后自动处理”)。例如:
- 当
TcpServer接收到新连接时,自动调用你注册的onConnection函数; - 当
TcpConnection收到数据时,自动调用你注册的onMessage函数。
二、EchoServer类的结构与设计解析
这个类是一个基于muduo库的回声服务器(收到数据后原样返回),其结构体现了“事件驱动+回调绑定”的设计思想,逐部分解析如下:
1. 类的成员变量
private:
TcpServer server_; // muduo库的TcpServer对象,封装了服务器核心逻辑
EventLoop *loop_; // 事件循环指针(指向服务器的主事件循环)
server_是核心,负责监听端口、接受连接、管理TCP连接等底层工作;loop_是事件循环的句柄,用于驱动整个服务器的事件处理(如I/O事件、定时事件)。
2. 构造函数:绑定回调函数
EchoServer(EventLoop *loop, const InetAddress &addr, const std::string &name)
: server_(loop, addr, name) // 初始化TcpServer,传入事件循环、地址、服务器名称
, loop_(loop)
{
// 注册“连接事件”回调:当新连接建立/断开时,调用EchoServer::onConnection
server_.setConnectionCallback(
std::bind(&EchoServer::onConnection, this, std::placeholders::_1));
// 注册“消息事件”回调:当连接收到数据时,调用EchoServer::onMessage
server_.setMessageCallback(
std::bind(&EchoServer::onMessage, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
// 设置子事件循环线程数(处理I/O的线程数,muduo的多Reactor模型)
server_.setThreadNum(3);
}
关键:std::bind的作用
- 将
EchoServer的成员函数(onConnection、onMessage)绑定为TcpServer可调用的回调函数; this表示回调函数属于当前EchoServer对象(确保调用时能访问类的成员);std::placeholders::_1等是占位符,对应回调函数的参数(由TcpServer在事件发生时传入)。
3. 回调函数的实现
这两个函数是服务器的核心业务逻辑,由TcpServer在特定事件发生时自动调用:
(1)onConnection:处理连接建立/断开事件
void onConnection(const TcpConnectionPtr &conn)
{
if (conn->connected()) // 判断连接状态:建立/断开
{
LOG_INFO << "Connection UP :" << conn->peerAddress().toIpPort().c_str();
}
else
{
LOG_INFO << "Connection DOWN :" << conn->peerAddress().toIpPort().c_str();
}
}
- 参数
TcpConnectionPtr是一个智能指针,指向当前的TCP连接对象,封装了socket操作; - 当客户端连接到服务器(三次握手完成),
conn->connected()为true,打印“连接建立”日志; - 当客户端断开连接(四次挥手完成),
conn->connected()为false,打印“连接断开”日志。
(2)onMessage:处理收到的数据(核心业务)
void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp time)
{
std::string msg = buf->retrieveAllAsString(); // 从缓冲区读取所有数据
conn->send(msg); // 将数据原样发回客户端(回声功能)
// conn->shutdown(); // 可选:发送后关闭连接(半关闭写端)
}
- 参数解析:
conn:当前收到数据的TCP连接;buf:数据缓冲区(muduo::Buffer,封装了从socket读取的字节流);time:收到数据的时间戳;
- 功能:读取客户端发送的所有数据,通过
conn->send()原样返回(典型的回声服务器逻辑)。
4. start方法:启动服务器
void start()
{
server_.start(); // 调用TcpServer的start(),启动监听和事件循环
}
- 本质是启动
TcpServer的监听流程:绑定端口、开始接受连接、进入事件循环(loop_->loop())。
三、核心设计思想:“框架做通用,业务做定制”
EchoServer的设计体现了muduo库的“高内聚低耦合”理念:
-
TcpServer(框架)负责通用工作:- 网络底层(socket监听、连接管理、I/O事件触发);
- 事件分发(连接建立/断开、数据到达时,调用注册的回调)。
-
EchoServer(业务)负责定制逻辑:- 通过回调函数(
onConnection、onMessage)注入业务逻辑; - 无需关心底层网络细节,专注于“连接如何处理”“数据如何响应”。
- 通过回调函数(
四、总结
这个类是嵌入式/网络编程中“事件驱动模型”的典型实现:
- 回调函数是业务逻辑与底层框架的“桥梁”,让框架在特定事件发生时自动执行你的代码;
EchoServer通过绑定onConnection和onMessage,实现了“收到连接就记录、收到数据就返回”的回声服务器功能;- 若要扩展功能(如添加AI推理、数据存储),只需修改回调函数(例如在
onMessage中调用AI模型处理msg,再返回结果)。
类定义规则
1. 关于 private 成员的作用
- 核心变量:通常在
private中声明类的内部状态变量(如你的server_、loop_),这是封装的核心——禁止外部直接修改,确保数据安全性。 - 回调函数:回调函数(如你的
onConnection、onMessage)放在private中是合理的,因为它们是类内部的实现细节,不需要暴露给外部调用。但回调函数的“功能定义”本质是其成员函数的实现,而非声明位置决定的。
2. 关于 public 成员的作用
- 构造函数:用于初始化对象(如你的
EchoServer构造函数初始化server_、loop_),并注册回调(将内部回调绑定到其他对象,如server_.setConnectionCallback)。这是正确的,因为构造函数是对象创建的入口。 - 对外接口函数:
public中定义的是类对外提供的功能(如你的start()),这些函数可以调用private成员(如server_.start()),这符合封装原则——外部通过接口间接使用内部资源,而非直接操作。
总结:类设计的核心原则
- 封装:
private隐藏内部实现(变量、辅助函数、回调等),public暴露必要接口(构造函数、对外功能函数)。 - 接口与实现分离:
public成员是“接口”(告诉外部“能做什么”),private成员是“实现”(告诉内部“怎么做”)。
浙公网安备 33010602011771号