libevent源码学习----io多路复用的封装和使用
因?yàn)槭欠亲枞O(jiān)聽事件的發(fā)生,所以內(nèi)部其實(shí)還是采用io多路復(fù)用函數(shù)實(shí)現(xiàn)的。
又因?yàn)榭晒┻x擇的io函數(shù)很多,linux下有epoll, poll, select等,window下有ICOP, select等,所以libevent需要在程序啟動(dòng)時(shí)選擇一個(gè)合適的io多路復(fù)用函數(shù),合適的依據(jù)是
- 系統(tǒng)支持,為了實(shí)現(xiàn)跨平臺(tái)
- io函數(shù)的效率盡量高
- 用戶是否主動(dòng)設(shè)置了不想使用的io函數(shù)
為了解決跨平臺(tái),libevent對(duì)所有的io函數(shù)都進(jìn)行了各自的封裝
為了解決效率問題,libevent在選擇時(shí),從效率高的開始選
為了解決用戶設(shè)置,libevent為每一個(gè)io函數(shù)提供一個(gè)名字,用戶人為設(shè)置不想使用的io函數(shù)時(shí)也是傳送io函數(shù)名字,libevent維護(hù)一個(gè)字符串隊(duì)列,選擇不在這個(gè)隊(duì)列中的io函數(shù)
以下程序就是libevent如何初始化io多路復(fù)用函數(shù)的
/* 由event_base_new調(diào)用 */ struct event_base * event_base_new_with_config(const struct event_config *cfg) {int i;struct event_base *base;/* ... *//* * 為了實(shí)現(xiàn)讓base可以根據(jù)系統(tǒng)需要或者用戶的需要調(diào)用不同的io復(fù)用函數(shù),* 比如說系統(tǒng)可能不支持某個(gè)io復(fù)用函數(shù),又或者是用戶指明不想要使用* 哪個(gè)io復(fù)用函數(shù),指明不想只要哪個(gè)函數(shù)可以通過調(diào)用帶有config的函數(shù)提供* 創(chuàng)建base的配置* 為了解決這種情況,在全局變量中有一個(gè)eventopts數(shù)組,這個(gè)數(shù)組中存儲(chǔ)著* 所有系統(tǒng)支持的io復(fù)用函數(shù),每個(gè)io復(fù)用函數(shù)都是一個(gè)結(jié)構(gòu)體實(shí)例化對(duì)象* 可以在每一個(gè)io函數(shù)的文件,比如select.c中看到* 而evbase存儲(chǔ)的不是使用的io函數(shù),而是使用的io函數(shù)對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)體* 其實(shí)就是存數(shù)據(jù)的,在io函數(shù)文件中也可以看到* 下面的for循環(huán)是為了找到第一個(gè)可用的io函數(shù),因?yàn)閿?shù)組中的函數(shù)是按效率* 排序的*/base->evbase = NULL;/* 如上 */for (i = 0; eventops[i] && !base->evbase; i++) {if (cfg != NULL) {/* determine if this backend should be avoided */if (event_config_is_avoided_method(cfg,eventops[i]->name))continue;if ((eventops[i]->features & cfg->require_features)!= cfg->require_features)continue;}base->evsel = eventops[i];/* 調(diào)用對(duì)應(yīng)io函數(shù)的初始化函數(shù)* 注意:在這個(gè)函數(shù)內(nèi)部同時(shí)對(duì)信號(hào)進(jìn)行了初始化,其實(shí)是創(chuàng)建了一個(gè)socketpair* 目的是將信號(hào)統(tǒng)一成event,見evsig_init*/base->evbase = base->evsel->init(base);}/* 如果沒有找到可用的io函數(shù),會(huì)出錯(cuò)返回,同時(shí)清除已經(jīng)創(chuàng)建的base */if (base->evbase == NULL) {event_warnx("%s: no event mechanism available",__func__);base->evsel = NULL;event_base_free(base);return NULL;}/* ... */return (base); }全局io函數(shù)數(shù)組,以此實(shí)現(xiàn)跨平臺(tái),根據(jù)預(yù)編譯頭判斷系統(tǒng)是否支持某個(gè)io函數(shù),從而構(gòu)造出一個(gè)存儲(chǔ)著所有可用的io多路復(fù)用函數(shù)的數(shù)組,初始化base時(shí),只需要篩選出用戶允許的即可
/* Array of backends in order of preference. */ static const struct eventop *eventops[] = { #ifdef _EVENT_HAVE_EVENT_PORTS&evportops, #endif #ifdef _EVENT_HAVE_WORKING_KQUEUE&kqops, #endif #ifdef _EVENT_HAVE_EPOLL&epollops, #endif #ifdef _EVENT_HAVE_DEVPOLL&devpollops, #endif #ifdef _EVENT_HAVE_POLL&pollops, #endif #ifdef _EVENT_HAVE_SELECT&selectops, #endif #ifdef WIN32&win32ops, #endifNULL };接下來單獨(dú)看每一個(gè)io函數(shù)的封裝
select的封裝,由此可見每個(gè)io函數(shù)的封裝都是定義一個(gè)struct eventop類型的變量,將對(duì)應(yīng)io函數(shù)的接口指針存儲(chǔ)著,這就將所有的io函數(shù)都統(tǒng)一起來,不需要特定io調(diào)用特定接口
const struct eventop selectops = {"select",select_init,select_add,select_del,select_dispatch,select_dealloc,0, /* doesn't need reinit. */EV_FEATURE_FDS,0, };此外libevent也對(duì)每個(gè)io函數(shù)使用的數(shù)據(jù)類型進(jìn)行了封裝,比如說epoll_event,pollfd以及fd_set
對(duì)select的fd_set進(jìn)行的封裝,為什么read/write都有兩份,可以參考幾種服務(wù)器模型以及io多路復(fù)用函數(shù)中的select部分
struct selectop {int event_fds; /* Highest fd in fd set */int event_fdsz;int resize_out_sets;fd_set *event_readset_in;fd_set *event_writeset_in;fd_set *event_readset_out;fd_set *event_writeset_out; };對(duì)于epoll也是如此,libevent內(nèi)部epoll有另一種封裝,不明白原理
const struct eventop epollops = {"epoll",epoll_init,epoll_nochangelist_add,epoll_nochangelist_del,epoll_dispatch,epoll_dealloc,1, /* need reinit */EV_FEATURE_ET|EV_FEATURE_O1,0 };struct epollop {struct epoll_event *events;int nevents;int epfd; };可以發(fā)現(xiàn),每個(gè)io多路復(fù)用函數(shù)的封裝都是遵循struct eventop類型的,所以base中只需要存儲(chǔ)著eventop類型的指針evsel,在初始化它之后只需要調(diào)用struct eventop提供的接口函數(shù),就可以直接調(diào)用io多路復(fù)用函數(shù)的接口函數(shù),實(shí)現(xiàn)了統(tǒng)一
而對(duì)于每個(gè)io多路復(fù)用函數(shù)的數(shù)據(jù)類型,libevent沒有進(jìn)行統(tǒng)一的封裝,因?yàn)橐矝]有必要。在初始化base中
/* * evbase存儲(chǔ)的就是對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),它是個(gè)void*指針,所以可以存儲(chǔ)任意類型的結(jié)構(gòu),比如* 對(duì)于select而言是struct selectop,* 對(duì)于epoll而言是struct epollop* / base->evbase = base->evsel->init(base);libevent中io多路復(fù)用的使用體現(xiàn)在
- 新建event注冊(cè)到base中,此時(shí)會(huì)把監(jiān)聽的fd和事件添加到io復(fù)用中,本質(zhì)上就是調(diào)用epoll_ctl,FD_SET等
- 刪除event從base中,會(huì)把監(jiān)聽的fd從io復(fù)用中刪除,本質(zhì)上調(diào)用epoll_ctl等
- 開啟事件驅(qū)動(dòng)循環(huán),監(jiān)聽事件的發(fā)生,本質(zhì)上調(diào)用各種wait函數(shù)如epoll_wait,select,poll等
比如添加event
其實(shí)都是間接調(diào)用每一個(gè)io接口
總結(jié)
這部分主要學(xué)習(xí)到libevent是如何實(shí)現(xiàn)跨平臺(tái)的io多路復(fù)用函數(shù)的選擇的,所謂跨平臺(tái),就是將所有可能的平臺(tái)都考慮到。同時(shí)看到libevent是如何把所有io函數(shù)都進(jìn)行統(tǒng)一的,這一點(diǎn)很值得學(xué)習(xí)
題外話,其實(shí)對(duì)io的封裝就是基類純虛函數(shù)加各種派生類,用基類指針實(shí)現(xiàn)多態(tài)….
總結(jié)
以上是生活随笔為你收集整理的libevent源码学习----io多路复用的封装和使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libevent源码学习-----阅读心
- 下一篇: libevent源码学习-----时间管