最近看了《后台開發核心技術與應用實踐》有關select、poll和epoll部分以及相關的一些博客,學習了這三個函數的使用方法和區別,寫一個易理解的總結。
IO多路復用
之前程序中使用的IO函數都是同步的,無論阻塞式還是非阻塞式,在數據從內核拷貝到用戶空間過程,用戶線程都是被阻塞的。非阻塞IO只是當內核還沒准備好數據時立即返回不等待,需要用戶自己去不斷檢查內核數據是否准備好,依然不高效。IO多路復用提出了新的思路,將IO過程分為等待內核數據准備好和讀取/寫入內核兩部分。一個IO函數監控多個IO可讀/可寫事件,任意1個IO設備准備好時返回(需要代碼中輪詢查看是哪個IO文件描述符,什么事件),再調用對應的read/write函數操作,減少不必要的等待時間,高效了很多。具體的實現有select、poll和epoll三種。
select
基於位圖型集合,通過宏和fd_set結構體設置事件和檢測事件的發生。最早被提出所以可移植性最好,該實現有以下缺點:1.每次調用都需要將fd集合從用戶空間拷貝到內核空間,完成后再從內核空間拷貝回用戶空間,fd很多時開銷很大。2.實現過程是在內核中遍歷所有fd,fd很多時開銷很大。3.支持同時可監控的文件描述符數少,1024或2048。4.fd_set在select返回后會改變,所以再次調用select時需要再次設置fd_set
原型:int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
fd_set set;
FD_ZERO(&set); /* 將set清零*/
FD_SET(fd, &set); 將fd加入set
FD_CLR(fd, &set); 將fd從set中清除
FD_ISSET(fd, &set); 測試fd是否在set中,如果在則為true
maxfdp是描述符最大值加1,指定描述符的范圍
timeout是NULL則無限等待,阻塞模式;timeout等於0則立即返回,非阻塞模式;timeout大於0則為超時時間,timeout內阻塞,到達超時時間不管怎樣一定返回。
返回值:文件無變化返回0,有變化返回正值
poll
要比select高級一些,實現和select大致相同,內核中遍歷所有文件描述符。使用鏈表式集合,不需要重復設置監控事件,同時監控文件描述符數遠大於select。缺點也和select大致相同:1.每次調用都需要將pollfd集合從用戶空間拷貝到內核空間,完成后再從內核空間拷貝回用戶空間。2.實現過程是在內核中遍歷所有pollfd
原型: int poll(struct pollfd* fds, unsigned int nfds, int timeout);
成功時返回revents不為0的文件描述符個數,0表示超時但沒有任何事件發生,-1表示失敗
struct pollfd{
int fd; 文件描述符
short events; 等待的事件,掩碼控制多個事件
short revents; 實際發生的事件,掩碼控制多個事件
}
fds鏈表是要監控文件描述符的pollfd鏈表
nfds指定描述符個數
timeout:0表示立即返回,非阻塞;正值表示等待的毫秒數;負值表示無限等待,阻塞模式
返回值:revents不為0的pollfd數,-1表示出錯
需要頭文件#include <poll.h>
events和revents中的事件:
合法事件:
POLLIN 有數據可讀
POLLRDNORM 有普通數據可讀
POLLRDBAND 有優先數據可讀
POLLPRI 有緊迫數據可讀
POLLOUT 寫數據不會導致阻塞
POLLWRNORM 寫普通數據不會導致阻塞
POLLWRBAND 寫優先數據不會導致阻塞
POLLMSGSIGPOLL 消息可用不會導致阻塞
非法事件:
POLLER 文件描述符發生錯誤
POLLHUP 文件描述符掛起事件
POLLNVAL 文件描述符非法
epoll
通過3個函數來實現,更加高效,當前使用也最多。在epoll_ctl中注冊事件到epoll文件描述符中,把fd全部拷貝進內核,而不是在epoll_wait中重復拷貝。實現中內核通過為每個fd指定一個回調函數,當fd就緒時調用回調函數把就緒fd加入一個就緒鏈表,epoll_wait只需要查看這個就緒鏈表是否有就緒fd就可。可監控文件描述符數是系統可同時打開文件數(超過10萬)
原型:
int epoll_create(int size); //返回epoll文件描述符,size表示要監聽的數目 (這個返回的fd要記得close)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //epoll事件注冊函數
epfd是epoll_create返回的值
op是動作:EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL分別表示:注冊fd到epfd,修改已注冊的fd,從epfd刪除1個fd
fd是要監聽的fd
event是告訴內核要監聽什么事件
struct epoll_event{
__uint32_t events; //epoll events
epoll_data_t data; //user data variable
}
event是宏的集合:EPOLLIN可讀;EPOLLOUT可寫;EPOLLPRI緊急數據可讀;EPOLLERR發生錯誤;EPOLLHUP被掛斷;EPOLLET將EPOLL設置為邊緣觸發模式;EPOLLONESHOT只監聽1次事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //等待事件發生,events是返回的事件鏈表;maxevents是events鏈表元素個數,timeout是等待毫秒數(0表示立即返回,非阻塞;正值表示等待的毫秒數;負值表示無限等待,阻塞模式 ),函數返回值是需要處理的事件數,通過events返回需要處理的事件。(通過events[i].data.fd和events[i].events匹配判斷)
需要#include <sys/epoll.h>
轉載請注明出處
參考:
《后台開發核心技術與應用實踐》
http://www.cnblogs.com/Anker/p/3265058.html