select和epoll

select:

1,select有數量限制。因爲一個進程所打開的FD(文件描述符)是有限制的,所以該函數調用可以操作的文件描述符有最大數量的限制,由FD_SETSIZE決定,內核默認的最大值是:32*32=1024

2,效率低。函數調用過後,需要輪詢所有的文件描述符來判斷哪個文件描述符可以讀寫。如果文件描述符的總量很大,而可以讀寫的文件描述符很少,那麼效率是很低的。

3. 內核/用戶空間 內存拷貝問題,如何讓內核把 FD 消息通知給用戶空間呢?在這個問題上 select 採取了內存拷貝方法。

poll是對select的改進,但是隻改進了第一個問題;後面兩個缺點一樣存在。

epoll:

1,數量不受限,可以認爲定義數量。

2,效率高。不採用輪詢方式,而只對可以讀寫的文件描述符進行操作。


epoll的使用方式如下:

epoll只有epoll_create,epoll_ctl,epoll_wait 3個系統調用。

1. int epoll_create(int size);
功能   :生成一個epoll專用的文件描述符(或句柄)。
參數   :size: 在該epoll fd上關注的最大socket fd數。 自從linux2.6.8之後,size參數是被忽略的。
返回值:生成的文件描述符。
需要注意:當創建好epoll句柄後,它就是會佔用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件註冊函數,它不同於select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
功能  :控制某個epoll文件描述符上的事件,可以註冊事件,修改事件,刪除事件。
參數  :
     參數epfd  :由epoll_create生成的epoll專用的文件描述符;
     參數op    :用三個宏來表示:
               EPOLL_CTL_ADD:註冊新的fd到epfd中;
               EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
               EPOLL_CTL_DEL:從epfd中刪除一個fd;
     參數fd    :關聯的文件描述符;
     參數event :指向epoll_event的指針;
返回值: 0:成功; 
          -1:失敗;
  struct epoll_event(第四個參數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隊列裏
3. int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout);
功能  :輪詢I/O事件的發生。
參數  :    epfd        :由 epoll_create 生成的epoll專用的文件描述符,即epoll_create的返回值;
            events      :用於回傳待處理事件的數組;參數events 收集在epoll監控的事件中已經發生的事件。
                             參數events是分配好的epoll_event結構體數組,epoll將會把發生的事件賦值到events數組
                             中(events不可以是空指針,內核只負責把數據複製到這個events數組中,不會去幫助我們
                             在用戶態中分配內存)。
            maxevents :每次能處理的事件數; 這個 maxevents的值不能大於創建epoll_create()時的size.
            timeout     :等待I/O事件發生的超時值;-1相當於阻塞,0相當於非阻塞;
返回值:      >=0     :返回發生事件數; 
                -1       :錯誤;
                     如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時。
4. int epoll_pwait(與函數epoll_wait類似)
int epoll_pwait(int epfd, struct epoll_event *events,
                   int maxevents, int timeout,
                   const sigset_t *sigmask); 
與epoll_wait的區別是可以通過最後一個參數設置阻塞過程中信號屏蔽字。
上面的函數原型等價於:
sigset_toriginmask;
sigpromask(SIG_SETMASK,&sigmask,& originmask);
ready = epoll_wait(epfd,&events,maxevents,timeout);
sigpromask(SIG_SETMASK,&  originmask ,NULL);


示例如下:

服務器端代碼:

#include <sys/epoll.h>  
#include <netinet/in.h>  
#include <sys/types.h>          /* See NOTES */  
#include <sys/socket.h>  
#include <string.h>  
#include <stdio.h>  
#include <unistd.h>  
#include <fcntl.h>  
  
#include <errno.h>  
#include <stdlib.h>  
typedef struct sockaddr_in sockaddr_in ;  
typedef struct sockaddr     sockaddr ;  
  
#define SER_PORT    8080  
  
int nonblock(int fd){  
    int opt ;  
    opt = fcntl(fd,F_GETFL);  
    opt |= O_NONBLOCK ;  
    return fcntl(fd,F_SETFL,opt);  
}  
  
int main(int argc,char**argv){  
    sockaddr_in srv, cli ;  
    int listen_fd ,con_fd ;  
    socklen_t  len;  
    int res ,nsize,ws;  
    char buf[255];  
  
    int epfd,ers;  
    struct epoll_event evn,events[50];  
    int i;  
  
    bzero(&srv,sizeof(srv));  
    bzero(&cli,sizeof(cli));  
    srv.sin_port= SER_PORT ;  
    srv.sin_family = AF_INET ;  
    listen_fd = socket(AF_INET,SOCK_STREAM,0);  
  
    int yes = 1;  
    setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));  
  
    if(bind(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0)  {  
        perror("bind");  
        exit(0);  
    }  
    listen(listen_fd,100);  
    nonblock(listen_fd);  
    epfd = epoll_create(200);  
    evn.events = EPOLLIN|EPOLLET ;  
    evn.data.fd = listen_fd;   
    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&evn);  
    static int count ;  
    while(1){  
        ers = epoll_wait(epfd,events,100,5000);  
        if(ers<0 ){  
            perror("epoll_wait:");exit(0);  
        }else if(ers==0){  
            printf("time out:%d\n",count++);  
            continue ;  
        }  
        for(i=0;i<ers;i++){  
            if(events[i].data.fd == listen_fd){  
                con_fd = accept(listen_fd,(sockaddr*)&cli ,&len);  
                nonblock(con_fd);  
                printf("connect from:%s\n",inet_ntoa(cli.sin_addr));  
                evn.data.fd = con_fd;  
                evn.events = EPOLLIN | EPOLLET ;  
                epoll_ctl(epfd,EPOLL_CTL_ADD,con_fd,&evn);  
  
            }else if(events[i].events & EPOLLIN){     
                  
                nsize = 0;  
                while((res=read(events[i].data.fd,buf+nsize,sizeof(buf)-1))>0){  
                    nsize+= res;  
                }  
                if(res==0){  
                    epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);     
                    printf("a client over\n");  
                    close(con_fd);  
                    continue ;  
                }else if(res<0 && errno!=EAGAIN){  
                    perror("read");  
                    continue ;  
                }  
                buf[nsize]=0;  
                evn.data.fd = events[i].data.fd;  
                evn.events=EPOLLOUT|EPOLLET ;  
                epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);                 
                  
            }else if(events[i].events & EPOLLOUT){  
                nsize = strlen(buf);  
                ws = 0;  
                while(nsize>0){  
                     ws=write(events[i].data.fd,buf,nsize);  
                    nsize-=ws;  
                }  
                evn.data.fd = events[i].data.fd;  
                evn.events=EPOLLIN|EPOLLET ;  
                epoll_ctl(epfd,EPOLL_CTL_MOD,events[i].data.fd,&evn);     
            }else{  
                printf("others\n");  
                  
            }             
        }  
  
    }  
  
    close(listen_fd);  
      
    return 0;  
}  

客戶端代碼:

#include <sys/epoll.h>  
#include <netinet/in.h>  
#include <sys/types.h>          /* See NOTES */  
#include <sys/socket.h>  
#include <strings.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
typedef struct sockaddr_in sockaddr_in ;  
typedef struct sockaddr     sockaddr ;  
  
#define SER_PORT    8080  
#define IP_ADDR     "10.33.28.230"  
  
int main(int argc,char**argv){  
    sockaddr_in srv, cli ;  
    int listen_fd ,con_fd ;  
    socklen_t  len;  
    int res,ws ;  
    char buf[255];  
  
    bzero(&srv,sizeof(srv));  
    bzero(&cli,sizeof(cli));  
    srv.sin_port= SER_PORT ;  
    srv.sin_family = AF_INET ;  
    inet_pton(AF_INET,IP_ADDR,&srv.sin_addr);  
  
    listen_fd = socket(AF_INET,SOCK_STREAM,0);  
    if(connect(listen_fd,(sockaddr*)&srv,sizeof(sockaddr))<0){  
        perror("connect");  
        exit(0);  
    }  
    while(1){  
        res = read(STDIN_FILENO,buf,sizeof(buf)-1);  
        ws = write(listen_fd,buf,res);  
        res = read(listen_fd,buf,sizeof(buf)-1);  
        ws = write(STDOUT_FILENO,buf,res);  
    }  
  
    close(listen_fd);  
      
    return 0;  
} 


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