muduo事件循環分析
muduo網絡庫網絡I/O模型爲非阻塞reactor模式。使用linux的epoll或poll系統調用,輪尋多個socket,然後利用事先註冊的事件句柄處理髮生事件的套接字。
muduo中一個thread與一個EventLoop綁定,即“one-loop-per-thread”模式,線程的主循環核心代碼:
while(1){ 1) poller_->poll(); 2) for(...){ /*處理髮生事件的socket*/ handleEvent(); } 3) doPendingFunctors(); } |
其中,muduo使用timerfd定時器描述符,可以像處理普通文件描述符一樣處理定時器。因此1)、2)兩個步驟完成文件描述符的同步處理操作。而步驟3)執行函數列表中積壓的函數對象(muduo規定,一個socket的事件句柄只能由其註冊的線程調用,如果其他線程想要調用必須將包含參數的回調函數對象投遞到目標線程時間循環的函數列表中異步執行),完成異步操作。
事件循環中的定時器
muduo庫中的定時器採用timerfd,因此需要使用select,poll,epoll等檢測,另外muduo的定時器不是嚴格定時器。爲了實現定時器功能,涉及的對象主要有Timer,Timestamp,TimerQueue。其中Timer對象爲定時器本體,包括定時器時限和超時回調函數;Timestamp是時間戳,主要用於比較定時器到期否;TimerQueue對象用於存儲Timer(使用二叉搜索樹set<>存儲),並在EventLoop中註冊channel,定時器超時後調用handleRead函數,依次處理所有超時定時器。
啓動TimerQueue
>muduo_net_::EventLoop loop
| >timerQueue_(new TimerQueue(this)) //構造TimerQueue對象
| | >timerfd_(createTimerfd())
| | | >return ::timerfd_create(CLOCK_MONOTONIC,TFD_NONBLOCK | TFD_CLOEXEC) //申請timefd描述符
| >timerfdChannel_(loop, timerfd_) // 關聯timerfd_的Channel對象
| >timerfdChannel_.setReadCallback(boost::bind(&TimerQueue::handleRead, this)); //綁定超時處理
| >timerfdChannel_.enableReading(); //爲timerfd_描述符添加poll事件源
添加定時器Timer,調用EventLoop::runAt \ runAfter \ runEvery均會調用TimerQueue::addTimer函數
>timerQueue_->addTimer(cb, time, interval);
| >Timer* timer = new Timer(cb, when, interval); // 構造新的定時器對象
| >loop_->runInLoop(boost::bind(&TimerQueue::addTimerInLoop, this, timer)); //添加定時器到EventLoop中
| |>bool earliestChanged = insert(timer); // 將Timer對象插入set<>中
| | >resetTimerfd(timerfd_, timer->expiration()); // 重置timerfd描述符
定時器超時處理
>currentActiveChannel_->handleEvent(pollReturnTime_); //在EventLoop的loop函數中調用
| >即調用TimerQueue::handleRead() //timerfd的事件處理函數
| | >std::vector<Entry> expired = getExpired(now); //查找set<>中所有超時的定時器
| | >for (std::vector<Entry>::iterator it = expired.begin(); it != expired.end(); ++it)
| | | >it->second->run(); //即調用Timer::callback_()
| | >reset(expired, now); //清除已超時定時器,並跟新timefd描述符