歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> Unix基礎知識

UNIX環境高級編程:select和epoll的區別

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/

Copyright © Linux教程網 All Rights Reserved