亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 2379 | 回復: 1
打印 上一主題 下一主題

[學習分享] linux(epoll詳解) [復制鏈接]

論壇徽章:
0
跳轉到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2018-11-05 16:47 |只看該作者 |倒序瀏覽
什么是epoll

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

?

epoll的相關系統(tǒng)調用

epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統(tǒng)調用。

?

1. int epoll_create(int size);

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

?

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

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

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

第二個參數(shù)表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的fd到epfd中;

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

EPOLL_CTL_DEL:從epfd中刪除一個fd;

?

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

第四個參數(shù)是告訴內核需要監(jiān)聽什么事,struct epoll_event結構如下:


//保存觸發(fā)事件的某個文件描述符相關的數(shù)據(jù)(與具體使用方式有關)

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可以是以下幾個宏的集合:

EPOLLIN?:表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);

EPOLLOUT:表示對應的文件描述符可以寫;

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

EPOLLERR:表示對應的文件描述符發(fā)生錯誤;

EPOLLHUP:表示對應的文件描述符被掛斷;

EPOLLET:?將EPOLL設為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。

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


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

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

?

epoll工作原理

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

?

另一個本質的改進在于epoll采用基于事件的就緒通知方式。在select/poll中,進程只有在調用一定的方法后,內核才對所有監(jiān)視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。

?

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


假如有這樣一個例子:

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

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

3. 調用epoll_wait(2),并且它會返回RFD,說明它已經(jīng)準備好讀取操作

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

5. 調用epoll_wait(2)......



Edge Triggered 工作模式:

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

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

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



Level Triggered 工作模式

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



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

?

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

因此,LT模式下開發(fā)基于epoll的應用要簡單些,不太容易出錯。而在ET模式下事件發(fā)生時,如果沒有徹底地將緩沖區(qū)數(shù)據(jù)處理完,則會導致緩沖區(qū)中的用戶請求得不到響應。

圖示說明:


Nginx默認采用ET模式來使用epoll。

?

epoll的優(yōu)點:

1.支持一個進程打開大數(shù)目的socket描述符(FD)

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

?

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

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

?

3.使用mmap加速內核與用戶空間的消息傳遞

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

?

4.內核微調

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

?

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

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

?

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

?

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

?

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

?

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

?

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

?當一個進程調用epoll_creaqte方法時,Linux內核會創(chuàng)建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關:

/*
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;//紅黑樹根節(jié)點,這棵樹存儲著所有添加到epoll中的事件,也就是這個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;//雙向鏈表中保存著將要通過epoll_wait返回給用戶的、滿足條件的事件
217};

每一個epoll對象都有一個獨立的eventpoll結構體,這個結構體會在內核空間中創(chuàng)造獨立的內存,用于存儲使用epoll_ctl方法向epoll對象中添加進來的事件。這樣,重復的事件就可以通過紅黑樹而高效的識別出來。

在epoll中,對于每一個事件都會建立一個epitem結構體:

/*
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還維護了一個雙鏈表,用戶存儲發(fā)生的事件。當epoll_wait調用時,僅僅觀察這個list鏈表里有沒有數(shù)據(jù)即eptime項即可。有數(shù)據(jù)就返回,沒有數(shù)據(jù)就sleep,等到timeout時間到后即使鏈表沒數(shù)據(jù)也返回。所以,epoll_wait非常高效。

?

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

?

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

?

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

?

epoll的使用方法

那么究竟如何來使用epoll呢?其實非常簡單。

?

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

?

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

?

之后在你的網(wǎng)絡主循環(huán)里面,每一幀的調用epoll_wait(int epfd, epoll_event events, int max events, int timeout)來查詢所有的網(wǎng)絡接口,看哪一個可以讀,哪一個可以寫了;镜恼Z法為:

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

?

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

?

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

?

?

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

for( ; ; )
    {
        nfds = epoll_wait(epfd,events,20,500);
        for(i=0;i<nfds;++i)
        {
            if(events.data.fd==listenfd) //有新的連接
            {
                connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連接
                ev.data.fd=connfd;
                ev.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監(jiān)聽隊列中
            }

            else if( events.events&EPOLLIN ) //接收到數(shù)據(jù),讀socket
            {
                n = read(sockfd, line, MAXLINE)) < 0    //讀
                ev.data.ptr = md;     //md為自定義類型,添加數(shù)據(jù)
                ev.events=EPOLLOUT|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環(huán)時發(fā)送數(shù)據(jù),異步處理的精髓
            }
            else if(events.events&EPOLLOUT) //有數(shù)據(jù)待發(fā)送,寫socket
            {
                struct myepoll_data* md = (myepoll_data*)events.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); //修改標識符,等待下一個循環(huán)時接收數(shù)據(jù)
            }
            else
            {
                //其他的處理
            }
        }
    }

epoll的程序實例


#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)建和綁定一個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ù)
//功能:設置socket為非阻塞的
static int
make_socket_non_blocking (int sfd)
{
  int flags, s;

  //得到文件狀態(tài)標志
  flags = fcntl (sfd, F_GETFL, 0);
  if (flags == -1)
    {
      perror ("fcntl");
      return -1;
    }

  //設置文件狀態(tài)標志
  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.events & EPOLLERR) ||
              (events.events & EPOLLHUP) ||
              (!(events.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.data.fd);
              continue;
            }

          else if (sfd == events.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;
                        }
                    }

                                  //將地址轉化為主機名或者服務名
                  s = getnameinfo (&in_addr, in_len,
                                   hbuf, sizeof hbuf,
                                   sbuf, sizeof sbuf,
                                   NI_NUMERICHOST | NI_NUMERICSERV);//flag參數(shù):以數(shù)字名返回
                                  //主機地址和服務地址

                  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.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.data.fd);

                  /* Closing the descriptor will make epoll remove it
                     from the set of descriptors which are monitored. */
                  close (events.data.fd);
                }
            }
        }
    }

  free (events);

  close (sfd);

  return EXIT_SUCCESS;
}

運行方式:

在一個終端運行此程序:epoll.out PORT

另一個終端:telnet ?127.0.0.1 PORT

截圖:




參考學習資料:http://www.makeru.com.cn/course/details/2478/?s=60220Q群556042177

論壇徽章:
0
2 [報告]
發(fā)表于 2018-11-08 14:29 |只看該作者
66666666666666666
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復

  

北京盛拓優(yōu)訊信息技術有限公司. 版權所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關心和支持過ChinaUnix的朋友們 轉載本站內容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP