在前面的兩篇部落格中,我們介紹了最早期的select和改進版的poll;但是,他兩都沒有改進的就是,想要快速知道事件就緒並沒有得到改進,兩個全部是遍歷陣列,我們都知道它的時間複雜度就是o(n);效率不是很高,時間複雜度達到o(1)才是高效的;
epoll是linux特有的i/o復用函式,它在實現和使用上與select、poll有很大差異,首先,epoll使用一組函式來完成任務,而不是單個函式。其次,epoll把使用者關心的檔案描述符上的事件放在核心裡面的乙個事件表中,從而無需向select和poll那樣每次都要重複的傳入檔案描述符集或事件集。
其實,簡單的來理解epoll伺服器,它的底層就是由一顆紅黑樹和乙個佇列組成的,當呼叫epoll_create函式時就是建立了乙個空的紅黑樹和空的佇列,當呼叫epoll_ctl函式的時候,就是在紅黑樹上面進行節點操作,當呼叫epoll_wait函式時就是把紅黑樹中的指定節點放到佇列中去,這樣就可以利用佇列先進先出的特點迅速的找到就緒節點,相比較二巷比陣列遍歷一次要節省不少時間;
1、epoll_create()
#include
int epoll_create(int size);
//size引數現在並不起什麼作用,只是給核心乙個提示,告訴它事件表需要多大,該函式返回的檔案描述符將作用其他所有epoll系統呼叫的第乙個引數,已指定要訪問的核心事件表
2、epoll_ctl()int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
fd:是要操作的檔案描述符;
op:指定操作型別
struct epoll_event
data成員用於儲存使用者資料,其型別:
typedef
union epoll_data
epoll_data_t;
3、epoll_wait()int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
從後往前討論引數:
timeout:超時設定;
maxevents:最多監聽多少個事件,必須大於0;(類似於陣列的最大值)
epoll_wait函式如果檢測到了事件,就將所有就緒事件ongoing核心事件表中賦值到它的第二個引數events指向的陣列中。這個陣列直營與出輸出epoll_wait檢測到的就緒事件。
epoll對檔案描述符的操作有兩種方式lt(水平觸發)和et(邊緣觸發),lt是預設的工作模式,這種模式epoll相當於乙個高效的poll,當往epoll核心事件表中註冊乙個檔案描述符上的epollet事件時,epoll將以et模式來操作該檔案描述符,et模式是epoll的高效工作模式。
對於採用lt工作模式的檔案描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程式後,應用程式可以不利己處理該事件,這樣,就當程式次啊一次呼叫epoll_wait時,epoll_wait還會再次向應用程式通知此事件,知道該事件被處理。而對於採用et工作模式的檔案描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程式後,應用程式必須立即處理該事件,因為後續的epoll_wait呼叫不在向應用程式通知這一事件。可見,et模式在很大程度上降低了同乙個epoll事件被重複觸發的次數,因此次奧綠要比lt模式高
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define max_ready_events 64
//將檔案描述符設定為非阻塞的
int setnonblocking(int fd)
//將檔案描述符fd上的epollin註冊到epollfd指示的epoll核心事件表中,引數enable_et指定是否對fd啟用et模式
void addfd(int epollfd, int fd, int enable_et)
epoll_ctl(epollfd, epoll_ctl_add, fd, &event);
setnonblocking(fd);
}static
void usage(const
char* proc)
int startup(const
char* _ip, int _port)
int opt = 1;
setsockopt(sock, sol_socket, so_reuseaddr, &opt, sizeof(opt));
struct sockaddr_in local;
local.sin_family = af_inet;
local.sin_port = htons(_port);
local.sin_addr.s_addr = inet_addr(_ip);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
if(listen(sock, 10) < 0)
return sock;
}int main(int argc, char* argv)
int listen_sock = startup(argv[1], atoi(argv[2]));
int epfd = epoll_create(256);
if(epfd < 0)
struct epoll_event ev;
ev.events = epollin;
ev.data.fd = listen_sock;
if(epoll_ctl(epfd, epoll_ctl_add, listen_sock, &ev) < 0)
int timeout = 1000;
int nums = -1;
struct epoll_event revs[max_ready_events];
while(1)
break;
case -1:
break;
default:
printf("get new client: [%s:%d]\n",inet_ntoa(client.sin_addr), ntohs(client.sin_port));
struct epoll_event ev1;
ev1.events = epollin;
ev1.data.fd = new_sock;
if(epoll_ctl(epfd, epoll_ctl_add, new_sock, &ev1) < 0)
addfd(epfd, new_sock, 1);//開啟et模式
}// listen_sock can read
else
if((fd != listen_sock) && (revs[i].events & epollin))
close(fd);
break;
}else
if(s == 0)
else
}}//other events can read
else
if((fd != listen_sock) && (revs[i].events & epollout))
//events can write
}//for
}//default
break;}}
return
0;}
epoll在呼叫epoll_create時作業系統會建立一顆紅黑樹存放socket和乙個佇列存放就緒事件
三種模式伺服器的比較
網路程式設計 多路I O轉接伺服器之epoll
相關api 建立乙個epoll控制代碼,引數size用來告訴核心監聽的檔案描述符的個數,跟記憶體大小有關。此步驟建立乙個建議大小為size的紅黑樹 二叉平衡樹 返回值為根結點的fd include intepoll create int size size 監聽數目控制某個epoll監控的檔案描述符...
IO多路轉接 epoll高階
例項三 基於網路c s非阻塞模型的epoll et觸發模式 epoll事件有兩種模型 et模式即edge triggered工作模式。如果我們在第1步將rfd新增到epoll描述符的時候使用了epollet標誌,那麼在第5步調用epoll wait之後將有可能會掛起,因為剩餘的資料還存在於檔案的輸入...
多路IO轉接伺服器實現方法三 epoll 函式
epoll是linux下多路復用介面select poll的增強版本。它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統cpu利用率,因為它會復用檔案描述符集合來傳遞結果而不用每次等待時間之前都必須重新準備要被監聽的檔案描述符集合 獲取事件的時候,它無須遍歷整個被監聽的檔案描述符集,只要遍歷...