6.3.1 代码结构(2)
公开接口
Buffer 仿Netty ChannelBuffer 的buffer class,数据的读写通过buffer 进行。用户代码不需要调用read(2)/write(2),只需要处理收到的数据和准备好要发送的数据(§7.4)。
InetAddress 封装IPv4 地址(end point),注意,它不能解析域名,只认IP 地址。因为直接用gethostbyname(3) 解析域名会阻塞IO 线程。
EventLoop 事件循环(反应器Reactor),每个线程只能有一个EventLoop 实体,它负责IO 和定时器事件的分派。它用eventfd(2) 来异步唤醒,这有别于传统的用一对pipe(2) 的办法。它用TimerQueue 作为计时器管理,用Poller 作为IO multiplexing。
EventLoopThread 启动一个线程,在其中运行EventLoop::loop()。
TcpConnection 整个网络库的核心,封装一次TCP 连接,注意它不能发起连接。
TcpClient 用于编写网络客户端,能发起连接,并且有重试功能。
TcpServer 用于编写网络服务器,接受客户的连接。
在这些类中,TcpConnection 的生命期依靠shared_ptr 管理(即用户和库共同控制)。Buffer 的生命期由TcpConnection 控制。其余类的生命期由用户控制。Buffer和InetAddress 具有值语义,可以拷贝;其他class 都是对象语义,不可以拷贝。
内部实现
Channel 是selectable IO channel,负责注册与响应IO 事件,注意它不拥有filedescriptor。它是Acceptor、Connector、EventLoop、TimerQueue、TcpConnection的成员,生命期由后者控制。
Socket 是一个RAII handle,封装一个file descriptor,并在析构时关闭fd。它是Acceptor、TcpConnection 的成员,生命期由后者控制。EventLoop、TimerQueue也拥有fd,但是不封装为Socket class。
SocketsOps 封装各种Sockets 系统调用。
Poller 是PollPoller 和EPollPoller 的基类,采用“电平触发”的语意。它是EventLoop 的成员,生命期由后者控制。
PollPoller 和EPollPoller 封装poll(2) 和epoll(4) 两种IO multiplexing 后端。poll 的存在价值是便于调试,因为poll(2) 调用是上下文无关的,用strace(1) 很容易知道库的行为是否正确。
Connector 用于发起TCP 连接,它是TcpClient 的成员,生命期由后者控制。
Acceptor 用于接受TCP 连接,它是TcpServer 的成员,生命期由后者控制。
TimerQueue 用timerfd 实现定时,这有别于传统的设置poll/epoll_wait 的等待时长的办法。TimerQueue 用std::map 来管理Timer,常用操作的复杂度是O(logN),N 为定时器数目。它是EventLoop 的成员,生命期由后者控制。
EventLoopThreadPool 用于创建IO 线程池,用于把TcpConnection 分派到某个EventLoop 线程上。它是TcpServer 的成员,生命期由后者控制。
图6-2 是muduo 的简化类图,Buffer 是TcpConnection 的成员。
|
| (点击查看大图)图6-2 |