libevent源码学习----io多路复用的封装和使用
因為是非阻塞監聽事件的發生,所以內部其實還是采用io多路復用函數實現的。
又因為可供選擇的io函數很多,linux下有epoll, poll, select等,window下有ICOP, select等,所以libevent需要在程序啟動時選擇一個合適的io多路復用函數,合適的依據是
- 系統支持,為了實現跨平臺
- io函數的效率盡量高
- 用戶是否主動設置了不想使用的io函數
為了解決跨平臺,libevent對所有的io函數都進行了各自的封裝
為了解決效率問題,libevent在選擇時,從效率高的開始選
為了解決用戶設置,libevent為每一個io函數提供一個名字,用戶人為設置不想使用的io函數時也是傳送io函數名字,libevent維護一個字符串隊列,選擇不在這個隊列中的io函數
以下程序就是libevent如何初始化io多路復用函數的
/* 由event_base_new調用 */ struct event_base * event_base_new_with_config(const struct event_config *cfg) {int i;struct event_base *base;/* ... *//* * 為了實現讓base可以根據系統需要或者用戶的需要調用不同的io復用函數,* 比如說系統可能不支持某個io復用函數,又或者是用戶指明不想要使用* 哪個io復用函數,指明不想只要哪個函數可以通過調用帶有config的函數提供* 創建base的配置* 為了解決這種情況,在全局變量中有一個eventopts數組,這個數組中存儲著* 所有系統支持的io復用函數,每個io復用函數都是一個結構體實例化對象* 可以在每一個io函數的文件,比如select.c中看到* 而evbase存儲的不是使用的io函數,而是使用的io函數對應的數據結構體* 其實就是存數據的,在io函數文件中也可以看到* 下面的for循環是為了找到第一個可用的io函數,因為數組中的函數是按效率* 排序的*/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];/* 調用對應io函數的初始化函數* 注意:在這個函數內部同時對信號進行了初始化,其實是創建了一個socketpair* 目的是將信號統一成event,見evsig_init*/base->evbase = base->evsel->init(base);}/* 如果沒有找到可用的io函數,會出錯返回,同時清除已經創建的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函數數組,以此實現跨平臺,根據預編譯頭判斷系統是否支持某個io函數,從而構造出一個存儲著所有可用的io多路復用函數的數組,初始化base時,只需要篩選出用戶允許的即可
/* 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 };接下來單獨看每一個io函數的封裝
select的封裝,由此可見每個io函數的封裝都是定義一個struct eventop類型的變量,將對應io函數的接口指針存儲著,這就將所有的io函數都統一起來,不需要特定io調用特定接口
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也對每個io函數使用的數據類型進行了封裝,比如說epoll_event,pollfd以及fd_set
對select的fd_set進行的封裝,為什么read/write都有兩份,可以參考幾種服務器模型以及io多路復用函數中的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; };對于epoll也是如此,libevent內部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; };可以發現,每個io多路復用函數的封裝都是遵循struct eventop類型的,所以base中只需要存儲著eventop類型的指針evsel,在初始化它之后只需要調用struct eventop提供的接口函數,就可以直接調用io多路復用函數的接口函數,實現了統一
而對于每個io多路復用函數的數據類型,libevent沒有進行統一的封裝,因為也沒有必要。在初始化base中
/* * evbase存儲的就是對應的數據結構,它是個void*指針,所以可以存儲任意類型的結構,比如* 對于select而言是struct selectop,* 對于epoll而言是struct epollop* / base->evbase = base->evsel->init(base);libevent中io多路復用的使用體現在
- 新建event注冊到base中,此時會把監聽的fd和事件添加到io復用中,本質上就是調用epoll_ctl,FD_SET等
- 刪除event從base中,會把監聽的fd從io復用中刪除,本質上調用epoll_ctl等
- 開啟事件驅動循環,監聽事件的發生,本質上調用各種wait函數如epoll_wait,select,poll等
比如添加event
其實都是間接調用每一個io接口
總結
這部分主要學習到libevent是如何實現跨平臺的io多路復用函數的選擇的,所謂跨平臺,就是將所有可能的平臺都考慮到。同時看到libevent是如何把所有io函數都進行統一的,這一點很值得學習
題外話,其實對io的封裝就是基類純虛函數加各種派生類,用基類指針實現多態….
總結
以上是生活随笔為你收集整理的libevent源码学习----io多路复用的封装和使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: libevent源码学习-----阅读心
- 下一篇: libevent源码学习-----时间管