select 和 epoll 多路複用IO

先說下本文框架,先是問題引出,而後歸納兩個機制的區別和聯繫,最後介紹每一個接口的用法linux

1、問題引出 聯繫區別編程

問題的引出,當須要讀兩個以上的I/O的時候,若是使用阻塞式的I/O,那麼可能長時間的阻塞在一個描述符上面,另外的描述符雖然有數據可是不能讀出來,這樣實時性不能知足要求,大概的解決方案有如下幾種:數組

1.使用多進程或者多線程,可是這種方法會形成程序的複雜,並且對與進程與線程的建立維護也須要不少的開銷。(Apache服務器是用的子進程的方式,優勢能夠隔離用戶)服務器

2.用一個進程,可是使用非阻塞的I/O讀取數據,當一個I/O不可讀的時候馬上返回,檢查下一個是否可讀,這種形式的循環爲輪詢(polling),這種方法比較浪費CPU時間,由於大多數時間是不可讀,可是仍花費時間不斷反覆執行read系統調用。數據結構

3.異步I/O(asynchronous I/O),當一個描述符準備好的時候用一個信號告訴進程,可是因爲信號個數有限,多個描述符時不適用。多線程

4.一種較好的方式爲I/O多路轉接(I/O multiplexing)(貌似也翻譯多路複用),先構造一張有關描述符的列表(epoll中爲隊列),而後調用一個函數,直到這些描述符中的一個準備好時才返回,返回時告訴進程哪些I/O就緒。select和epoll這兩個機制都是多路I/O機制的解決方案,select爲POSIX標準中的,而 epoll爲Linux所特有的。框架

區別(epoll相對select優勢)主要有三:異步

1.select的句柄數目受限,在linux/posix_types.h頭文件有這樣的聲明:#define __FD_SETSIZE    1024  表示select最多同時監聽1024個fd。而epoll沒有,它的限制是最大的打開文件句柄數目。socket

2.epoll的最大好處是不會隨着FD的數目增加而下降效率,在selec中採用輪詢處理,其中的數據結構相似一個數組的數據結構,而epoll 是維護一個隊列,直接看隊列是否是空就能夠了。epoll只會對"活躍"的socket進行操做---這是由於在內核實現中epoll是根據每一個fd上面的callback函數實現的。那麼,只有"活躍"的socket纔會主動的去調用 callback函數(把這個句柄加入隊列),其餘idle狀態句柄則不會,在這點上,epoll實現了一個"僞"AIO。可是若是絕大部分的I/O都是 「活躍的」,每一個I/O端口使用率很高的話,epoll效率不必定比select高(多是要維護隊列複雜)。async

3.使用mmap加速內核與用戶空間的消息傳遞。不管是select,poll仍是epoll都須要內核把FD消息通知給用戶空間,如何避免沒必要要的內存拷貝就很重要,在這點上,epoll是經過內核於用戶空間mmap同一塊內存實現的。

2、接口

1)select

1. int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);

struct timeval{

  long tv_sec;

  long tv_usec;

}

有三種狀況:tvptr == NULL 永遠等待;tvptr->tv_sec == 0 && tvptr->tv_usec == 0 徹底不等待;不等於0的時候爲等待的時間。select的三個指針均可覺得空,這時候select提供了一種比sleep更精確的定時器。注意 select的第一個參數maxfdp1並非描述符的個數,而是最大的描述符加1,一是起限制做用,防止出錯,二來能夠給內核輪詢的時候提供一個上屆,提升效率。select返回-1表示出錯,0表示超時,返回正值是全部的已經準備好的描述符個數(同一個描述符若是讀和寫都準備好,對結果影響是+2)。

2.int FD_ISSET(int fd, fd_set *fdset);  fd在描述符集合中非0,不然返回0

3.int FD_CLR(int fd, fd_set *fd_set); int FD_SET(int fd, fd_set *fdset) ;int FD_ZERO(fd_set *fdset);

用一段linux 中man裏的話「FD_ZERO()  clears  a set.FD_SET() and  FD_CLR() respectively add and remove a given file descriptor from a set.  FD_ISSET() tests to see if a file descriptor is part of the set; this is useful after select() returns.」這幾個函數與描述符的0和1不要緊,只是添加刪除檢測描述符是否在set中。

2)epoll

1.int epoll_create(int size);
建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不一樣於select()中的第一個參數,給出最大監聽的fd+1的值。須要注意的是,當建立好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_create()的返回值,第二個參數表示動做,用三個宏來表示:
EPOLL_CTL_ADD:註冊新的fd到epfd中;
EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個參數是須要監聽的fd,第四個參數是告訴內核須要監聽什麼事,struct epoll_event結構以下:
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隊列裏

關於epoll工做模式ET,LT

LT(level triggered)是缺省的工做方式,而且同時支持block和no-block socket.在這種作法中,內核告訴你一個文件描述符是否就緒了,而後你能夠對這個就緒的fd進行IO操做。若是你不做任何操做,內核仍是會繼續通知你的,因此,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的表明.
ET (edge-triggered)是高速工做方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核經過epoll告訴你。而後它會假設你知道文件描述符已經就緒,而且不會再爲那個文件描述符發送更多的就緒通知,直到你作了某些操做致使那個文件描述符再也不爲就緒狀態了,可是請注意,若是一直不對這個fd做IO操做(從而致使它再次變成未就緒),內核不會發送更多的通知(only once)

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
等待事件的產生,相似於select()調用。參數events用來從內核獲得事件的集合,maxevents告以內核這個events有多大,這個 maxevents的值不能大於建立epoll_create()時的size,參數timeout是超時時間(毫秒,0會當即返回,-1永久阻塞)。該函數返回須要處理的事件數目,如返回0表示已超時。

 

----------------------------------------------------以上轉自 http://blog.csdn.net/ysu108/article/details/7570571--------------------------------------------------------------------------

 

下面是我寫的一個例子: 沒有關心出錯的狀況

客戶端  client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXBUF 1024

int main(int argc,char *argv[])
{
    int socketfd,newfd;
    char buf[MAXBUF+1];
    struct sockaddr_in my_addr,client_addr;
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&my_addr,0);

    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(atoi(argv[1]));
    my_addr.sin_addr.s_addr = INADDR_ANY;//inet_addr(argv[1]);
    bind(socketfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr));
    listen(socketfd,2);
    while(1)
    {
        printf("\n----------wait for connect---------------\n");
        int len = sizeof(struct sockaddr);
        newfd = accept(socketfd,(struct sockaddr *)&client_addr,&len);
        printf("client has connect\n");
        while(1)
        {
            fd_set rfds;
            FD_ZERO(&rfds);
            FD_SET(0,&rfds);
            FD_SET(newfd,&rfds);
            struct timeval tv;
            tv.tv_sec = 1;
            tv.tv_usec = 0;
            int ret = select(newfd+1,&rfds,NULL,NULL,&tv);
            if (ret == -1) {
                perror("select failure");
                exit (EXIT_FAILURE);
            } else if (ret == 0 ){
                continue;
            } else {
                if (FD_ISSET(0,&rfds))
                {
                    bzero(buf,MAXBUF+1);
                    fgets(buf,MAXBUF,stdin);
                    if (strcmp(buf,"quit") == 0 )
                        break;
                    len = send(newfd,buf,strlen(buf) - 1,0);
                }
                if (FD_ISSET(newfd,&rfds))
                {
               
                    memset(buf,'\0',sizeof(buf));
                    int len = recv(newfd,buf,MAXBUF,0);
                    printf("recv success: %s ,bytes : %d\n",buf,len);
                }
            }
        }
        close(newfd);
    }
    close(socketfd);
    return 0;
}

服務器端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/time.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXBUF 1024

int main(int argc,char *argv[])
{
    int socketfd,newfd;
    char buf[MAXBUF+1];
    struct sockaddr_in my_addr,client_addr;
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    bzero(&my_addr,0);

    my_addr.sin_family = PF_INET;     my_addr.sin_port = htons(atoi(argv[1]));     my_addr.sin_addr.s_addr = INADDR_ANY;//inet_addr(argv[1]);     bind(socketfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr));     listen(socketfd,2);     while(1)     {         printf("\n----------wait for connect---------------\n");         int len = sizeof(struct sockaddr);         newfd = accept(socketfd,(struct sockaddr *)&client_addr,&len);         printf("client has connect\n");         while(1)         {             fd_set rfds;             FD_ZERO(&rfds);             FD_SET(0,&rfds);             FD_SET(newfd,&rfds);             struct timeval tv;             tv.tv_sec = 1;             tv.tv_usec = 0;             int ret = select(newfd+1,&rfds,NULL,NULL,&tv);             if (ret == -1) {                 perror("select failure");                 exit (EXIT_FAILURE);             } else if (ret == 0 ){                 continue;             } else {                 if (FD_ISSET(0,&rfds))                 {                     bzero(buf,MAXBUF+1);                     fgets(buf,MAXBUF,stdin);                     if (strcmp(buf,"quit") == 0 )                         break;                     len = send(newfd,buf,strlen(buf) - 1,0);                 }                 if (FD_ISSET(newfd,&rfds))                 {                                     memset(buf,'\0',sizeof(buf));                     int len = recv(newfd,buf,MAXBUF,0);                     printf("recv success: %s ,bytes : %d\n",buf,len);                 }             }         }         close(newfd);     }     close(socketfd);     return 0; }

相關文章
相關標籤/搜索