前面我们大概介绍了Muduo网络库的整体架构以及Reactor模式的一般架构,从这里就开始分析Muduo网络库中用到的一些类。
本篇文章主要是介绍Acceptor
muduo网络库的example中作为server的类都是继承于class TcpServer, 而TcpServer初始化的时候创建一个Acceptor对象、一个EventThreadPool对象以及一些Callback。因此从整体上来看Acceptor主要就是供TcpServer使用,生命期由后者控制,负责管理服务器监听的socket并将回调通知使用者,进行连接分发。下面我就专门分析下Acceptor的实现。
首先先看下类图:
首先从class Acceptor类的声明中可以看出该类包含主要包含如下几个数据成员:
class Acceptor : boost::noncopyable { public: typedef boost::function<void (int sockfd, const InetAddress&)> NewConnectionCallback; Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport); ~Acceptor(); void setNewConnectionCallback(const NewConnectionCallback& cb) { newConnectionCallback_ = cb; } bool listenning() const { return listenning_; } void listen(); private: void handleRead(); EventLoop* loop_; Socket acceptSocket_; Channel acceptChannel_; NewConnectionCallback newConnectionCallback_; bool listenning_; int idleFd_; };从上面可以看到主要包含一个EventLoop、Socket和一个Channel对象。 Acceptor主要负责服务器端的端口监听、事件连接以及事件分发。
acceptSocket_就是服务器监听端口的对象,class Socket把传统意义上的socket进行封装,而class Channel又是对socket的封装,他将socket以及对应的多个处理函数Callback封装在一起,那么当socket发生事件的时候,能够很快的找到相应的处理函数。 handleRead()是读事件处理函数,服务器因为会把具体事件分发给别的线程,那么针对主线程来说就只有连接建立的读事件需要处理。
而loop_就是Acceptor的EventLoop,因为Acceptor需要一直监听连接事件,Acceptor拥有自己的EventLoop和Channel。
通过源代码中的例子可以看到如果要服务器开始工作总是需要调用server.start()和loop_.loop()
当我们通过TcpServer.start()的时候,实际上就是调用Acceptor.listen()。
那么就详细分析下Acceptor这个类。
从构造函数的传入参数可以看出需要传入当前线程的EventLoop对象,而在acceptChannel的初始化中完成了Loop和acceptSocket的关联。
前三句话是设置监听套接字的,而之前我们提到监听服务器一般只会有读事件发生,那么最后一行就是设置读事件发生的回调函数的,当有连接请求建立的时候时候就会产生读事件,那么这时候就会回调handleRead(),
通过accpet可以获得连接建立的套接字,然后判断是否newConnectionCallback回调是否设置,有的话就直接调用它。这个回调函数是在TcpServer的构造函数中设置的,这边是关联到TcpServer::newConnection(),这个函数呆会再分析。
回到之前当TcpServer初始化完成之后(这里其实就是把accpetor和threadPool以及该主线程需要的EventLoop都初始化好了,之后就会调用start()),那么我们这边再来看下Acceptor::listen()
listen函数首先判断loop是否在当前线程中,之后设置listening为true表明开始监听过程,之后就是调用Socket.listen()以及Channel.enableReading(),前者最后还是会执行系统调用的::listen(),关键是后者,我们有提到Channel是对socket和回调函数的封装,他也会有成员指针指向该线程的loop,我们就需要将读事件设置到关心的条件里面,这里就是通过Channgel类里面的如下几个接口去设置的。
void enableReading() { events_ |= kReadEvent; update(); } void disableReading() { events_ &= ~kReadEvent; update(); } void enableWriting() { events_ |= kWriteEvent; update(); } void disableWriting() { events_ &= ~kWriteEvent; update(); } void disableAll() { events_ = kNoneEvent; update(); }而其中的update()就会去调用loop_->updateChannel(),在Looper里面再去更新Poller关心的条件/监听的事件。然后调用loop.loop()就开始poll了。
回到刚才的TcpServer::newConnection(),我们来看下当有新的连接请求进来的时候回发生什么?
从上面的代码可以看出首先从threadPool里面按照一定原则取出一个Eventloop,然后创建了一个TcpConnection对象并进行初始化,然后将该TcpConnection添加到刚才取出的EventLoop的任务队列里面。
从上面的分析可以看出,Acceptor有自己的EventLoop和Channel、Socket对象,因为监听连接请求也是个poll行为。Acceptor通过将这些对象进行封装,实现了服务器端的端口监听以及连接分发。
当收到连接请求的时候,会从threadPool里面取一个EventLoop然后创建一个TcpConnection对象并加入到任务队列里面。