select和epoll都用於監聽套接口描述字上是否有事件發生,實現I/O復用
select(輪詢)
#include <sys/select.h> #include <sys/time.h> int select (int maxfdpl, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout)
調用時輪詢一次所有描述字,超時時再輪詢一次。如果沒有描述字准備好,則返回0;中途錯誤返回-1;有描述字准備好,則將其對應位置為1,其他描述字置為0,返回准備好的描述字個數。
fd_set:整數數組,每個數中的每一位對應一個描述字,其具體大小有內核的FD_SETSIZE(1024)決定。
void FD_ZERO(fd_set* fdset);//clear all bits in fdset void FD_SET(int fd, fd_set* fdset);//turn on the bit for fd in fdset void FD_CLR(int fd, fd_set* fdset);//turn off the bit for fd in fdset int FD_ISSET(int fd, fd_set* fdset);//is the bit for fd on in fdset
epoll(觸發)
epoll對監聽的每個fd都會有回調函數,當該fd上發生事件時,會調用對應的回調函數。
系列函數:
創建函數:
創建一個epoll句柄,size-監聽套接字的數。當創建好epoll句柄後,會占用一個fd值,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。
#include <sys/epoll.h> int epoll_create(int size);
事件注冊函數:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
第一個參數是epoll_create()的返回值;
第二個參數表示動作,用三個宏來表示:
EPOLL_CTL_ADD:注冊新的fd到epfd中,
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件,
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是需要監聽的fd;
第四個參數是告訴內核監聽事件,struct epoll_event結構如下:
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裡應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裡
使用如下:
struct epoll_event ev; ev.data.fd=listenfd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
等待函數
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,參數events用來從內核得到事件集合。maxevents告之內核這個events有多大,這個maxevents的值不能大於創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
注意:某fd上發生事件後,其事件類型會被清空,所以如果下一個循環還要關注這個socket fd的話,則需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)來重新設置socket fd的事件類型。這時不用EPOLL_CTL_ADD,因為socket fd並未清空,只是事件類型清空。這一步非常重要。
實例
struct epoll_event ev, *events; for(;;) { nfds = epoll_wait(kdpfd, events, maxevents, -1); for(n = 0; n < nfds; ++n) { if(events[n].data.fd == listener) { client = accept(listener, (struct sockaddr *) &local, &addrlen); if(client < 0){ perror("accept"); continue; } setnonblocking(client); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d\n", client); return -1; } } else { do_use_fd(events[n].data.fd); } }
對比:
select - 如果同時建立很多連接,但只有少數事件發生,這種輪詢會造成效率很低;頻繁從內核拷貝、復制描述字;監聽描述字受限於內核的FD_SETSIZE;
epoll - 這種回調觸發式操作會保證效率;不需要頻繁的拷貝;監聽描述字沒有限止,只與系統資源有關;epoll返回時已經明確的知道哪個sokcet fd發生了事件,不用再一個個比對。
查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/