日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

libevent源码学习----io多路复用的封装和使用

發布時間:2024/4/19 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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
//函數將event添加到base的io map和io復用函數的監聽事件中 int evmap_io_add(struct event_base *base, evutil_socket_t fd, struct event *ev) {/* ... */if (evsel->add(base, ev->ev_fd,old, (ev->ev_events & EV_ET) | res, extra) == -1)return (-1);/* ... */ }//將event從io map中刪除,由event_del_internal調用 int evmap_io_del(struct event_base *base, evutil_socket_t fd, struct event *ev) {/* ... */if (evsel->del(base, ev->ev_fd, old, res, extra) == -1)return (-1);/* ... */ } /** 實際的事件驅動循環,其實就是一個while循環,每次調用io復用函數進行事件監聽* 監聽返回之前將活躍的event都按優先級添加到base的激活隊列中* 回到循環后對base的激活隊列中的event按照優先級順序調用回調函數* 再根據是否是永久event決定要不要從base的所有隊列中刪除event* 對于具有超時時間的event則需要特殊處理,見timeout_process*/ int event_base_loop(struct event_base *base, int flags) {const struct eventop *evsel = base->evsel;struct timeval tv;struct timeval *tv_p;int res, done, retval = 0;/* ... */done = 0;while (!done) {/* ... *//** 調用Io復用函數的監聽函數,開始阻塞/非阻塞的監聽* 超時時間設置為最小堆中堆頂event的超時時間,原因如下* * 此時監聽的有三種event* 第一種是沒有設置超時時間的,包括信號,所以什么時候返回都不影響* 第二種是取得最小超時時間的堆頂event,此時可以滿足在超時時間返回* 第三種是最小堆中的其他event,這些event的超時時間在堆頂event之后,因為超時時間是絕對時間* 也就是說如果堆頂event沒有超時,那么其它的event將不可能超時* 而當最小超時時間后返回處理超時之后重新開始監聽,* 因為是絕對時間,所以不會影響最小堆的其他event的超時** 在返回之間,將活躍的event添加到base的激活隊列中* * 注意:不處理具有超時時間的event,因為這些event根本就沒有添加到io函數中* 處理這些是在timeout_process函數中*/res = evsel->dispatch(base, tv_p);/* ... */}/* ... */return (retval); }

其實都是間接調用每一個io接口


總結
這部分主要學習到libevent是如何實現跨平臺的io多路復用函數的選擇的,所謂跨平臺,就是將所有可能的平臺都考慮到。同時看到libevent是如何把所有io函數都進行統一的,這一點很值得學習
題外話,其實對io的封裝就是基類純虛函數加各種派生類,用基類指針實現多態….

總結

以上是生活随笔為你收集整理的libevent源码学习----io多路复用的封装和使用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。