Epoll詳解

epoll是Linux下多路複用IO接口select/poll的增強版本,它能顯著減少程序在大量併發連接中只有少量活躍的情況下的系統CPU利用率。

 

 

一、epoll的優點

支持一個進程打開大數目的socket描述符

IO效率不隨FD數目增加而線性下降

 

二、epoll的使用

epoll有2種工作方式:LT和ET。   

LT(level triggered,水平觸發是缺省的工作方式,並且同時支持block和no-block socket.在這種做法中,

內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任操作,

內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型

的代表。   

 

ET (edge-triggered,邊緣觸發是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未

就緒變爲就緒時,內核通過epoll告訴你。然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文

件描述符發送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再爲就緒狀態了(比如,你在

發送,接收或者接收請求,或者發送接收的數據少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是

請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知

(only once)

 

epoll相關的系統調用有3個:epoll_create, epoll_ctl和epoll_wait。在頭文件<sys/epoll.h>

 

1. int epoll_create(int size);

創建一個epoll句柄,即圖中的epfd, 用來監聽事件, size用來告訴內核這個監聽的數目一共有多大。

這個參數不同於select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄後,它就是會佔用一個fd值,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡

 

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

參數op是操作類型, 使用這個方法完成3種操作: 

EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;

(1) 註冊新事件

C代碼 複製代碼 收藏代碼
  1. struct epoll_event ev;   
  2. ev.data.fd = fd;   
  3. ev.events = EPOLLIN;   
  4. epoll_cntl(epfd, EPOOL_CTL_ADD, fd, &ev);   

 

(2) 修改監聽事件

  

  1. struct epoll_event *a_event = get_a_event()   
  2. struct epoll_event ev;  
  3. ev.data.fd = a_event->data.fd;  
  4. ev.events = a_event->events | EPOLLOUT;  
  5. epoll_cntl(epfd, EPOOL_CTL_MOD, a_event->data.fd, &ev);  
 

 

(3) 刪除事件

 

  1. struct epoll_event *a_event = get_a_event()   
  2. struct epoll_event ev;  
  3. ev.data.fd = a_event->data.fd;  
  4. epoll_cntl(epfd, EPOOL_CTL_DEL, a_event->data.fd, &ev);  
 

 

3種操作都使用了一個ev變量, 這個變量用來關聯fd和它的監聽事件, 是臨時的, 可以反覆使用.

ev是一個struct epoll_event結構體, 結構如下:

 

  1. typedef union epoll_data {  
  2.    void *ptr;  
  3.    int fd;  
  4.    __uint32_t u32;  
  5.    __uint64_t u64;  
  6. } epoll_data_t;  
  7.   
  8. struct epoll_event {  
  9.    __uint32_t events; /* Epoll events */  
  10.    epoll_data_t data; /* User data variable */  
  11. };  

 

events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。

注意多個socket可以設置不同的觸發模式
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏


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

等待事件的產生, 把產生的事件存放到events數組裏, 如圖中所示, 調用epoll_wait後, 

fd 1和 fd 3和fd k產生了事件, 把它們分別存放到events[0], events[1], events[2]
參數epfdepoll_create()函數返回的epoll句柄。

參數eventsstruct epoll_event結構指針,用來從內核得到事件的集合

參數 maxevents內核這個events有多大

參數 timeout: 等待時的超時時間,以毫秒爲單位。

返回值:成功時,返回需要處理的事件數目。調用失敗時,返回0,表示等待超時。

 

 

三 epoll實例 -- 模擬HTTP服務器 

 

  1. #include <sys/socket.h>  
  2. #include <sys/wait.h>  
  3. #include <netinet/in.h>  
  4. #include <netinet/tcp.h>  
  5. #include <sys/epoll.h>  
  6. #include <sys/sendfile.h>  
  7. #include <sys/stat.h>  
  8. #include <unistd.h>  
  9. #include <stdio.h>  
  10. #include <stdlib.h>  
  11. #include <string.h>  
  12. #include <strings.h>  
  13. #include <fcntl.h>  
  14. #include <errno.h>   
  15.   
  16. #define MAX_EVENTS 10  
  17. #define PORT 8080  
  18.   
  19. //設置socket連接爲非阻塞模式  
  20. void setnonblocking(int sockfd) {  
  21.     int opts;  
  22.   
  23.     opts = fcntl(sockfd, F_GETFL);  
  24.     if(opts < 0) {  
  25.         perror("fcntl(F_GETFL)\n");  
  26.         exit(1);  
  27.     }  
  28.     opts = (opts | O_NONBLOCK);  
  29.     if(fcntl(sockfd, F_SETFL, opts) < 0) {  
  30.         perror("fcntl(F_SETFL)\n");  
  31.         exit(1);  
  32.     }  
  33. }  
  34.   
  35. int main(){  
  36.     struct epoll_event ev, events[MAX_EVENTS];  
  37.     int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n;  
  38.     struct sockaddr_in local, remote;  
  39.     char buf[BUFSIZ];  
  40.   
  41.     //創建listen socket  
  42.     if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {  
  43.         perror("sockfd\n");  
  44.         exit(1);  
  45.     }  
  46.     bzero(&local, sizeof(local));  
  47.     local.sin_family = AF_INET;  
  48.     local.sin_addr.s_addr = htonl(INADDR_ANY);;  
  49.     local.sin_port = htons(PORT);  
  50.     if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) {  
  51.         perror("bind\n");  
  52.         exit(1);  
  53.     }  
  54.     listen(listenfd, 20);  
  55.   
  56.     epfd = epoll_create(MAX_EVENTS);  
  57.     if (epfd == -1) {  
  58.         perror("epoll_create");  
  59.         exit(EXIT_FAILURE);  
  60.     }  
  61.   
  62.     ev.events = EPOLLIN;  
  63.     ev.data.fd = listenfd;  
  64.     if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {  
  65.         perror("epoll_ctl: listen_sock");  
  66.         exit(EXIT_FAILURE);  
  67.     }  
  68.   
  69.     for (;;) {  
  70.         nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);  
  71.         if (nfds == -1) {  
  72.             perror("epoll_pwait");  
  73.             exit(EXIT_FAILURE);  
  74.         }  
  75.   
  76.         for (i = 0; i < nfds; ++i) {  
  77.             fd = events[i].data.fd;  
  78.             if (fd == listenfd) {  
  79.                 conn_sock = accept(listenfd,  
  80.                         (struct sockaddr *) &remote, &addrlen);  
  81.                 if (conn_sock == -1) {  
  82.                     perror("accept");  
  83.                     exit(EXIT_FAILURE);  
  84.                 }  
  85.                 setnonblocking(conn_sock);  
  86.                 ev.events = EPOLLIN | EPOLLET;  
  87.                 ev.data.fd = conn_sock;  
  88.                 if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,  
  89.                             &ev) == -1) {  
  90.                     perror("epoll_ctl: add");  
  91.                     exit(EXIT_FAILURE);  
  92.                 }  
  93.                 continue;  
  94.             }    
  95.             if (events[i].events & EPOLLIN) {  
  96.                 n = 0;  
  97.                 while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) {  
  98.                     n += nread;  
  99.                 }  
  100.                 buf[n] = '\0';  
  101.   
  102.                 ev.data.fd = fd;  
  103.                 ev.events = events[i].events | EPOLLOUT;  
  104.                 if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) {  
  105.                     perror("epoll_ctl: mod");  
  106.                 }  
  107.             }  
  108.             if (events[i].events & EPOLLOUT) {  
  109.                 sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11);  
  110.                 n = strlen(buf);  
  111.                 if (write(fd, buf, n) < n) {  
  112.                     perror("write");  
  113.                 }  
  114.                 close(fd);  
  115.             }  
  116.         }  
  117.     }  
  118.   
  119.     return 0;  
  120. }  

最後說明個errno的EINTR 和EWOULDBLOCK

他們其實是一個錯誤類型,對應的整數值都是11
EWOULDBLOCK用於非阻塞模式,不需要重新讀或者寫

EINTR指操作被中斷喚醒,需要重新讀/寫

在Linux環境下開發經常會碰到很多錯誤(設置errno),其中EAGAIN是其中比較常見的一個錯誤(比如用在非阻塞操作中)。
從字面上來看,是提示再試一次。這個錯誤經常出現在當應用程序進行一些非阻塞(non-blocking)操作(對文件或socket)的時候。例如,以 O_NONBLOCK的標誌打開文件/socket/FIFO,如果你連續做read操作而沒有數據可讀。此時程序不會阻塞起來等待數據準備就緒返 回,read函數會返回一個錯誤EAGAIN,提示你的應用程序現在沒有數據可讀請稍後再試。
又例如,當一個系統調用(比如fork)因爲沒有足夠的資源(比如虛擬內存)而執行失敗,返回EAGAIN提示其再調用一次(也許下次就能成功)。
Linux - 非阻塞socket編程處理EAGAIN錯誤
在linux進行非阻塞的socket接收數據時經常出現Resource temporarily unavailable,errno代碼爲11(EAGAIN),這是什麼意思?
這表明你在非阻塞模式下調用了阻塞操作,在該操作沒有完成就返回這個錯誤,這個錯誤不會破壞socket的同步,不用管它,下次循環接着recv就可以。 對非阻塞socket而言,EAGAIN不是一種錯誤。在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。
另外,如果出現EINTR即errno爲4,錯誤描述Interrupted system call,操作也應該繼續。
最後,如果recv的返回值爲0,那表明連接已經斷開,我們的接收操作也應該結束。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章