Acceptor:接收新连接
Acceptor 是 TCP 服务器的核心入口,负责监听 socket 的可读事件并处理新连接。它的职责很明确:连通 socket 和 channel,将 IO 事件交给 EventLoop 管理。
在使用时,我们通常这样初始化:
mymuduo::net::Acceptor acceptor(&loop, listenAddr, true);
acceptor.setNewConnectionCallback(newConnection);
acceptor.listen();
构造过程中有几个关键点值得注意。首先是 SO_REUSEADDR 和 SO_REUSEPORT 选项的设置。前者防止服务器重启时因 TIME_WAIT 状态导致 bind 失败,后者则支持多进程或线程监听同一端口,提升并发能力。其次,Acceptor 内部维护了一个空闲文件描述符 idleFd_,它打开的是 /dev/null。这招是为了防止 fd 耗尽(EMFILE 错误)。当进程打开的文件描述符达到系统上限(默认 1024)时,accept 会失败;此时关闭 idleFd_ 腾出一个名额,能让最后一个连接成功接受,避免程序崩溃。最后,通过 setNewConnectionCallback 设置回调函数,利用 std::function 的 const& 传递参数,既实现了逻辑解耦,又避免了拷贝开销。
Connector:客户端连接管理
Connector 负责发起非阻塞链接、自动重连及超时处理。与 Acceptor 不同,它处理的是主动连接。
使用示例如下:
mymuduo::net::InetAddress serverAddr("127.0.0.1", port);
mymuduo::net::EventLoop loop;
g_loop = &loop;
mymuduo::net::ConnectorPtr connector(new mymuduo::net::Connector(&loop, serverAddr));
connector->setNewConnectionCallback(connectCallback);
connector->start();
loop.loop();
这里有个技术细节:为什么必须用非阻塞 connect?因为阻塞 connect 会挂起线程,而 muduo 的设计原则是单线程事件循环。非阻塞 connect 立即返回 0 或 EINPROGRESS,后续通过套接字可写事件判定完成。如果连接失败,Socket 的可写事件触发时检查 SO_ERROR 即可获知结果。
另一个重点是 shared_from_this 的使用。Connector 继承自 enable_shared_from_this,这是因为重试定时器回调需要持有对象的 shared_ptr 确保存活。如果外部释放了 Connector,定时器触发时会访问悬挂对象。此外,Channel 使用 unique_ptr 表示独占所有权,防止重复管理。跨线程调用安全方面,start/stop 虽然可以跨线程调用,但实际逻辑通过 runInLoop 转移到所属 loop 线程执行,避免了竞态条件。
重连策略也经过精心设计。失败后关闭 fd 并设置定时器延迟重试,延迟时间按 min(2^n * 0.5s, 30s) 指数增长,最大限制 30 秒。这样既兼顾了快速恢复,又保护了对端和自身资源不被过度请求淹没。


