Acceptormuduo 网络库核心的TCP 连接接收器类,专门负责监听端口、接收新的客户端 TCP 连接

Acceptor.h

1
2
3
4
5
6
7
8
9
10
11
12
#pragma once

#include "noncopyable.h"
#include "Channel.h"
#include "Socket.h"

#include <functional>

class EventLoop;
class InetAddrress;

class Acceptor : noncopyable

class Acceptor : noncopyable:Acceptor 持有唯一的监听 Socket、Channel 资源,复制会导致资源冲突、文件描述符错乱。

核心回调类型

1
using NewConnectionCallback = std::function<void(int sockfd, const InetAddress&)>;

当 Acceptor 接收新连接后,调用这个回调,把新连接的 fd客户端地址传给上层(TcpServer)处理。

私有成员变量

1
2
3
4
5
6
private:
EventLoop* loop_;
Socket acceptSocket_;
Channel acceptChannel_;
NewConnectionCallback newConnectionCallback_;
bool listenning_;

用成员对象(实例)不用指针:生命周期严格绑定,Acceptor 销毁 → socket/channel 自动销毁

私有核心方法

1
2
private:	
void handleRead();

处理新连接到达的读事件

公有接口

1
2
Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport);
~Acceptor();
  • 传入:事件循环、监听地址、端口复用开关

  • 核心工作:创建监听 socket → 绑定 IP + 端口 → 初始化 Channel

1
2
3
4
void setNewConnectionCallback(const NewConnectionCallback &cb) 
{
newConnectionCallback_ = cb;
}

上层(TcpServer)注册回调,接收新连接通知

1
2
bool listenning() const { return listenning_; } // 获取监听状态
void listen(); // 启动监听(调用socket::listen())

Acceptor.cc

1
2
3
4
5
6
7
8
9
static int createNonblocking()
{
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0)
if(sockfd < 0)
{
LOG_FATAL("Nonblocking listen socket create failed, errno : {}", errno);
}
return sockfd;
}
  • 参数详解:
    • SOCK_STREAM:TCP 流式套接字
    • SOCK_NONBLOCK:创建 socket 时直接设置非阻塞,比后面用 fcntl 改更高效
    • SOCK_CLOEXEC:调用 exec 执行新程序时,自动关闭这个 socket,防止父进程创建的 socket 被子进程意外继承,导致文件描述符泄露、端口无法释放
  • 在** .cc/.cpp 文件里,函数前加 static:这个函数只在当前文件可见**,其他文件看不到、调用不到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
: loop_(loop)
, acceptSocket_(createNonblocking()) //Socket
, acceptChannel_(loop, acceptSocket_.fd())
, listening_(false)
{
acceptSocket_.setReuseAddr(true);
acceptSocket_.setReusePort(true);
acceptSocket_.bindAddress(listenAddr);
acceptChannel_.setReadCallback(std::bind(&Acceptor::handleRead, this));
}

Acceptor::~Acceptor()
{
acceptChannel_.disableAll();
acceptChannel_.remove();
}
  • 给 channel 绑定读事件回调 → 有新连接时调用 handleRead
  • 调用刚才的工具函数,创建非阻塞 TCP socket,得到监听 fd(listenfd),封装成 Socket 对象,由 Acceptor 持有管理
  • 为什么用 std::bind?
    • handleRead 是成员函数
    • 调用成员函数必须有 this
    • std::bind 把:&Acceptor::handleRead + this绑定成一个可以直接调用的回调函数
  • 构造函数不直接启动监听(listen),由上层 TcpServer 控制何时启动监听
  • 析构函数,先取消所有监听事件,再从EventLoop中移除这个Channel

调用逻辑

1
2
3
4
5
6
7
8
9
10
11
客户端发起连接

内核通知 listenfd 可读

EventLoop 监听并激活 Channel

Channel 调用 读回调

Acceptor::handleRead() 执行

accept() 接收新连接 → 回调给 TcpServer
1
2
3
4
5
6
void Acceptor::listen()
{
listening_ = true;
acceptSocket_.listen();
acceptChannel_.enableReading();
}

先调用系统的listen函数开始监听,再通过enableReading调用到epoll_ctl开始监听读事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Acceptor::handleRead()
{
InetAddress peerAddress;
int connfd = acceptSocket_.accept(&peerAddress);
if(connfd >= 0)
{
if(newConnectionCallback_)
{
newConnectionCallback_(connfd, peerAddress);
}
else
{
::close(connfd);
}
}
else
{
LOG_ERROR("{}:{}:{} accept failed, errno : {}", __FILE__, __FUNCTION__, __LINE__, errno);
if (errno == EMFILE)
{
LOG_ERROR("{}:{}:{} sockfd reached limit", __FILE__, __FUNCTION__, __LINE__);
}
}
}
  • 核心功能:
    • 调用 accept 接收新连接,得到客户端通信 fd(connfd)
    • 获取客户端 IP + 端口(peerAddress存储)
    • 通过回调交给 TcpServer,或者没人要就直接关闭
  • 调用时机:
    • 客户端完成三次握手,listenfd 变成可读
    • 由 Channel 的读事件回调触发执行
  • EMFILE:当前进程打开的文件描述符达到上限,达到上限后 accept 会失败,无法建立新连接
  • Acceptor 只做 接收连接,连接建立后的 IO 通信 交给 TcpConnection + subLoop
  • 没有回调就要直接 close (connfd),newConnectionCallback_ 是空 → 没人接管这个连接,此时还没进入业务通信阶段,没有数据需要收发,直接关闭最安全

源码地址

Acceptor.h:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/Acceptor.h

Acceptor.cc:https://gitee.com/lpzdinghai/lpzmuduo/blob/master/Acceptor.cc