5. Acceptor
约 1723 字大约 6 分钟
2026-04-13
Acceptor 简介
Acceptor 类可以理解为 新连接的接收器,它是 muduo 服务器中专门负责“监听 listenfd,并在有新客户端连接到来时 accept 出 connfd”的模块。
在 TCP 服务器启动之后,Acceptor 会监听服务器本地端口对应的监听套接字 listenfd。当有新的客户端连接到来时,监听套接字会变成可读,Acceptor 通过 Channel 感知到这个事件,然后调用 accept() 接收新连接,得到一个新的连接文件描述符 connfd,再通过回调把这个新连接交给上层 TcpServer 处理。
可以把它理解为:
- Socket:真正负责 listen / accept 的套接字封装;
- Channel:负责监听这个 listenfd 是否有新连接到来;
- Acceptor:负责把“监听 + 接收新连接 + 回调通知上层”串起来。
Acceptor 类重要的成员变量
1. 重要成员
EventLoop *loop_:指向所属的事件循环,一般就是服务器的baseLoop或mainLoop。Acceptor使用这个EventLoop来把acceptChannel_注册到 Poller 中。Socket acceptSocket_:专门用于监听和接收新连接的 socket。
它内部对应的就是服务器的监听套接字listenfd。Channel acceptChannel_:用于监听acceptSocket_的读事件。
当listenfd可读时,就说明有新连接到来,此时acceptChannel_会触发handleRead()。NewConnectionCallback NewConnectionCallback_:新连接到来的回调函数。
当Acceptor接收到一个新的connfd后,会通过这个回调把连接交给上层,例如TcpServer::newConnection()。bool listenning_:表示当前Acceptor是否已经开始监听。
注意这里变量名是listenning_,语义就是“正在监听”。
EventLoop *loop_; // Acceptor用的就是用户定义的那个baseLoop 也称作mainLoop
Socket acceptSocket_;//专门用于接收新连接的socket
Channel acceptChannel_;//专门用于监听新连接的channel
NewConnectionCallback NewConnectionCallback_;//新连接的回调函数
bool listenning_;//是否在监听Acceptor 类重要的成员方法
1. 构造 / 析构
Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
~Acceptor();// 创建非阻塞 socket
static int createNonblocking()
{
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (sockfd < 0)
{
LOG_FATAL("%s:%s:%d listen socket create err:%d\n", __FILE__, __FUNCTION__, __LINE__, errno);
}
return sockfd;
}
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
: loop_(loop)
, acceptSocket_(createNonblocking())
, acceptChannel_(loop, acceptSocket_.fd())
, listenning_(false)
{
acceptSocket_.setReuseAddr(true);
acceptSocket_.setReusePort(true);
acceptSocket_.bindAddress(listenAddr);
// TcpServer::start() => Acceptor.listen() 如果有新用户连接 要执行一个回调(accept => connfd => 打包成Channel => 唤醒subloop)
// baseloop监听到有事件发生 => acceptChannel_(listenfd) => 执行该回调函数
acceptChannel_.setReadCallback(
std::bind(&Acceptor::handleRead, this));
}
Acceptor::~Acceptor()
{
acceptChannel_.disableAll(); // 把从Poller中感兴趣的事件删除掉
acceptChannel_.remove(); // 调用EventLoop->removeChannel => Poller->removeChannel 把Poller的ChannelMap对应的部分删除
}构造函数主要做了几件事:
- 创建一个非阻塞、支持 CLOEXEC 的监听 socket;
- 把这个 socket 封装到
acceptSocket_; - 构造
acceptChannel_,让它监听这个 socket; - 设置地址重用、端口重用;
- 绑定本地监听地址;
- 给
acceptChannel_注册读回调handleRead()。
提示
为什么要监听读事件?
因为对于 listenfd 来说,可读就表示有新的连接请求到来。所以 acceptChannel_ 只需要关心读事件即可。
析构函数负责:
- 取消
acceptChannel_的所有感兴趣事件; - 把它从 EventLoop / Poller 中移除。
这样可以避免对象销毁后,Poller 里还残留这个监听通道。
2. setNewConnectionCallback(...)
void setNewConnectionCallback(const NewConnectionCallback &cb) { NewConnectionCallback_ = cb; }这个函数用来设置“有新连接到来时该做什么”:当 Acceptor 接收到一个新的 connfd 后,它不会自己处理连接,而是把这个新连接交给上层。上层一般会把这个连接包装成 TcpConnection,再分配到某个 subLoop 中处理。
这个回调的意义:它把“接收新连接”和“管理新连接”解耦了,Acceptor 只负责接收,TcpServer 负责把连接分发给后续逻辑。
3. listen()
void listen();void Acceptor::listen()
{
listenning_ = true;
acceptSocket_.listen(); // listen
acceptChannel_.enableReading(); // acceptChannel_注册至Poller !重要
}listen() 是启动监听的入口。
它做了什么?
- 把
listenning_设为 true; - 调用
acceptSocket_.listen(),让 socket 进入监听状态; - 调用
acceptChannel_.enableReading(),把监听 socket 的读事件注册到 Poller 中。
提示
为什么必须 enableReading()?
因为 acceptChannel_ 只有监听到读事件,才会知道有新连接到来。 这里的“读事件”不是普通的数据读取,而是监听 socket 可读 = 有新的连接请求可以 accept
4. handleRead()
private:
void handleRead();//处理新用户的连接事件// listenfd有事件发生了,就是有新用户连接了
void Acceptor::handleRead()
{
InetAddress peerAddr;
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
if (NewConnectionCallback_)
{
NewConnectionCallback_(connfd, peerAddr); // 轮询找到subLoop 唤醒并分发当前的新客户端的Channel
}
else
{
::close(connfd);
}
}
else
{
LOG_ERROR("%s:%s:%d accept err:%d\n", __FILE__, __FUNCTION__, __LINE__, errno);
if (errno == EMFILE)
{
LOG_ERROR("%s:%s:%d sockfd reached limit\n", __FILE__, __FUNCTION__, __LINE__);
}
}
}handleRead() 是 acceptChannel_ 的读回调,也就是“有新连接到来时”的处理函数。
它做了什么?
- 创建一个
InetAddress peerAddr,用于保存对端地址; - 调用
acceptSocket_.accept(&peerAddr)接收新连接; - 如果接收成功,拿到新的连接
fd:connfd; - 如果设置了新连接回调,就把 connfd 和对端地址交给上层;
- 如果没有设置回调,就直接关闭这个新连接;
- 如果
accept()失败,就记录错误日志。
这个函数的本质就是:把 listenfd 上的“新连接事件”转换成一个新的 connfd,并通知上层继续处理。
Acceptor 和 Socket、Channel、EventLoop 的关系
可以这样理解:
- Socket:负责底层套接字操作,比如
bind()、listen()、accept(); - Channel:负责监听这个监听 socket 是否可读;
- EventLoop:负责把
acceptChannel_注册到 Poller,并驱动事件分发; - Acceptor:负责把这些东西组织起来,完成“监听新连接”的功能。
Acceptor 的整体工作流程
1. 创建阶段
构造 Acceptor 时:
- 创建非阻塞监听 socket;
- 绑定监听地址;
- 创建对应的
acceptChannel_; - 设置读回调为
handleRead()。
2. 启动监听阶段
调用acceptor.listen();后:
- socket 开始监听;
acceptChannel_注册到 Poller;- 开始等待新连接事件。
3. 事件到来阶段
当有客户端连接时:
- listenfd 变为可读;
- Poller 发现
acceptChannel_有事件; - EventLoop 调用
acceptChannel_.handleEvent(); - Channel 触发
Acceptor::handleRead()。
4. 接收并分发阶段
handleRead() 里:
- 调用
accept()得到新连接 connfd; - 通过
NewConnectionCallback_交给上层。
你可以这样理解 Acceptor,Acceptor 就像服务器门口的“接待员”,它一直盯着大门(listenfd),有新客人来了,就立刻开门(accept()),然后把这个新客人交给后面的服务人员(TcpServer / TcpConnection)处理。
总结
Acceptor 的核心职责
- 创建和管理监听 socket;
- 监听新连接到来;
- 接收到新连接后调用回调通知上层;
- 负责把“新连接接入”与“后续业务处理”解耦。
一句话总结,Acceptor 是 TCP 服务器的“新连接入口”,它负责监听 listenfd、接收 connfd,并把新连接交给上层继续处理。
