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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

读过的最好的epoll讲解

發(fā)布時(shí)間:2023/11/30 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 读过的最好的epoll讲解 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

首先我們來(lái)定義流的概念,一個(gè)流可以是文件,socket,pipe等等可以進(jìn)行I/O操作的內(nèi)核對(duì)象。

? ? 不管是文件,還是套接字,還是管道,我們都可以把他們看作流。

? ? 之后我們來(lái)討論I/O的操作,通過(guò)read,我們可以從流中讀入數(shù)據(jù);通過(guò)write,我們可以往流寫(xiě)入數(shù)據(jù)。現(xiàn)在假定一個(gè)情形,我們需要從流中讀數(shù)據(jù),但是流中還沒(méi)有數(shù)據(jù),(典型的例子為,客戶端要從socket讀如數(shù)據(jù),但是服務(wù)器還沒(méi)有把數(shù)據(jù)傳回來(lái)),這時(shí)候該怎么辦?

阻塞:阻塞是個(gè)什么概念呢?比如某個(gè)時(shí)候你在等快遞,但是你不知道快遞什么時(shí)候過(guò)來(lái),而且你沒(méi)有別的事可以干(或者說(shuō)接下來(lái)的事要等快遞來(lái)了才能做);那么你可以去睡覺(jué)了,因?yàn)槟阒揽爝f把貨送來(lái)時(shí)一定會(huì)給你打個(gè)電話(假定一定能叫醒你)。

非阻塞忙輪詢:接著上面等快遞的例子,如果用忙輪詢的方法,那么你需要知道快遞員的手機(jī)號(hào),然后每分鐘給他掛個(gè)電話:“你到了沒(méi)?”

? ? 很明顯一般人不會(huì)用第二種做法,不僅顯很無(wú)腦,浪費(fèi)話費(fèi)不說(shuō),還占用了快遞員大量的時(shí)間。

? ? 大部分程序也不會(huì)用第二種做法,因?yàn)榈谝环N方法經(jīng)濟(jì)而簡(jiǎn)單,經(jīng)濟(jì)是指消耗很少的CPU時(shí)間,如果線程睡眠了,就掉出了系統(tǒng)的調(diào)度隊(duì)列,暫時(shí)不會(huì)去瓜分CPU寶貴的時(shí)間片了。

? ? 為了了解阻塞是如何進(jìn)行的,我們來(lái)討論緩沖區(qū),以及內(nèi)核緩沖區(qū),最終把I/O事件解釋清楚。緩沖區(qū)的引入是為了減少頻繁I/O操作而引起頻繁的系統(tǒng)調(diào)用(你知道它很慢的),當(dāng)你操作一個(gè)流時(shí),更多的是以緩沖區(qū)為單位進(jìn)行操作,這是相對(duì)于用戶空間而言。對(duì)于內(nèi)核來(lái)說(shuō),也需要緩沖區(qū)。

假設(shè)有一個(gè)管道,進(jìn)程A為管道的寫(xiě)入方,B為管道的讀出方。

假設(shè)一開(kāi)始內(nèi)核緩沖區(qū)是空的,B作為讀出方,被阻塞著。然后首先A往管道寫(xiě)入,這時(shí)候內(nèi)核緩沖區(qū)由空的狀態(tài)變到非空狀態(tài),內(nèi)核就會(huì)產(chǎn)生一個(gè)事件告訴B該醒來(lái)了,這個(gè)事件姑且稱(chēng)之為“緩沖區(qū)非空”。

? ? 但是“緩沖區(qū)非空”事件通知B后,B卻還沒(méi)有讀出數(shù)據(jù);且內(nèi)核許諾了不能把寫(xiě)入管道中的數(shù)據(jù)丟掉這個(gè)時(shí)候,A寫(xiě)入的數(shù)據(jù)會(huì)滯留在內(nèi)核緩沖區(qū)中,如果內(nèi)核也緩沖區(qū)滿了,B仍未開(kāi)始讀數(shù)據(jù),最終內(nèi)核緩沖區(qū)會(huì)被填滿,這個(gè)時(shí)候會(huì)產(chǎn)生一個(gè)I/O事件,告訴進(jìn)程A,你該等等(阻塞)了,我們把這個(gè)事件定義為“緩沖區(qū)滿”。

假設(shè)后來(lái)B終于開(kāi)始讀數(shù)據(jù)了,于是內(nèi)核的緩沖區(qū)空了出來(lái),這時(shí)候內(nèi)核會(huì)告訴A,內(nèi)核緩沖區(qū)有空位了,你可以從長(zhǎng)眠中醒來(lái)了,繼續(xù)寫(xiě)數(shù)據(jù)了,我們把這個(gè)事件叫做“緩沖區(qū)非滿”

? ? 也許事件Y1已經(jīng)通知了A,但是A也沒(méi)有數(shù)據(jù)寫(xiě)入了,而B繼續(xù)讀出數(shù)據(jù),知道內(nèi)核緩沖區(qū)空了。這個(gè)時(shí)候內(nèi)核就告訴B,你需要阻塞了!,我們把這個(gè)時(shí)間定為“緩沖區(qū)空”。

這四個(gè)情形涵蓋了四個(gè)I/O事件,緩沖區(qū)滿,緩沖區(qū)空,緩沖區(qū)非空,緩沖區(qū)非滿(注都是說(shuō)的內(nèi)核緩沖區(qū),且這四個(gè)術(shù)語(yǔ)都是我生造的,僅為解釋其原理而造)。這四個(gè)I/O事件是進(jìn)行阻塞同步的根本。(如果不能理解“同步”是什么概念,請(qǐng)學(xué)習(xí)操作系統(tǒng)的鎖,信號(hào)量,條件變量等任務(wù)同步方面的相關(guān)知識(shí))。

? ? 然后我們來(lái)說(shuō)說(shuō)阻塞I/O的缺點(diǎn)。但是阻塞I/O模式下,一個(gè)線程只能處理一個(gè)流的I/O事件。如果想要同時(shí)處理多個(gè)流,要么多進(jìn)程(fork),要么多線程(pthread_create),很不幸這兩種方法效率都不高。

? ? 于是再來(lái)考慮非阻塞忙輪詢的I/O方式,我們發(fā)現(xiàn)我們可以同時(shí)處理多個(gè)流了(把一個(gè)流從阻塞模式切換到非阻塞模式再此不予討論):

  • while?true?{

  • ????for?i?in?stream[];?{

  • ????????if?i?has?data

  • ????????????read?until?unavailable

  • ????}

  • }

  • ? ? 我們只要不停的把所有流從頭到尾問(wèn)一遍,又從頭開(kāi)始。這樣就可以處理多個(gè)流了,但這樣的做法顯然不好,因?yàn)槿绻械牧鞫紱](méi)有數(shù)據(jù),那么只會(huì)白白浪費(fèi)CPU。這里要補(bǔ)充一點(diǎn),阻塞模式下,內(nèi)核對(duì)于I/O事件的處理是阻塞或者喚醒,而非阻塞模式下則把I/O事件交給其他對(duì)象(后文介紹的select以及epoll)處理甚至直接忽略。

    ? ? 為了避免CPU空轉(zhuǎn),可以引進(jìn)了一個(gè)代理(一開(kāi)始有一位叫做select的代理,后來(lái)又有一位叫做poll的代理,不過(guò)兩者的本質(zhì)是一樣的)。這個(gè)代理比較厲害,可以同時(shí)觀察許多流的I/O事件,在空閑的時(shí)候,會(huì)把當(dāng)前線程阻塞掉,當(dāng)有一個(gè)或多個(gè)流有I/O事件時(shí),就從阻塞態(tài)中醒來(lái),于是我們的程序就會(huì)輪詢一遍所有的流(于是我們可以把“忙”字去掉了)。代碼長(zhǎng)這樣:

  • while?true?{

  • ????select(streams[])

  • ????for?i?in?streams[]?{

  • ????????if?i?has?data

  • ????????????read?until?unavailable

  • ????}

  • }

  • ? ? 于是,如果沒(méi)有I/O事件產(chǎn)生,我們的程序就會(huì)阻塞在select處。但是依然有個(gè)問(wèn)題,我們從select那里僅僅知道了,有I/O事件發(fā)生了,但卻并不知道是那幾個(gè)流(可能有一個(gè),多個(gè),甚至全部),我們只能無(wú)差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫(xiě)入數(shù)據(jù)的流,對(duì)他們進(jìn)行操作。

    ? ? 但是使用select,我們有O(n)的無(wú)差別輪詢復(fù)雜度,同時(shí)處理的流越多,沒(méi)一次無(wú)差別輪詢時(shí)間就越長(zhǎng)。再次

    說(shuō)了這么多,終于能好好解釋epoll了

    ? ? epoll可以理解為event poll,不同于忙輪詢和無(wú)差別輪詢,epoll之會(huì)把哪個(gè)流發(fā)生了怎樣的I/O事件通知我們。此時(shí)我們對(duì)這些流的操作都是有意義的。(復(fù)雜度降低到了O(1))

    ? ? 在討論epoll的實(shí)現(xiàn)細(xì)節(jié)之前,先把epoll的相關(guān)操作列出:

    epoll_create 創(chuàng)建一個(gè)epoll對(duì)象,一般epollfd = epoll_create()

    epoll_ctl (epoll_add/epoll_del的合體),往epoll對(duì)象中增加/刪除某一個(gè)流的某一個(gè)事件

    比如

    epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注冊(cè)緩沖區(qū)非空事件,即有數(shù)據(jù)流入

    epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注冊(cè)緩沖區(qū)非滿事件,即流可以被寫(xiě)入

    epoll_wait(epollfd,...)等待直到注冊(cè)的事件發(fā)生

    (注:當(dāng)對(duì)一個(gè)非阻塞流的讀寫(xiě)發(fā)生緩沖區(qū)滿或緩沖區(qū)空,write/read會(huì)返回-1,并設(shè)置errno=EAGAIN。而epoll只關(guān)心緩沖區(qū)非滿和緩沖區(qū)非空事件)。

    一個(gè)epoll模式的代碼大概的樣子是:

  • while?true?{

  • ????active_stream[]?=?epoll_wait(epollfd)

  • ????for?i?in?active_stream[]?{

  • ????????read?or?write?till

  • ????}

  • }

  • ? ? 限于篇幅,我只說(shuō)這么多,以揭示原理性的東西,至于epoll的使用細(xì)節(jié),請(qǐng)參考man和google,實(shí)現(xiàn)細(xì)節(jié),請(qǐng)參閱linux kernel source。

    ?

    ------

    文章來(lái)源:http://blog.csdn.net/xiajun07061225/article/details/9250579

    ?

    什么是epoll

    ?

    epoll是什么?按照man手冊(cè)的說(shuō)法:是為處理大批量句柄而作了改進(jìn)的poll。當(dāng)然,這不是2.6內(nèi)核才有的,它是在2.5.44內(nèi)核中被引進(jìn)的(epoll(4) is a new API introduced in Linux kernel 2.5.44),它幾乎具備了之前所說(shuō)的一切優(yōu)點(diǎn),被公認(rèn)為L(zhǎng)inux2.6下性能最好的多路I/O就緒通知方法。

    ?

    epoll的相關(guān)系統(tǒng)調(diào)用

    epoll只有epoll_create,epoll_ctl,epoll_wait 3個(gè)系統(tǒng)調(diào)用。

    ?

    1. int epoll_create(int size);

    創(chuàng)建一個(gè)epoll的句柄。自從linux2.6.8之后,size參數(shù)是被忽略的。需要注意的是,當(dāng)創(chuàng)建好epoll句柄后,它就是會(huì)占用一個(gè)fd值,在linux下如果查看/proc/進(jìn)程id/fd/,是能夠看到這個(gè)fd的,所以在使用完epoll后,必須調(diào)用close()關(guān)閉,否則可能導(dǎo)致fd被耗盡。

    ?

    2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    epoll的事件注冊(cè)函數(shù),它不同于select()是在監(jiān)聽(tīng)事件時(shí)告訴內(nèi)核要監(jiān)聽(tīng)什么類(lèi)型的事件,而是在這里先注冊(cè)要監(jiān)聽(tīng)的事件類(lèi)型。

    第一個(gè)參數(shù)是epoll_create()的返回值。

    第二個(gè)參數(shù)表示動(dòng)作,用三個(gè)宏來(lái)表示:

    EPOLL_CTL_ADD:注冊(cè)新的fd到epfd中;

    EPOLL_CTL_MOD:修改已經(jīng)注冊(cè)的fd的監(jiān)聽(tīng)事件;

    EPOLL_CTL_DEL:從epfd中刪除一個(gè)fd;

    ?

    第三個(gè)參數(shù)是需要監(jiān)聽(tīng)的fd。

    第四個(gè)參數(shù)是告訴內(nèi)核需要監(jiān)聽(tīng)什么事,struct epoll_event結(jié)構(gòu)如下:

    ?

    [cpp]?view plain?copy

    ?

  • //保存觸發(fā)事件的某個(gè)文件描述符相關(guān)的數(shù)據(jù)(與具體使用方式有關(guān))??
  • ??
  • typedef?union?epoll_data?{??
  • ????void?*ptr;??
  • ????int?fd;??
  • ????__uint32_t?u32;??
  • ????__uint64_t?u64;??
  • }?epoll_data_t;??
  • ?//感興趣的事件和被觸發(fā)的事件??
  • struct?epoll_event?{??
  • ????__uint32_t?events;?/*?Epoll?events?*/??
  • ????epoll_data_t?data;?/*?User?data?variable?*/??
  • };??
  • ?

    ?

    events可以是以下幾個(gè)宏的集合:

    EPOLLIN?:表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉);

    EPOLLOUT:表示對(duì)應(yīng)的文件描述符可以寫(xiě);

    EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來(lái));

    EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤;

    EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷;

    EPOLLET:?將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)來(lái)說(shuō)的。

    EPOLLONESHOT:只監(jiān)聽(tīng)一次事件,當(dāng)監(jiān)聽(tīng)完這次事件之后,如果還需要繼續(xù)監(jiān)聽(tīng)這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里

    ?

    ?

    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

    收集在epoll監(jiān)控的事件中已經(jīng)發(fā)送的事件。參數(shù)events是分配好的epoll_event結(jié)構(gòu)體數(shù)組,epoll將會(huì)把發(fā)生的事件賦值到events數(shù)組中(events不可以是空指針,內(nèi)核只負(fù)責(zé)把數(shù)據(jù)復(fù)制到這個(gè)events數(shù)組中,不會(huì)去幫助我們?cè)谟脩魬B(tài)中分配內(nèi)存)。maxevents告之內(nèi)核這個(gè)events有多大,這個(gè)?maxevents的值不能大于創(chuàng)建epoll_create()時(shí)的size,參數(shù)timeout是超時(shí)時(shí)間(毫秒,0會(huì)立即返回,-1將不確定,也有說(shuō)法說(shuō)是永久阻塞)。如果函數(shù)調(diào)用成功,返回對(duì)應(yīng)I/O上已準(zhǔn)備好的文件描述符數(shù)目,如返回0表示已超時(shí)。

    ?

    epoll工作原理

    epoll同樣只告知那些就緒的文件描述符,而且當(dāng)我們調(diào)用epoll_wait()獲得就緒文件描述符時(shí),返回的不是實(shí)際的描述符,而是一個(gè)代表就緒描述符數(shù)量的值,你只需要去epoll指定的一個(gè)數(shù)組中依次取得相應(yīng)數(shù)量的文件描述符即可,這里也使用了內(nèi)存映射(mmap)技術(shù),這樣便徹底省掉了這些文件描述符在系統(tǒng)調(diào)用時(shí)復(fù)制的開(kāi)銷(xiāo)。

    ?

    另一個(gè)本質(zhì)的改進(jìn)在于epoll采用基于事件的就緒通知方式。在select/poll中,進(jìn)程只有在調(diào)用一定的方法后,內(nèi)核才對(duì)所有監(jiān)視的文件描述符進(jìn)行掃描,而epoll事先通過(guò)epoll_ctl()來(lái)注冊(cè)一個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí),內(nèi)核會(huì)采用類(lèi)似callback的回調(diào)機(jī)制,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知。

    ?

    Epoll的2種工作方式-水平觸發(fā)(LT)和邊緣觸發(fā)(ET)

    ?

    假如有這樣一個(gè)例子:

    1. 我們已經(jīng)把一個(gè)用來(lái)從管道中讀取數(shù)據(jù)的文件句柄(RFD)添加到epoll描述符

    2. 這個(gè)時(shí)候從管道的另一端被寫(xiě)入了2KB的數(shù)據(jù)

    3. 調(diào)用epoll_wait(2),并且它會(huì)返回RFD,說(shuō)明它已經(jīng)準(zhǔn)備好讀取操作

    4. 然后我們讀取了1KB的數(shù)據(jù)

    5. 調(diào)用epoll_wait(2)......

    ?

    Edge Triggered 工作模式:

    如果我們?cè)诘?步將RFD添加到epoll描述符的時(shí)候使用了EPOLLET標(biāo)志,那么在第5步調(diào)用epoll_wait(2)之后將有可能會(huì)掛起,因?yàn)槭S嗟臄?shù)據(jù)還存在于文件的輸入緩沖區(qū)內(nèi),而且數(shù)據(jù)發(fā)出端還在等待一個(gè)針對(duì)已經(jīng)發(fā)出數(shù)據(jù)的反饋信息。只有在監(jiān)視的文件句柄上發(fā)生了某個(gè)事件的時(shí)候 ET 工作模式才會(huì)匯報(bào)事件。因此在第5步的時(shí)候,調(diào)用者可能會(huì)放棄等待仍在存在于文件輸入緩沖區(qū)內(nèi)的剩余數(shù)據(jù)。在上面的例子中,會(huì)有一個(gè)事件產(chǎn)生在RFD句柄上,因?yàn)樵诘?步執(zhí)行了一個(gè)寫(xiě)操作,然后,事件將會(huì)在第3步被銷(xiāo)毀。因?yàn)榈?步的讀取操作沒(méi)有讀空文件輸入緩沖區(qū)內(nèi)的數(shù)據(jù),因此我們?cè)诘?步調(diào)用 epoll_wait(2)完成后,是否掛起是不確定的。epoll工作在ET模式的時(shí)候,必須使用非阻塞套接口,以避免由于一個(gè)文件句柄的阻塞讀/阻塞寫(xiě)操作把處理多個(gè)文件描述符的任務(wù)餓死。最好以下面的方式調(diào)用ET模式的epoll接口,在后面會(huì)介紹避免可能的缺陷。

    ? ?i ? ?基于非阻塞文件句柄

    ? ?ii ? 只有當(dāng)read(2)或者write(2)返回EAGAIN時(shí)才需要掛起,等待。但這并不是說(shuō)每次read()時(shí)都需要循環(huán)讀,直到讀到產(chǎn)生一個(gè)EAGAIN才認(rèn)為此次事件處理完成,當(dāng)read()返回的讀到的數(shù)據(jù)長(zhǎng)度小于請(qǐng)求的數(shù)據(jù)長(zhǎng)度時(shí),就可以確定此時(shí)緩沖中已沒(méi)有數(shù)據(jù)了,也就可以認(rèn)為此事讀事件已處理完成。

    ?

    Level Triggered 工作模式

    相反的,以LT方式調(diào)用epoll接口的時(shí)候,它就相當(dāng)于一個(gè)速度比較快的poll(2),并且無(wú)論后面的數(shù)據(jù)是否被使用,因此他們具有同樣的職能。因?yàn)榧词故褂肊T模式的epoll,在收到多個(gè)chunk的數(shù)據(jù)的時(shí)候仍然會(huì)產(chǎn)生多個(gè)事件。調(diào)用者可以設(shè)定EPOLLONESHOT標(biāo)志,在 epoll_wait(2)收到事件后epoll會(huì)與事件關(guān)聯(lián)的文件句柄從epoll描述符中禁止掉。因此當(dāng)EPOLLONESHOT設(shè)定后,使用帶有 EPOLL_CTL_MOD標(biāo)志的epoll_ctl(2)處理文件句柄就成為調(diào)用者必須作的事情。

    ?

    ?

    LT(level triggered)是epoll缺省的工作方式,并且同時(shí)支持block和no-block socket.在這種做法中,內(nèi)核告訴你一個(gè)文件描述符是否就緒了,然后你可以對(duì)這個(gè)就緒的fd進(jìn)行IO操作。如果你不作任何操作,內(nèi)核還是會(huì)繼續(xù)通知你?的,所以,這種模式編程出錯(cuò)誤可能性要小一點(diǎn)。傳統(tǒng)的select/poll都是這種模型的代表.

    ?

    ET (edge-triggered)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET與LT的區(qū)別在于,當(dāng)一個(gè)新的事件到來(lái)時(shí),ET模式下當(dāng)然可以從epoll_wait調(diào)用中獲取到這個(gè)事件,可是如果這次沒(méi)有把這個(gè)事件對(duì)應(yīng)的套接字緩沖區(qū)處理完,在這個(gè)套接字中沒(méi)有新的事件再次到來(lái)時(shí),在ET模式下是無(wú)法再次從epoll_wait調(diào)用中獲取這個(gè)事件的。而LT模式正好相反,只要一個(gè)事件對(duì)應(yīng)的套接字緩沖區(qū)還有數(shù)據(jù),就總能從epoll_wait中獲取這個(gè)事件。

    因此,LT模式下開(kāi)發(fā)基于epoll的應(yīng)用要簡(jiǎn)單些,不太容易出錯(cuò)。而在ET模式下事件發(fā)生時(shí),如果沒(méi)有徹底地將緩沖區(qū)數(shù)據(jù)處理完,則會(huì)導(dǎo)致緩沖區(qū)中的用戶請(qǐng)求得不到響應(yīng)。

    圖示說(shuō)明:

    ?

    Nginx默認(rèn)采用ET模式來(lái)使用epoll。

    ?

    epoll的優(yōu)點(diǎn):

    1.支持一個(gè)進(jìn)程打開(kāi)大數(shù)目的socket描述符(FD)

    ??? select?最不能忍受的是一個(gè)進(jìn)程所打開(kāi)的FD是有一定限制的,由FD_SETSIZE設(shè)置,默認(rèn)值是2048。對(duì)于那些需要支持的上萬(wàn)連接數(shù)目的IM服務(wù)器來(lái)說(shuō)顯然太少了。這時(shí)候你一是可以選擇修改這個(gè)宏然后重新編譯內(nèi)核,不過(guò)資料也同時(shí)指出這樣會(huì)帶來(lái)網(wǎng)絡(luò)效率的下降,二是可以選擇多進(jìn)程的解決方案(傳統(tǒng)的?Apache方案),不過(guò)雖然linux上面創(chuàng)建進(jìn)程的代價(jià)比較小,但仍舊是不可忽視的,加上進(jìn)程間數(shù)據(jù)同步遠(yuǎn)比不上線程間同步的高效,所以也不是一種完美的方案。不過(guò)?epoll則沒(méi)有這個(gè)限制,它所支持的FD上限是最大可以打開(kāi)文件的數(shù)目,這個(gè)數(shù)字一般遠(yuǎn)大于2048,舉個(gè)例子,在1GB內(nèi)存的機(jī)器上大約是10萬(wàn)左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來(lái)說(shuō)這個(gè)數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大。

    ?

    2.IO效率不隨FD數(shù)目增加而線性下降

    ????傳統(tǒng)的select/poll另一個(gè)致命弱點(diǎn)就是當(dāng)你擁有一個(gè)很大的socket集合,不過(guò)由于網(wǎng)絡(luò)延時(shí),任一時(shí)間只有部分的socket是"活躍"的,但是select/poll每次調(diào)用都會(huì)線性掃描全部的集合,導(dǎo)致效率呈現(xiàn)線性下降。但是epoll不存在這個(gè)問(wèn)題,它只會(huì)對(duì)"活躍"的socket進(jìn)行操作---這是因?yàn)樵趦?nèi)核實(shí)現(xiàn)中epoll是根據(jù)每個(gè)fd上面的callback函數(shù)實(shí)現(xiàn)的。那么,只有"活躍"的socket才會(huì)主動(dòng)的去調(diào)用?callback函數(shù),其他idle狀態(tài)socket則不會(huì),在這點(diǎn)上,epoll實(shí)現(xiàn)了一個(gè)"偽"AIO,因?yàn)檫@時(shí)候推動(dòng)力在os內(nèi)核。在一些?benchmark中,如果所有的socket基本上都是活躍的---比如一個(gè)高速LAN環(huán)境,epoll并不比select/poll有什么效率,相反,如果過(guò)多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模擬WAN環(huán)境,epoll的效率就遠(yuǎn)在select/poll之上了。

    ?

    3.使用mmap加速內(nèi)核與用戶空間的消息傳遞

    ????這點(diǎn)實(shí)際上涉及到epoll的具體實(shí)現(xiàn)了。無(wú)論是select,poll還是epoll都需要內(nèi)核把FD消息通知給用戶空間,如何避免不必要的內(nèi)存拷貝就很重要,在這點(diǎn)上,epoll是通過(guò)內(nèi)核于用戶空間mmap同一塊內(nèi)存實(shí)現(xiàn)的。而如果你想我一樣從2.5內(nèi)核就關(guān)注epoll的話,一定不會(huì)忘記手工?mmap這一步的。

    ?

    4.內(nèi)核微調(diào)

    這一點(diǎn)其實(shí)不算epoll的優(yōu)點(diǎn)了,而是整個(gè)linux平臺(tái)的優(yōu)點(diǎn)。也許你可以懷疑linux平臺(tái),但是你無(wú)法回避linux平臺(tái)賦予你微調(diào)內(nèi)核的能力。比如,內(nèi)核TCP/IP協(xié)議棧使用內(nèi)存池管理sk_buff結(jié)構(gòu),那么可以在運(yùn)行時(shí)期動(dòng)態(tài)調(diào)整這個(gè)內(nèi)存pool(skb_head_pool)的大小---?通過(guò)echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數(shù)的第2個(gè)參數(shù)(TCP完成3次握手的數(shù)據(jù)包隊(duì)列長(zhǎng)度),也可以根據(jù)你平臺(tái)內(nèi)存大小動(dòng)態(tài)調(diào)整。更甚至在一個(gè)數(shù)據(jù)包面數(shù)目巨大但同時(shí)每個(gè)數(shù)據(jù)包本身大小卻很小的特殊系統(tǒng)上嘗試最新的NAPI網(wǎng)卡驅(qū)動(dòng)架構(gòu)。

    ?

    linux下epoll如何實(shí)現(xiàn)高效處理百萬(wàn)句柄的

    開(kāi)發(fā)高性能網(wǎng)絡(luò)程序時(shí),windows開(kāi)發(fā)者們言必稱(chēng)iocp,linux開(kāi)發(fā)者們則言必稱(chēng)epoll。大家都明白epoll是一種IO多路復(fù)用技術(shù),可以非常高效的處理數(shù)以百萬(wàn)計(jì)的socket句柄,比起以前的select和poll效率高大發(fā)了。我們用起epoll來(lái)都感覺(jué)挺爽,確實(shí)快,那么,它到底為什么可以高速處理這么多并發(fā)連接呢?

    ?

    使用起來(lái)很清晰,首先要調(diào)用epoll_create建立一個(gè)epoll對(duì)象。參數(shù)size是內(nèi)核保證能夠正確處理的最大句柄數(shù),多于這個(gè)最大數(shù)時(shí)內(nèi)核可不保證效果。

    ?

    epoll_ctl可以操作上面建立的epoll,例如,將剛建立的socket加入到epoll中讓其監(jiān)控,或者把?epoll正在監(jiān)控的某個(gè)socket句柄移出epoll,不再監(jiān)控它等等。

    ?

    epoll_wait在調(diào)用時(shí),在給定的timeout時(shí)間內(nèi),當(dāng)在監(jiān)控的所有句柄中有事件發(fā)生時(shí),就返回用戶態(tài)的進(jìn)程。

    ?

    從上面的調(diào)用方式就可以看到epoll比select/poll的優(yōu)越之處:因?yàn)楹笳呙看握{(diào)用時(shí)都要傳遞你所要監(jiān)控的所有socket給select/poll系統(tǒng)調(diào)用,這意味著需要將用戶態(tài)的socket列表copy到內(nèi)核態(tài),如果以萬(wàn)計(jì)的句柄會(huì)導(dǎo)致每次都要copy幾十幾百KB的內(nèi)存到內(nèi)核態(tài),非常低效。而我們調(diào)用epoll_wait時(shí)就相當(dāng)于以往調(diào)用select/poll,但是這時(shí)卻不用傳遞socket句柄給內(nèi)核,因?yàn)閮?nèi)核已經(jīng)在epoll_ctl中拿到了要監(jiān)控的句柄列表。

    ?

    所以,實(shí)際上在你調(diào)用epoll_create后,內(nèi)核就已經(jīng)在內(nèi)核態(tài)開(kāi)始準(zhǔn)備幫你存儲(chǔ)要監(jiān)控的句柄了,每次調(diào)用epoll_ctl只是在往內(nèi)核的數(shù)據(jù)結(jié)構(gòu)里塞入新的socket句柄。

    ?當(dāng)一個(gè)進(jìn)程調(diào)用epoll_creaqte方法時(shí),Linux內(nèi)核會(huì)創(chuàng)建一個(gè)eventpoll結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體中有兩個(gè)成員與epoll的使用方式密切相關(guān):

    [cpp]?view plain?copy

    ?

  • /*?
  • ?
  • ?171?*?This?structure?is?stored?inside?the?"private_data"?member?of?the?file?
  • ?
  • ?172?*?structure?and?represents?the?main?data?structure?for?the?eventpoll?
  • ?
  • ?173?*?interface.?
  • ?
  • ?174?*/??
  • ??
  • ?175struct?eventpoll?{??
  • ??
  • ?176????????/*?Protect?the?access?to?this?structure?*/??
  • ??
  • ?177????????spinlock_t?lock;??
  • ??
  • ?178??
  • ??
  • ?179????????/*?
  • ?
  • ?180?????????*?This?mutex?is?used?to?ensure?that?files?are?not?removed?
  • ?
  • ?181?????????*?while?epoll?is?using?them.?This?is?held?during?the?event?
  • ?
  • ?182?????????*?collection?loop,?the?file?cleanup?path,?the?epoll?file?exit?
  • ?
  • ?183?????????*?code?and?the?ctl?operations.?
  • ?
  • ?184?????????*/??
  • ??
  • ?185????????struct?mutex?mtx;??
  • ??
  • ?186??
  • ??
  • ?187????????/*?Wait?queue?used?by?sys_epoll_wait()?*/??
  • ??
  • ?188????????wait_queue_head_t?wq;??
  • ??
  • ?189??
  • ??
  • ?190????????/*?Wait?queue?used?by?file->poll()?*/??
  • ??
  • ?191????????wait_queue_head_t?poll_wait;??
  • ??
  • ?192??
  • ??
  • ?193????????/*?List?of?ready?file?descriptors?*/??
  • ??
  • ?194????????struct?list_head?rdllist;??
  • ??
  • ?195??
  • ??
  • ?196????????/*?RB?tree?root?used?to?store?monitored?fd?structs?*/??
  • ??
  • ?197????????struct?rb_root?rbr;//紅黑樹(shù)根節(jié)點(diǎn),這棵樹(shù)存儲(chǔ)著所有添加到epoll中的事件,也就是這個(gè)epoll監(jiān)控的事件??
  • ?198??
  • ?199????????/*?
  • ?200?????????*?This?is?a?single?linked?list?that?chains?all?the?"struct?epitem"?that?
  • ?201?????????*?happened?while?transferring?ready?events?to?userspace?w/out?
  • ?202?????????*?holding?->lock.?
  • ?203?????????*/??
  • ?204????????struct?epitem?*ovflist;??
  • ?205??
  • ?206????????/*?wakeup_source?used?when?ep_scan_ready_list?is?running?*/??
  • ?207????????struct?wakeup_source?*ws;??
  • ?208??
  • ?209????????/*?The?user?that?created?the?eventpoll?descriptor?*/??
  • ?210????????struct?user_struct?*user;??
  • ?211??
  • ?212????????struct?file?*file;??
  • ?213??
  • ?214????????/*?used?to?optimize?loop?detection?check?*/??
  • ?215????????int?visited;??
  • ?216????????struct?list_head?visited_list_link;//雙向鏈表中保存著將要通過(guò)epoll_wait返回給用戶的、滿足條件的事件??
  • ?217};??
  • ?

    每一個(gè)epoll對(duì)象都有一個(gè)獨(dú)立的eventpoll結(jié)構(gòu)體,這個(gè)結(jié)構(gòu)體會(huì)在內(nèi)核空間中創(chuàng)造獨(dú)立的內(nèi)存,用于存儲(chǔ)使用epoll_ctl方法向epoll對(duì)象中添加進(jìn)來(lái)的事件。這樣,重復(fù)的事件就可以通過(guò)紅黑樹(shù)而高效的識(shí)別出來(lái)。

    ?

    在epoll中,對(duì)于每一個(gè)事件都會(huì)建立一個(gè)epitem結(jié)構(gòu)體:

    [cpp]?view plain?copy

    ?

  • /*?
  • ?130?*?Each?file?descriptor?added?to?the?eventpoll?interface?will?
  • ?131?*?have?an?entry?of?this?type?linked?to?the?"rbr"?RB?tree.?
  • ?132?*?Avoid?increasing?the?size?of?this?struct,?there?can?be?many?thousands?
  • ?133?*?of?these?on?a?server?and?we?do?not?want?this?to?take?another?cache?line.?
  • ?134?*/??
  • ?135struct?epitem?{??
  • ?136????????/*?RB?tree?node?used?to?link?this?structure?to?the?eventpoll?RB?tree?*/??
  • ?137????????struct?rb_node?rbn;??
  • ?138??
  • ?139????????/*?List?header?used?to?link?this?structure?to?the?eventpoll?ready?list?*/??
  • ?140????????struct?list_head?rdllink;??
  • ?141??
  • ?142????????/*?
  • ?143?????????*?Works?together?"struct?eventpoll"->ovflist?in?keeping?the?
  • ?144?????????*?single?linked?chain?of?items.?
  • ?145?????????*/??
  • ?146????????struct?epitem?*next;??
  • ?147??
  • ?148????????/*?The?file?descriptor?information?this?item?refers?to?*/??
  • ?149????????struct?epoll_filefd?ffd;??
  • ?150??
  • ?151????????/*?Number?of?active?wait?queue?attached?to?poll?operations?*/??
  • ?152????????int?nwait;??
  • ?153??
  • ?154????????/*?List?containing?poll?wait?queues?*/??
  • ?155????????struct?list_head?pwqlist;??
  • ?156??
  • ?157????????/*?The?"container"?of?this?item?*/??
  • ?158????????struct?eventpoll?*ep;??
  • ?159??
  • ?160????????/*?List?header?used?to?link?this?item?to?the?"struct?file"?items?list?*/??
  • ?161????????struct?list_head?fllink;??
  • ?162??
  • ?163????????/*?wakeup_source?used?when?EPOLLWAKEUP?is?set?*/??
  • ?164????????struct?wakeup_source?__rcu?*ws;??
  • ?165??
  • ?166????????/*?The?structure?that?describe?the?interested?events?and?the?source?fd?*/??
  • ?167????????struct?epoll_event?event;??
  • ?168};??
  • ?

    此外,epoll還維護(hù)了一個(gè)雙鏈表,用戶存儲(chǔ)發(fā)生的事件。當(dāng)epoll_wait調(diào)用時(shí),僅僅觀察這個(gè)list鏈表里有沒(méi)有數(shù)據(jù)即eptime項(xiàng)即可。有數(shù)據(jù)就返回,沒(méi)有數(shù)據(jù)就sleep,等到timeout時(shí)間到后即使鏈表沒(méi)數(shù)據(jù)也返回。所以,epoll_wait非常高效。

    ?

    而且,通常情況下即使我們要監(jiān)控百萬(wàn)計(jì)的句柄,大多一次也只返回很少量的準(zhǔn)備就緒句柄而已,所以,epoll_wait僅需要從內(nèi)核態(tài)copy少量的句柄到用戶態(tài)而已,如何能不高效?!

    ?

    那么,這個(gè)準(zhǔn)備就緒list鏈表是怎么維護(hù)的呢?當(dāng)我們執(zhí)行epoll_ctl時(shí),除了把socket放到epoll文件系統(tǒng)里file對(duì)象對(duì)應(yīng)的紅黑樹(shù)上之外,還會(huì)給內(nèi)核中斷處理程序注冊(cè)一個(gè)回調(diào)函數(shù),告訴內(nèi)核,如果這個(gè)句柄的中斷到了,就把它放到準(zhǔn)備就緒list鏈表里。所以,當(dāng)一個(gè)socket上有數(shù)據(jù)到了,內(nèi)核在把網(wǎng)卡上的數(shù)據(jù)copy到內(nèi)核中后就來(lái)把socket插入到準(zhǔn)備就緒鏈表里了。

    ?

    如此,一顆紅黑樹(shù),一張準(zhǔn)備就緒句柄鏈表,少量的內(nèi)核cache,就幫我們解決了大并發(fā)下的socket處理問(wèn)題。執(zhí)行epoll_create時(shí),創(chuàng)建了紅黑樹(shù)和就緒鏈表,執(zhí)行epoll_ctl時(shí),如果增加socket句柄,則檢查在紅黑樹(shù)中是否存在,存在立即返回,不存在則添加到樹(shù)干上,然后向內(nèi)核注冊(cè)回調(diào)函數(shù),用于當(dāng)中斷事件來(lái)臨時(shí)向準(zhǔn)備就緒鏈表中插入數(shù)據(jù)。執(zhí)行epoll_wait時(shí)立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可。

    ?

    epoll的使用方法

    那么究竟如何來(lái)使用epoll呢?其實(shí)非常簡(jiǎn)單。

    ?

    通過(guò)在包含一個(gè)頭文件#include <sys/epoll.h>?以及幾個(gè)簡(jiǎn)單的API將可以大大的提高你的網(wǎng)絡(luò)服務(wù)器的支持人數(shù)。

    ?

    首先通過(guò)create_epoll(int maxfds)來(lái)創(chuàng)建一個(gè)epoll的句柄。這個(gè)函數(shù)會(huì)返回一個(gè)新的epoll句柄,之后的所有操作將通過(guò)這個(gè)句柄來(lái)進(jìn)行操作。在用完之后,記得用close()來(lái)關(guān)閉這個(gè)創(chuàng)建出來(lái)的epoll句柄。

    ?

    之后在你的網(wǎng)絡(luò)主循環(huán)里面,每一幀的調(diào)用epoll_wait(int epfd, epoll_event events, int max events, int timeout)來(lái)查詢所有的網(wǎng)絡(luò)接口,看哪一個(gè)可以讀,哪一個(gè)可以寫(xiě)了。基本的語(yǔ)法為:

    nfds = epoll_wait(kdpfd, events, maxevents, -1);

    ?

    其中kdpfd為用epoll_create創(chuàng)建之后的句柄,events是一個(gè)epoll_event*的指針,當(dāng)epoll_wait這個(gè)函數(shù)操作成功之后,epoll_events里面將儲(chǔ)存所有的讀寫(xiě)事件。max_events是當(dāng)前需要監(jiān)聽(tīng)的所有socket句柄數(shù)。最后一個(gè)timeout是?epoll_wait的超時(shí),為0的時(shí)候表示馬上返回,為-1的時(shí)候表示一直等下去,直到有事件返回,為任意正整數(shù)的時(shí)候表示等這么長(zhǎng)的時(shí)間,如果一直沒(méi)有事件,則返回。一般如果網(wǎng)絡(luò)主循環(huán)是單獨(dú)的線程的話,可以用-1來(lái)等,這樣可以保證一些效率,如果是和主邏輯在同一個(gè)線程的話,則可以用0來(lái)保證主循環(huán)的效率。

    ?

    epoll_wait返回之后應(yīng)該是一個(gè)循環(huán),遍歷所有的事件。

    ?

    ?

    幾乎所有的epoll程序都使用下面的框架:

    [cpp]?view plain?copy

    ?

  • for(?;?;?)??
  • ???{??
  • ???????nfds?=?epoll_wait(epfd,events,20,500);??
  • ???????for(i=0;i<nfds;++i)??
  • ???????{??
  • ???????????if(events[i].data.fd==listenfd)?//有新的連接??
  • ???????????{??
  • ???????????????connfd?=?accept(listenfd,(sockaddr?*)&clientaddr,?&clilen);?//accept這個(gè)連接??
  • ???????????????ev.data.fd=connfd;??
  • ???????????????ev.events=EPOLLIN|EPOLLET;??
  • ???????????????epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);?//將新的fd添加到epoll的監(jiān)聽(tīng)隊(duì)列中??
  • ???????????}??
  • ??
  • ???????????else?if(?events[i].events&EPOLLIN?)?//接收到數(shù)據(jù),讀socket??
  • ???????????{??
  • ???????????????n?=?read(sockfd,?line,?MAXLINE))?<?0????//讀??
  • ???????????????ev.data.ptr?=?md;?????//md為自定義類(lèi)型,添加數(shù)據(jù)??
  • ???????????????ev.events=EPOLLOUT|EPOLLET;??
  • ???????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標(biāo)識(shí)符,等待下一個(gè)循環(huán)時(shí)發(fā)送數(shù)據(jù),異步處理的精髓??
  • ???????????}??
  • ???????????else?if(events[i].events&EPOLLOUT)?//有數(shù)據(jù)待發(fā)送,寫(xiě)socket??
  • ???????????{??
  • ???????????????struct?myepoll_data*?md?=?(myepoll_data*)events[i].data.ptr;????//取數(shù)據(jù)??
  • ???????????????sockfd?=?md->fd;??
  • ???????????????send(?sockfd,?md->ptr,?strlen((char*)md->ptr),?0?);????????//發(fā)送數(shù)據(jù)??
  • ???????????????ev.data.fd=sockfd;??
  • ???????????????ev.events=EPOLLIN|EPOLLET;??
  • ???????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);?//修改標(biāo)識(shí)符,等待下一個(gè)循環(huán)時(shí)接收數(shù)據(jù)??
  • ???????????}??
  • ???????????else??
  • ???????????{??
  • ???????????????//其他的處理??
  • ???????????}??
  • ???????}??
  • ???}??
  • ?

    epoll的程序?qū)嵗?/h2>

    ?

    [cpp]?view plain?copy

    ?

  • ?#include?<stdio.h>??
  • #include?<stdlib.h>??
  • #include?<unistd.h>??
  • #include?<errno.h>??
  • #include?<sys/socket.h>??
  • #include?<netdb.h>??
  • #include?<fcntl.h>??
  • #include?<sys/epoll.h>??
  • #include?<string.h>??
  • ??
  • #define?MAXEVENTS?64??
  • ??
  • //函數(shù):??
  • //功能:創(chuàng)建和綁定一個(gè)TCP?socket??
  • //參數(shù):端口??
  • //返回值:創(chuàng)建的socket??
  • static?int??
  • create_and_bind?(char?*port)??
  • {??
  • ??struct?addrinfo?hints;??
  • ??struct?addrinfo?*result,?*rp;??
  • ??int?s,?sfd;??
  • ??
  • ??memset?(&hints,?0,?sizeof?(struct?addrinfo));??
  • ??hints.ai_family?=?AF_UNSPEC;?????/*?Return?IPv4?and?IPv6?choices?*/??
  • ??hints.ai_socktype?=?SOCK_STREAM;?/*?We?want?a?TCP?socket?*/??
  • ??hints.ai_flags?=?AI_PASSIVE;?????/*?All?interfaces?*/??
  • ??
  • ??s?=?getaddrinfo?(NULL,?port,?&hints,?&result);??
  • ??if?(s?!=?0)??
  • ????{??
  • ??????fprintf?(stderr,?"getaddrinfo:?%s\n",?gai_strerror?(s));??
  • ??????return?-1;??
  • ????}??
  • ??
  • ??for?(rp?=?result;?rp?!=?NULL;?rp?=?rp->ai_next)??
  • ????{??
  • ??????sfd?=?socket?(rp->ai_family,?rp->ai_socktype,?rp->ai_protocol);??
  • ??????if?(sfd?==?-1)??
  • ????????continue;??
  • ??
  • ??????s?=?bind?(sfd,?rp->ai_addr,?rp->ai_addrlen);??
  • ??????if?(s?==?0)??
  • ????????{??
  • ??????????/*?We?managed?to?bind?successfully!?*/??
  • ??????????break;??
  • ????????}??
  • ??
  • ??????close?(sfd);??
  • ????}??
  • ??
  • ??if?(rp?==?NULL)??
  • ????{??
  • ??????fprintf?(stderr,?"Could?not?bind\n");??
  • ??????return?-1;??
  • ????}??
  • ??
  • ??freeaddrinfo?(result);??
  • ??
  • ??return?sfd;??
  • }??
  • ??
  • ??
  • //函數(shù)??
  • //功能:設(shè)置socket為非阻塞的??
  • static?int??
  • make_socket_non_blocking?(int?sfd)??
  • {??
  • ??int?flags,?s;??
  • ??
  • ??//得到文件狀態(tài)標(biāo)志??
  • ??flags?=?fcntl?(sfd,?F_GETFL,?0);??
  • ??if?(flags?==?-1)??
  • ????{??
  • ??????perror?("fcntl");??
  • ??????return?-1;??
  • ????}??
  • ??
  • ??//設(shè)置文件狀態(tài)標(biāo)志??
  • ??flags?|=?O_NONBLOCK;??
  • ??s?=?fcntl?(sfd,?F_SETFL,?flags);??
  • ??if?(s?==?-1)??
  • ????{??
  • ??????perror?("fcntl");??
  • ??????return?-1;??
  • ????}??
  • ??
  • ??return?0;??
  • }??
  • ??
  • //端口由參數(shù)argv[1]指定??
  • int??
  • main?(int?argc,?char?*argv[])??
  • {??
  • ??int?sfd,?s;??
  • ??int?efd;??
  • ??struct?epoll_event?event;??
  • ??struct?epoll_event?*events;??
  • ??
  • ??if?(argc?!=?2)??
  • ????{??
  • ??????fprintf?(stderr,?"Usage:?%s?[port]\n",?argv[0]);??
  • ??????exit?(EXIT_FAILURE);??
  • ????}??
  • ??
  • ??sfd?=?create_and_bind?(argv[1]);??
  • ??if?(sfd?==?-1)??
  • ????abort?();??
  • ??
  • ??s?=?make_socket_non_blocking?(sfd);??
  • ??if?(s?==?-1)??
  • ????abort?();??
  • ??
  • ??s?=?listen?(sfd,?SOMAXCONN);??
  • ??if?(s?==?-1)??
  • ????{??
  • ??????perror?("listen");??
  • ??????abort?();??
  • ????}??
  • ??
  • ??//除了參數(shù)size被忽略外,此函數(shù)和epoll_create完全相同??
  • ??efd?=?epoll_create1?(0);??
  • ??if?(efd?==?-1)??
  • ????{??
  • ??????perror?("epoll_create");??
  • ??????abort?();??
  • ????}??
  • ??
  • ??event.data.fd?=?sfd;??
  • ??event.events?=?EPOLLIN?|?EPOLLET;//讀入,邊緣觸發(fā)方式??
  • ??s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?sfd,?&event);??
  • ??if?(s?==?-1)??
  • ????{??
  • ??????perror?("epoll_ctl");??
  • ??????abort?();??
  • ????}??
  • ??
  • ??/*?Buffer?where?events?are?returned?*/??
  • ??events?=?calloc?(MAXEVENTS,?sizeof?event);??
  • ??
  • ??/*?The?event?loop?*/??
  • ??while?(1)??
  • ????{??
  • ??????int?n,?i;??
  • ??
  • ??????n?=?epoll_wait?(efd,?events,?MAXEVENTS,?-1);??
  • ??????for?(i?=?0;?i?<?n;?i++)??
  • ????????{??
  • ??????????if?((events[i].events?&?EPOLLERR)?||??
  • ??????????????(events[i].events?&?EPOLLHUP)?||??
  • ??????????????(!(events[i].events?&?EPOLLIN)))??
  • ????????????{??
  • ??????????????/*?An?error?has?occured?on?this?fd,?or?the?socket?is?not?
  • ?????????????????ready?for?reading?(why?were?we?notified?then?)?*/??
  • ??????????????fprintf?(stderr,?"epoll?error\n");??
  • ??????????????close?(events[i].data.fd);??
  • ??????????????continue;??
  • ????????????}??
  • ??
  • ??????????else?if?(sfd?==?events[i].data.fd)??
  • ????????????{??
  • ??????????????/*?We?have?a?notification?on?the?listening?socket,?which?
  • ?????????????????means?one?or?more?incoming?connections.?*/??
  • ??????????????while?(1)??
  • ????????????????{??
  • ??????????????????struct?sockaddr?in_addr;??
  • ??????????????????socklen_t?in_len;??
  • ??????????????????int?infd;??
  • ??????????????????char?hbuf[NI_MAXHOST],?sbuf[NI_MAXSERV];??
  • ??
  • ??????????????????in_len?=?sizeof?in_addr;??
  • ??????????????????infd?=?accept?(sfd,?&in_addr,?&in_len);??
  • ??????????????????if?(infd?==?-1)??
  • ????????????????????{??
  • ??????????????????????if?((errno?==?EAGAIN)?||??
  • ??????????????????????????(errno?==?EWOULDBLOCK))??
  • ????????????????????????{??
  • ??????????????????????????/*?We?have?processed?all?incoming?
  • ?????????????????????????????connections.?*/??
  • ??????????????????????????break;??
  • ????????????????????????}??
  • ??????????????????????else??
  • ????????????????????????{??
  • ??????????????????????????perror?("accept");??
  • ??????????????????????????break;??
  • ????????????????????????}??
  • ????????????????????}??
  • ??
  • ??????????????????????????????????//將地址轉(zhuǎn)化為主機(jī)名或者服務(wù)名??
  • ??????????????????s?=?getnameinfo?(&in_addr,?in_len,??
  • ???????????????????????????????????hbuf,?sizeof?hbuf,??
  • ???????????????????????????????????sbuf,?sizeof?sbuf,??
  • ???????????????????????????????????NI_NUMERICHOST?|?NI_NUMERICSERV);//flag參數(shù):以數(shù)字名返回??
  • ??????????????????????????????????//主機(jī)地址和服務(wù)地址??
  • ??
  • ??????????????????if?(s?==?0)??
  • ????????????????????{??
  • ??????????????????????printf("Accepted?connection?on?descriptor?%d?"??
  • ?????????????????????????????"(host=%s,?port=%s)\n",?infd,?hbuf,?sbuf);??
  • ????????????????????}??
  • ??
  • ??????????????????/*?Make?the?incoming?socket?non-blocking?and?add?it?to?the?
  • ?????????????????????list?of?fds?to?monitor.?*/??
  • ??????????????????s?=?make_socket_non_blocking?(infd);??
  • ??????????????????if?(s?==?-1)??
  • ????????????????????abort?();??
  • ??
  • ??????????????????event.data.fd?=?infd;??
  • ??????????????????event.events?=?EPOLLIN?|?EPOLLET;??
  • ??????????????????s?=?epoll_ctl?(efd,?EPOLL_CTL_ADD,?infd,?&event);??
  • ??????????????????if?(s?==?-1)??
  • ????????????????????{??
  • ??????????????????????perror?("epoll_ctl");??
  • ??????????????????????abort?();??
  • ????????????????????}??
  • ????????????????}??
  • ??????????????continue;??
  • ????????????}??
  • ??????????else??
  • ????????????{??
  • ??????????????/*?We?have?data?on?the?fd?waiting?to?be?read.?Read?and?
  • ?????????????????display?it.?We?must?read?whatever?data?is?available?
  • ?????????????????completely,?as?we?are?running?in?edge-triggered?mode?
  • ?????????????????and?won't?get?a?notification?again?for?the?same?
  • ?????????????????data.?*/??
  • ??????????????int?done?=?0;??
  • ??
  • ??????????????while?(1)??
  • ????????????????{??
  • ??????????????????ssize_t?count;??
  • ??????????????????char?buf[512];??
  • ??
  • ??????????????????count?=?read?(events[i].data.fd,?buf,?sizeof(buf));??
  • ??????????????????if?(count?==?-1)??
  • ????????????????????{??
  • ??????????????????????/*?If?errno?==?EAGAIN,?that?means?we?have?read?all?
  • ?????????????????????????data.?So?go?back?to?the?main?loop.?*/??
  • ??????????????????????if?(errno?!=?EAGAIN)??
  • ????????????????????????{??
  • ??????????????????????????perror?("read");??
  • ??????????????????????????done?=?1;??
  • ????????????????????????}??
  • ??????????????????????break;??
  • ????????????????????}??
  • ??????????????????else?if?(count?==?0)??
  • ????????????????????{??
  • ??????????????????????/*?End?of?file.?The?remote?has?closed?the?
  • ?????????????????????????connection.?*/??
  • ??????????????????????done?=?1;??
  • ??????????????????????break;??
  • ????????????????????}??
  • ??
  • ??????????????????/*?Write?the?buffer?to?standard?output?*/??
  • ??????????????????s?=?write?(1,?buf,?count);??
  • ??????????????????if?(s?==?-1)??
  • ????????????????????{??
  • ??????????????????????perror?("write");??
  • ??????????????????????abort?();??
  • ????????????????????}??
  • ????????????????}??
  • ??
  • ??????????????if?(done)??
  • ????????????????{??
  • ??????????????????printf?("Closed?connection?on?descriptor?%d\n",??
  • ??????????????????????????events[i].data.fd);??
  • ??
  • ??????????????????/*?Closing?the?descriptor?will?make?epoll?remove?it?
  • ?????????????????????from?the?set?of?descriptors?which?are?monitored.?*/??
  • ??????????????????close?(events[i].data.fd);??
  • ????????????????}??
  • ????????????}??
  • ????????}??
  • ????}??
  • ??
  • ??free?(events);??
  • ??
  • ??close?(sfd);??
  • ??
  • ??return?EXIT_SUCCESS;??
  • }??
  • ?

    ?

    運(yùn)行方式:

    在一個(gè)終端運(yùn)行此程序:epoll.out PORT

    另一個(gè)終端:telnet ?127.0.0.1 PORT

    截圖:

    ?

    ?

    參考資料:

    ?

    http://man7.org/linux/man-pages/man2/epoll_create.2.html

    https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/

    總結(jié)

    以上是生活随笔為你收集整理的读过的最好的epoll讲解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。