muduo网络库学习(一)对io复用的封装Poller,面向对象与基于对象
高效并發的網絡框架大多離不開io多路復用函數,Linux下有三種
關于三者的區別可以參考 linux網絡編程—–幾種服務器模型及io多路復用函數
前段時間看Libevent源碼時也學習過對epoll/poll/select的封裝,但是畢竟c語言寫的庫,是通過函數指針實現多態。我學習的muduo源碼是c++11版本的,利用c++進行封裝。
首先復習一下C++面向對象和基于對象的區別
- 面向對象
- 面向對象的三大特點:封裝,繼承,多態缺一不可
- 封裝:數據和處理數據的函數統一起來,封裝在一個class中
- 繼承:通過繼承某個類派生出一個新類,被繼承的類稱作基類,派生出的類稱作派生類。派生類是對基類的補充,二者之間滿足一定的歸屬關心,如動物(基類),鳥(派生類)。繼承可以是public/private/protected繼承,也可以是虛繼承(用于解決多重繼承帶來的重復問題),基類可以是抽象基類(不能被實例化),但是派生類需要重新實現基類定義的每個純虛函數。
- 多態:在繼承的基礎上通過基類指針指向派生類的實例化對象,達到調用派生類虛函數的目的,多態又被叫做運行時多態,是在運行期根據基類指針實際指向的對象類型判斷調用哪個函數的方式。
- 基于對象
- 無繼承,無多態,只有封裝
- 利用類封裝好的接口實現對數據的操作
如何禁止編譯器自動生成拷貝構造函數/賦值運算符
- 繼承boost::noncopyable
- 自定義空基類,基類中將兩個函數放在private域,派生類private繼承該基類
- 在自己的private域中聲明兩個函數,不予實現
c++11版本采用第2中,boost版本采用第1中,第三種效果不好,因為錯誤是在鏈接期發現,前兩個是在編譯期
現如今大多C++程序都是基于對象的,面向對象只在整個程序中占一小部分比重。
muduo采用的也是基于對象的手法,但是對io多路復用的封裝采用的是面向對象,即定義一個基類,派生出不同的派生類。muduo只派生了poll/epoll兩個類封裝,因為二者在實現上有相似性,可以共用基類。
基類Poller主要用于設計統一接口,兩個派生類EPollPoller/PollPoller用于實現各自的操作,Poller定義如下
/* 禁止編譯器自動生成拷貝構造函數/賦值操作運算符 */ class Poller : noncopyable {public:typedef std::vector<Channel*> ChannelList;Poller(EventLoop* loop);virtual ~Poller();/// Polls the I/O events./// Must be called in the loop thread./* * 監聽函數,對于epoll是epoll_wait,對于poll是poll * 返回epoll_wait/poll返回的時間*/virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels) = 0;/// Changes the interested I/O events./// Must be called in the loop thread./* 更新監聽事件,增刪改對fd的監聽事件 */virtual void updateChannel(Channel* channel) = 0;/// Remove the channel, when it destructs./// Must be called in the loop thread./* 刪除監聽事件 */virtual void removeChannel(Channel* channel) = 0;virtual bool hasChannel(Channel* channel) const;static Poller* newDefaultPoller(EventLoop* loop);void assertInLoopThread() const{ownerLoop_->assertInLoopThread();}protected:/* * Channel,保存fd和需要監聽的events,以及各種回調函數(可讀/可寫/錯誤/關閉等)* 類似libevent的struct event*/typedef std::map<int, Channel*> ChannelMap;/* 保存所有事件Channel,類似libevent中base的注冊隊列 */ChannelMap channels_;private:/* * EventLoop,事件驅動主循環,用于調用poll函數* 類似libevent的struct event_base*/EventLoop* ownerLoop_; };類中采用前向聲明,即在定義Poller之前聲明一下class Channel;,用處是避免讓頭文件#include <muduo/net/Channel.h>從而增加依賴性,因為頭文件中并沒有使用Channel,只是定義了這個類型的變量,所以只聲明就好了,而在成員函數的實現中需要使用Channel的接口,這就需要讓編譯器知道Channel是怎么定義的,就需要在.cpp文件中#include <muduo/net/Channel.h>。另外,因為Channel是Poller的成員變量,當Poller析構時也會調用Channel的析構函數,這就需要讓編譯器知道Channel析構函數的定義,所以Poller的析構函數需要在.cpp中定義。
以上也是大多muduo類采用的方法,這種方法可以降低依賴關系,如果Channel文件改變,不需要重新編譯Poller文件
派生類EPollPoller的實現就是重新實現基類Poller聲明的純虛函數,簡單的調用epoll的接口。在poll返回后也會將就緒的fd(muduo是由Channel管理,libevent是由struct event管理)添加到激活隊列中
/** 對epoll函數的封裝,繼承自Poller*/ class EPollPoller : public Poller {public:EPollPoller(EventLoop* loop);virtual ~EPollPoller();/* epoll_wait */ virtual Timestamp poll(int timeoutMs, ChannelList* activeChannels);/* ADD/MOD/DEL */ virtual void updateChannel(Channel* channel);/* DEL */virtual void removeChannel(Channel* channel);private:static const int kInitEventListSize = 16;/* EPOLL_CTL_ADD/MOD/DEL轉成字符串 */static const char* operationToString(int op);/* epoll_wait返回后將就緒的文件描述符添加到參數的激活隊列中 */void fillActiveChannels(int numEvents,ChannelList* activeChannels) const;/* 由updateChannel/removeChannel間接調用,執行epoll_ctl */void update(int operation, Channel* channel);typedef std::vector<struct epoll_event> EventList;int epollfd_;EventList events_; };類的聲明中的EventList記錄著所有監聽的epoll_event,.cpp中就是實現上述函數,進行增刪改等,主要記錄一些沒接觸過的知識
epoll_create1
int epoll_create(int size);- 創建epollfd,早期linux引入的創建監聽epollfd的函數,傳入的參數size作為給內核的一個提示 - 內核會根據這個size分配一塊這么大的數據空間用來監聽事件(struct epoll_event) - 當在使用的過程中出現大于size的值時,內核會重新分配內存空間。 - 目前這個size已經沒有作用,內核可以動態改變數據空間大小,但仍然需要傳入大于0的數 int epoll_create1(int flag);- 創建epollfd,新版linux引入的函數,當flag為0時和不帶size的epoll_create效果一樣 - 目前flag只支持EPOLL_CLOEXEC,在創建的過程中將返回的epollfd描述符設置CLOSE-ON-EXEC屬性 - 當程序exec執行新程序時自動close epollfdstatic_assert
static_assert(bool flag, char *msg);- 編譯期斷言,程序在編譯的過程中執行 - 若flag為真,什么也不做 - 若flag為假,產生一條編譯錯誤,輸出錯誤信息msg,錯誤位置為當前行號 static_assert可以增加編譯期對程序的控制,準確定位出錯的可能assert
assert(bool flag);- 運行期DEBUG模式下的斷言- 若flag為真,什么也不做- 若flag為假,終止程序注意assert只有在debug模式下才會有效,在release模式下這條語句就被編譯器刪除了 對此,通常在assert后面有一個(void)n;等語句int fd = channel_->fd(); assert(channels_[fd] == channel); (void)fd; 如果以release模式下運行,assert被刪除,編譯器會發出警告通知,提示變量fd未使用 而如果在設置編譯條件時將警告提升為錯誤,那么編譯就不會繼續進行 (void)fd;意為將fd轉為void類型,簡單使用一下fd,消除警告總結
以上是生活随笔為你收集整理的muduo网络库学习(一)对io复用的封装Poller,面向对象与基于对象的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 每天一道LeetCode-----括号匹
- 下一篇: muduo网络库学习(二)对套接字和监听