I/O多路複用之epoll

在上一章,咱們對select進行了大體的描述,知道了它相對傳統的阻塞式服務提升了併發度,可是它也因爲輪詢而致使效率底下。本文對epoll進行講解,相比select它的併發度更高,現代高負載服務器不少都採用這種模型。
在講解epoll的具體用法以前,咱們先看看採用 epoll模型主要用到的三個函數以及一個數據結構
epoll中三個主要的函數:
(1)int epoll_create(int size);
功能 :生成一個epoll專用的文件描述符。
參數 :size:在該epoll fd上關注的最大socket fd數。
返回值:生成的文件描述符。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能 :控制某個epoll文件描述符上的事件,能夠註冊事件,修改事件,刪除事件。
參數 :epfd :由 epoll_create 生成的epoll專用的文件描述符;
op :EPOLL_CTL_ADD 註冊、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 刪除;
fd :關聯的文件描述符;
event:指向epoll_event的指針;
返回值:0:成功;
-1:失敗;
(3)int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
功能 :輪詢I/O事件的發生。
參數 :epfd :由 epoll_create 生成的epoll專用的文件描述符;
events :用於回傳待處理事件的數組;
maxevents:每次能處理的事件數;
timeout :等待I/O事件發生的超時值;-1至關於阻塞,0至關於非阻塞;
返回值:>=0 :返回發生事件數;
-1 :錯誤;

epoll中的主要數據結構:
01 typedef union epoll_data {
02 void *ptr;
03 int fd;
04 __uint32_t u32;
05 __uint64_t u64;
06 } epoll_data_t;
07
08 struct epoll_event {
09 __uint32_t events; /* Epoll events */
10 epoll_data_t data; /* User data variable */
11 };

其中,events的類型有:
EPOLLIN :文件描述符能夠讀;
EPOLLOUT:文件描述符能夠寫;
EPOLLPRI:文件描述符有緊急的數據可讀;
EPOLLERR:文件描述符發生錯誤;
EPOLLHUP:文件描述符被掛斷;
EPOLLET :文件描述符有事件發生;
epoll的使用仍是很簡單的,請看下面一個簡單的採用epoll提供併發服務的服務端程序(注:爲了簡潔,都沒有進行錯誤處理,實際使用時,必定要記住進行錯誤處理。):
01 #include <errno.h>
02 #include <string.h>
03 #include <sys/types.h>
04 #include <netinet/in.h>
05 #include <sys/socket.h>
06 #include <sys/wait.h>
07 #include <unistd.h>
08 #include <arpa/inet.h>
09 #include <sys/epoll.h>
10 #include <sys/time.h>
11
12 #define MAXBUF 1024
13 #define MAX_EPOLL_SIZE 10000
14 #define SERVICE_PORT 8888
15
16
17 int main(int argc, char **argv)
18 {
19 int server_fd, new_fd;
20 struct sockaddr_in server_addr, client_addr;
21
22 struct epoll_event ev;
23 struct epoll_event events[MAX_EPOLL_SIZE];
24
25 socklen_t len = sizeof(struct sockaddr_in);
26 server_fd = socket(AF_INET, SOCK_STREAM, 0);
27
28 bzero(&server_addr, sizeof(server_addr));
29 server_addr.sin_family = AF_INET;
30 server_addr.sin_port = htons(SERVICE_PORT);
31 server_addr.sin_addr.s_addr = INADDR_ANY;
32
33 bind(server_fd, (struct sockaddr *) &server_addr, sizeof(struct sockaddr));
34 listen(server_fd, 1000);
35
36 //create epoll fd, and register the server listening fd
37 int epoll_fd = epoll_create(MAX_EPOLL_SIZE);
38 ev.events = EPOLLIN | EPOLLET;
39 ev.data.fd = server_fd;
40 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);
41
42 int active_fd_count = 1;
43 while (1)
44 {
45 //wait for some events to happen
46 int event_active_fd_count = epoll_wait(epoll_fd, events, active_fd_count, -1);
47
48 // process all events
49 for (int i = 0; i < event_active_fd_count; ++i)
50 {
51 if (events[i].data.fd == server_fd)
52 {
53 new_fd = accept(server_fd, (struct sockaddr *) &client_addr,&len);
54
55 //register new fd to epoll
56 ev.events = EPOLLIN | EPOLLET;
57 ev.data.fd = new_fd;
58 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd, &ev);
59 active_fd_count++;
60 }
61 else
62 {
63 handle message on events[i].data.fd
64 if (client close the connection)
65 {
66 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd,&ev);
67 active_fd_count--;
68 }
69 }
70 }
71 }
72 close(server_fd);
73 return 0;
74 }

講完epoll的常規使用方法,這裏須要注意的是epoll有兩種工做方式:
(1)ET:Edge Triggered,邊緣觸發。僅當狀態發生變化時纔會通知,須要細緻的處理每一個請求,不然容易發生丟失事件的狀況。只支持非阻塞的socket。
(2)LT:Level Triggered,水平觸發(默認工做方式)。只要還有沒有處理的事件就會一直通知,所以不用擔憂事件丟失的狀況。效率會低於ET觸發,尤爲在大併發,大流量的狀況下。支持阻塞和非阻塞的socket。

最後講講 爲何epoll會比select高效,主要從三方面來進行論述。
(1)elect對描述符狀態的改變是經過輪詢來進行查找的;而epoll是當描述符狀態發生改變時主動進行通知內核,這就是所謂的Reactor事件處理機制。能夠用「好萊塢原則」進行描述:不要打電話給咱們,咱們會打電話通知你。相比之下,select的機制就比如面試結束後不停給面試官打電話詢問面試結果。效率孰高孰低,可見一 斑。
(2)select的文件描述符是使用鏈表進行組織的;而epoll是使用紅黑樹這一高效數據結構組織的。
(3)select從內核到用戶空間傳遞文件描述符上發送的信息是使用內存複製的方式進行的;而epoll是採用共享內存的方式。
相關文章
相關標籤/搜索