EPOLL的工作原理及流程
時間:2018-05-14作(zuo)者:華清(qing)遠見
一.Epoll是什么(me)? epoll是(shi)個什么(me)東(dong)東(dong)呢?按照man手(shou)冊(ce)的說法(fa):是(shi)為(wei)處理大批量句柄而作了(le)改進的poll。當然,這不是(shi)2.6內核才有的,它(ta)是(shi)在2.5.44內核中(zhong)被(bei)引進的(epoll(4) is a new API introduced in Linux kernel 2.5.44),它(ta)幾(ji)乎具備了(le)之前(qian)所說的一切優點,被(bei)公認為(wei)Linux2.6下性能最好的多路I/O就(jiu)緒(xu)通知方法(fa)。 二.epoll與poll和select對比(bi) [1]select 的缺點: 單個進程能夠(gou)監視的文(wen)件描述符(fu)(fu)的數量存在(zai)最大限制,通常是1024,當(dang)然可以更改數量,但由于select采用輪詢(xun)的方式掃描文(wen)件描述符(fu)(fu),文(wen)件描述符(fu)(fu)數量越(yue)多(duo),性能越(yue)差;(在(zai)linux內核頭(tou)文(wen)件中,有這樣的定義(yi):#define __FD_SETSIZE 1024) 內(nei)(nei)核 / 用(yong)戶空(kong)間內(nei)(nei)存(cun)拷貝問題,select需(xu)要(yao)復制大量的(de)句柄數據結(jie)構,產生巨大的(de)開銷; select返(fan)回的(de)是含有整個(ge)句柄的(de)數(shu)組(zu),應用程序需要遍歷整個(ge)數(shu)組(zu)才能(neng)發(fa)現哪些句柄發(fa)生了事件(jian); select中應用程(cheng)序(xu)如(ru)果沒有完成對一個已經就(jiu)緒的文(wen)件(jian)描述(shu)(shu)符(fu)進行IO操(cao)作,那(nei)么之(zhi)后每次select調用還是會將(jiang)這些文(wen)件(jian)描述(shu)(shu)符(fu)通知(zhi)進程(cheng)。 相對于(yu)我(wo)們的select模型,我(wo)們的poll是使用(yong)鏈(lian)表保(bao)持文件描述符,因此沒有(you)了(le)監視(shi)文件數量的限制(zhi),但(dan)是2,3,4等缺(que)點依(yi)舊存(cun)在。 拿select模型為(wei)例(li),假設我們的(de)(de)(de)服務器需要(yao)支持100萬的(de)(de)(de)并發(fa)連(lian)接,則(ze)在__FD_SETSIZE 為(wei)1024的(de)(de)(de)情(qing)況下(xia),則(ze)我們至少需要(yao)開辟1k個進(jin)程(cheng)才能實現100萬的(de)(de)(de)并發(fa)連(lian)接。除(chu)了進(jin)程(cheng)間上下(xia)文切換的(de)(de)(de)時間消耗(hao)外,從內(nei)核/用(yong)戶空間大量的(de)(de)(de)無腦內(nei)存拷貝、數組輪詢等,是(shi)系統難(nan)以承受的(de)(de)(de)。因此(ci),基于select模型的(de)(de)(de)服務器程(cheng)序,要(yao)達到(dao)10萬級別(bie)的(de)(de)(de)并發(fa)訪問(wen),是(shi)一個很難(nan)完(wan)成的(de)(de)(de)任務。 因此,該epoll上場了(le)。 三(san).Epoll的工作原(yuan)理 設想一(yi)下如下場景(jing):有100萬個(ge)客戶(hu)端同時(shi)(shi)與一(yi)個(ge)服務器進(jin)程保持著TCP連接。而每一(yi)時(shi)(shi)刻(ke),通常只(zhi)有幾(ji)百上(shang)千個(ge)TCP連接是活躍的(事實(shi)上(shang)大部分(fen)場景(jing)都是這(zhe)種情況)。如何實(shi)現這(zhe)樣的高(gao)并發? 在(zai)select/poll時代(dai),服務器進程每次(ci)都(dou)把這100萬個連接(jie)告訴操作(zuo)(zuo)系統(從用(yong)戶態(tai)復制句柄數(shu)據結構到(dao)內核態(tai)),讓(rang)操作(zuo)(zuo)系統內核去查詢這些套接(jie)字上是否(fou)有事(shi)件發(fa)生,輪詢完后(hou),再將(jiang)句柄數(shu)據復制到(dao)用(yong)戶態(tai),讓(rang)服務器應用(yong)程序輪詢處(chu)理已發(fa)生的(de)網絡事(shi)件,這一(yi)(yi)過程資(zi)源消(xiao)耗(hao)較大,因此,select/poll一(yi)(yi)般只能(neng)處(chu)理幾(ji)千的(de)并發(fa)連接(jie)。 epoll的(de)設計(ji)和實現(xian)與select完全(quan)不同。epoll通(tong)過(guo)在Linux內核中申請一個簡(jian)易的(de)文(wen)件系(xi)統(文(wen)件系(xi)統一般用什么(me)數(shu)據結構實現(xian)?二叉樹(shu)樹(shu))。然后(hou)epoll的(de)調用分成了3個部分: 1)調(diao)用(yong)epoll_create()建立一個epoll對(dui)象(在(zai)epoll文件系統中為這個句(ju)柄對(dui)象分配資源) 2)調用epoll_ctl向epoll對象中(zhong)添(tian)加這100萬個連接的套接字(zi) 3)調用epoll_wait收集(ji)發生的事件的連接 如(ru)此一(yi)來,要(yao)實現(xian)上(shang)面(mian)說是的場(chang)景,只需(xu)(xu)要(yao)在(zai)進程啟動(dong)時(shi)(shi)建立一(yi)個epoll對(dui)象(xiang)(xiang),然后在(zai)需(xu)(xu)要(yao)的時(shi)(shi)候向這個epoll對(dui)象(xiang)(xiang)中添加或者刪(shan)除(chu)連接(jie)(jie)。同時(shi)(shi),epoll_wait的效率也非常高,因為調用epoll_wait時(shi)(shi),并沒(mei)有一(yi)股腦的向操(cao)作(zuo)系統復(fu)制這100萬(wan)個連接(jie)(jie)的句(ju)柄數(shu)據,內(nei)核也不需(xu)(xu)要(yao)去遍歷(li)全部的連接(jie)(jie)。 具體流程: [1]當我們(men)某個(ge)進程調用(yong)epoll_create()函(han)數的時候(hou),linux內核會默認創建(jian)一個(ge)eventpoll結(jie)構(gou)體,這(zhe)個(ge)結(jie)構(gou)體中有兩個(ge)成員與epoll的使用(yong)方式相關。
每一個(ge)epoll對象(xiang)都有一個(ge)獨(du)立(li)的eventpoll結構(gou)體(ti),用于(yu)存放通(tong)(tong)過epoll_ctl方法向epoll對象(xiang)中添加(jia)進來(lai)的事(shi)(shi)件。這些事(shi)(shi)件都會掛(gua)載在紅(hong)黑樹中,如此,重復(fu)添加(jia)的事(shi)(shi)件就可(ke)以通(tong)(tong)過紅(hong)黑樹而高效的識別出來(lai). 而所有添加到epoll中的事(shi)件都(dou)會(hui)與設備(網卡)驅(qu)動程(cheng)序(xu)建立回(hui)調(diao)(diao)關系,也就(jiu)是說,當相應的事(shi)件發(fa)生(sheng)時會(hui)調(diao)(diao)用這(zhe)個回(hui)調(diao)(diao)方法。這(zhe)個回(hui)調(diao)(diao)方法在內核中叫ep_poll_callback,它會(hui)將發(fa)生(sheng)的事(shi)件epitem添加到rdlist雙鏈表中。 在epoll中,對于每(mei)一個事件,都會(hui)建(jian)立一個epitem結構(gou)體,如下所示:
當(dang)調用(yong)epoll_wait檢(jian)查是(shi)否有事件發生時(shi),只(zhi)需要檢(jian)查eventpoll對(dui)象中的rdlist雙鏈表中是(shi)否有epitem元素即可。如果rdlist不為空(kong),則把(ba)發生的事件復制到用(yong)戶(hu)態,同時(shi)將事件數量返回給用(yong)戶(hu)。
總結: (1)我們我們調用epoll_wait()函數的時候,系統創建一個epoll對(dui)象,每(mei)個對(dui)象都(dou)有(you)一個 叫(jiao)做eventpoll類型的(de)結構體與之對(dui)應,該(gai)結構體中主要(yao)有兩個主要(yao)的(de)成員,一個是 rbn,代表將要通過(guo)epoll_ctl向epll對象中(zhong)添加的事(shi)件(jian)。這些事(shi)情(qing)都是掛載在紅黑樹中(zhong)。 一個是rdlist,里面存放的(de)是將要發生的(de)事件 (2)當(dang)我們使用epoll_ctrl()函數(shu)的時候,就是向epoll對象中添加(jia),刪除,修(xiu)改感興(xing)趣的事件(jian) (3) epoll_wait()系(xi)統。通過此調用收集(ji)在(zai)epoll監控中已經(jing)發生(sheng)的(de)(de)事件。當(dang)監控的(de)(de)事件狀態發生(sheng)改變的(de)(de)時候,我們會(hui)調用會(hui)調用函數把epitem加入到rdlist中去。 一. Epoll的API函(han)數接口 3.1 事件(jian)的創建---epoll_create(); int epoll_create(int size); int epoll_create1(int flags); 功能:poll_create()創建(jian)(jian)一個(ge)epoll的(de)事例,通知內核需(xu)要監聽(ting)size個(ge)fd。size指的(de)并不是(shi)(shi)最大的(de)后備存儲(chu)設備,而是(shi)(shi)衡量內核內部結構大小的(de)一個(ge)提示。當創建(jian)(jian)成功后,會占用(yong)一個(ge)fd,所以(yi)記(ji)得在使用(yong)完之后調用(yong)close(),否則(ze)fd可能會被耗盡。 Note:自從Linux2.6.8版本以(yi)后,size值其實是(shi)沒什么(me)用的(de),不過(guo)要大于0,因為(wei)內核可以(yi)動態(tai)的(de)分配大小(xiao),所以(yi)不需(xu)要size這個提示了。 其次:epoll_create1()函(han)數,其實它和epoll_create差(cha)不多,不同的是epoll_create1函(han)參(can)數flag: · 當flag是0時,表示和epoll_create函數完全一樣,不需(xu)要size的提示了; · 當flag = EPOLL_CLOEXEC,創(chuang)建(jian)的epfd會設置FD_CLOEXEC; · 當flag = EPOLL_NONBLOCK,創建的(de)epfd會(hui)設(she)置為非阻塞。 一般用(yong)法都是使用(yong)EPOLL_CLOEXEC。 Note:關(guan)于FD_CLOEXEC,它(ta)是fd的一個標識說(shuo)明(ming),用來設置文件close-on-exec狀態的。當close-on-exec狀態為(wei)0時(shi)(shi),調用exec時(shi)(shi),fd不會被關(guan)閉(bi);狀態非(fei)零時(shi)(shi)則(ze)會被關(guan)閉(bi),這樣做(zuo)可以防(fang)止(zhi)fd泄露給執行(xing)exec后的進程。 返(fan)回值:成功返(fan)回一個非負的文件(jian)描述符。 例如: int epfd = epoll_create(20); //注:20為隨機(ji)寫(xie)的一(yi)個值,大(da)于0即可(ke)。 或 int epfd = epoll_create1(0); 3.1 事件(jian)的注(zhu)冊---epoll_ctl(); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 功(gong)能:epoll的(de)事(shi)件(jian)(jian)注冊函數(shu),epoll的(de)事(shi)件(jian)(jian)注冊函數(shu),它不(bu)同于select()是(shi)在(zai)監(jian)聽事(shi)件(jian)(jian)時告訴內核(he)要監(jian)聽什么類(lei)型(xing)的(de)事(shi)件(jian)(jian),而是(shi)在(zai)這里先(xian)注冊要監(jian)聽的(de)事(shi)件(jian)(jian)類(lei)型(xing)。 參數: @epfd epoll_create()函數(shu)的返回值 @op 表(biao)示參數的動(dong)作,常用(yong)以下(xia)宏: EPOLL_CTL_ADD:注(zhu)冊(ce)新(xin)的fd到epfd中; EPOLL_CTL_MOD:修改(gai)已(yi)經注冊的fd的監(jian)聽事(shi)件; EPOLL_CTL_DEL:從epfd中(zhong)刪(shan)除一(yi)個fd; @fd 表示(shi)我(wo)們需(xu)要監聽的文件描述(shu)符 @event 表示告訴(su)內核,我們需(xu)要(yao)監聽什(shen)么(me)事件。 結構體如下: typedef union epoll_data { void *ptr; int fd; //保存(cun)我(wo)們使(shi)用(yong)的(de)sockfd uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; events參數是一(yi)個枚舉的集合,可以(yi)用(yong)” | “來增加事件類型,枚舉如下: · EPOLLIN :表示對應(ying)的文(wen)件(jian)描述符可以讀(包括對端SOCKET正常關閉); · EPOLLOUT:表示對應的(de)文(wen)件描述符可以寫; · EPOLLPRI:表(biao)示對應的文(wen)件(jian)描述符有緊急的數(shu)據可讀(這里應該(gai)表(biao)示有帶外(wai)數(shu)據到(dao)來(lai)); · EPOLLERR:表示對(dui)應的文(wen)件(jian)描述(shu)符發生錯誤(wu); · EPOLLHUP:表示對應的文件描述符(fu)被掛(gua)斷; · EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是(shi)相對于(yu)水(shui)平觸發(Level Triggered)來(lai)說(shuo)的; · EPOLLONESHOT:只監聽(ting)一次(ci)(ci)事(shi)件,當(dang)監聽(ting)完這(zhe)次(ci)(ci)事(shi)件之后,如果(guo)還需要繼續監聽(ting)這(zhe)個socket的話,需要再次(ci)(ci)把這(zhe)個socket加(jia)入(ru)到(dao)EPOLL隊(dui)列里 返回(hui)值:成功返回(hui)0,失敗(bai)返回(hui)-1. 3.2等待事件---epoll_wait(); int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 功能:收集在epoll監(jian)控的事件中已經發送(song)的事件。 參數: @epfd epoll_create()函(han)數的返回值(zhi) @events 已經分配(pei)好的epoll_event結構(gou)體數(shu)組(zu),epoll會把將發生(sheng)的事情(qing)存放(fang)到events中。 @maxevents 告訴內核events有多(duo)大(da)。必須大(da)于0 @timeout 超時時間(jian) -1 表示epoll將(jiang)無限制(zhi)的(de)等待(dai)下去 0 立即返回 >0 指定超(chao)時(shi)時(shi)間 返(fan)回(hui)值: 成功返(fan)回(hui)已(yi)經就(jiu)緒的文件描述符個數。若是(shi)設(she)置(zhi)了超(chao)時(shi)時(shi)間,在超(chao)時(shi)時(shi)間內返(fan)回(hui)0. 失敗返回-1. 五.Epoll的(de)工作(zuo)模式。 LT(level triggered)是缺省的工(gong)作方式,并且同(tong)時支 持block和no-block socket.在這種做法中,。當epoll_wait檢(jian)測到(dao)描(miao)述符事(shi)件發生并(bing)將此事(shi)件通知應(ying)(ying)用(yong)(yong)程(cheng)序,應(ying)(ying)用(yong)(yong)程(cheng)序可(ke)以不立(li)即處(chu)理該事(shi)件。下次調(diao)用(yong)(yong)epoll_wait時,會再次響應(ying)(ying)應(ying)(ying)用(yong)(yong)程(cheng)序并(bing)通知此事(shi)件。 ET (edge-triggered)是高(gao)速(su)工作(zuo)方式,常(chang)工作(zuo)在no-block socket。在這種(zhong)模式下(xia),當(dang)epoll_wait檢測到描(miao)述(shu)符事(shi)(shi)件(jian)發生并將此事(shi)(shi)件(jian)通(tong)知應(ying)(ying)用(yong)程(cheng)(cheng)序(xu),應(ying)(ying)用(yong)程(cheng)(cheng)序(xu)必(bi)須立(li)即(ji)處理(li)該事(shi)(shi)件(jian)。如果不處理(li),下(xia)次調用(yong)epoll_wait時,不會再次響應(ying)(ying)應(ying)(ying)用(yong)程(cheng)(cheng)序(xu)并通(tong)知此事(shi)(shi)件(jian)。 EPOLLIN事(shi)件: EPOLLIN事件則只(zhi)有(you)當對端(duan)(duan)有(you)數據寫(xie)入時才會觸發,所以觸發一次后需(xu)要不斷讀(du)取(qu)所有(you)數據直到讀(du)完EAGAIN為止。否則剩下(xia)(xia)的(de)數據只(zhi)有(you)在下(xia)(xia)次對端(duan)(duan)有(you)寫(xie)入時才能一起(qi)取(qu)出來(lai)了(le)。設想這樣(yang)一個場景:接收端(duan)(duan)接收完整的(de)數據后會向對端(duan)(duan)發送應答報文, ,對端(duan)才會(hui)繼續向(xiang)(xiang)接(jie)收(shou)端(duan)發(fa)(fa)(fa)送數(shu)(shu)據,從而觸(chu)發(fa)(fa)(fa)下一(yi)(yi)次的EPOLLIN,而這時沒有(you)讀完socket緩(huan)沖區(qu)中的所有(you)數(shu)(shu)據,導致接(jie)收(shou)端(duan)無法向(xiang)(xiang)對端(duan)發(fa)(fa)(fa)送應答報文,而對端(duan)沒有(you)收(shou)到應答報文,也(ye)就(jiu)不會(hui)再發(fa)(fa)(fa)送數(shu)(shu)據觸(chu)發(fa)(fa)(fa)下一(yi)(yi)次的EPOLLIN,而沒有(you)下一(yi)(yi)次的EPOLLIN事件,接(jie)收(shou)端(duan)也(ye)就(jiu)永遠不知道此socket緩(huan)沖區(qu)中還有(you)未讀出的數(shu)(shu)據。一(yi)(yi)個完美的死(si)循環) 示例代碼: 實(shi)現多個(ge)客戶端和服務端的回射(she)代碼。 Server.c
Client.c
運行結果:
相關資訊
發表評論
|
全國咨詢(xun)電話:400-611-6270,雙休日及節假日請(qing)致電值班手機:15010390966
在線咨詢: 曹老(lao)師(shi)QQ(3337544669), 徐老(lao)師(shi)QQ(1462495461), 劉老(lao)師(shi) QQ(3108687497)
企(qi)業(ye)培(pei)訓洽談(tan)專(zhuan)線(xian):010-82600901,院校合作洽談(tan)專(zhuan)線(xian):010-82600350,在線(xian)咨詢(xun):QQ(248856300)
Copyright 2004-2018 華清(qing)遠見教育科(ke)技集團(tuan) 版權所有 ,京ICP備16055225號,京公海網安備11010802025203號