Linux epoll模型__Linux

來源:互聯網
上載者:User

轉載自:http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html

定義:

  epoll是Linux核心為處理大批控制代碼而作改進的poll,是Linux下多工IO介面select/poll的增強版本,它能顯著的減少程式在大量並發串連中只有少量活躍的情況下的系統CPU利用率。因為它會複用檔案描述符集合來傳遞結果而不是迫使開發人員每次等待事件之前都必須重新準備要被偵聽的檔案描述符集合,另一個原因就是擷取事件的時候,它無須遍曆整個被偵聽的描述符集,只要遍曆那些被核心IO事件非同步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select\poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得使用者空間程式有可能緩衝IO狀態,減少epoll_wait/epoll_pwait的調用,提供應用程式的效率。

工作方式:

  LT(level triggered):水平觸發,預設方式,同時支援block和no-block socket,在這種做法中,核心告訴我們一個檔案描述符是否被就緒了,如果就緒了,你就可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式編程出錯的可能性較小。傳統的select\poll都是這種模型的代表。

  ET(edge-triggered):邊沿觸發,高速工作方式,只支援no-block socket。在這種模式下,當描述符從未就緒變為就緒狀態時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個描述符發送更多的就緒通知,直到你做了某些操作導致那個檔案描述符不再為就緒狀態了(比如:你在發送、接受或者接受請求,或者發送接受的資料少於一定量時導致了一個EWOULDBLOCK錯誤)。但是請注意,如果一直不對這個fs做IO操作(從而導致它再次變成未就緒狀態),核心不會發送更多的通知。

  區別:LT事件不會丟棄,而是只要讀buffer裡面有資料可以讓使用者讀取,則不斷的通知你。而ET則只在事件發生之時通知。

使用方式:

  1、int epoll_create(int size)

建立一個epoll控制代碼,參數size用來告訴核心監聽的數目。

  2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

epoll事件註冊函數,

  參數epfd為epoll的控制代碼;

  參數op表示動作,用3個宏來表示:EPOLL_CTL_ADD(註冊新的fd到epfd),EPOLL_CTL_MOD(修改已經註冊的fd的監聽事件),EPOLL_CTL_DEL(從epfd刪除一個fd);

  參數fd為需要監聽的標示符;

  參數event告訴核心需要監聽的事件,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隊列裡

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表示已逾時。

應用舉例:

  下面,我引用google code中別人寫的一個簡單程式來進行說明。svn路徑:http://sechat.googlecode.com/svn/trunk/

  該程式一個簡單的聊天室程式,用Linux C++寫的,伺服器主要是用epoll模型實現,支援高並發,我測試在有10000個用戶端串連伺服器的時候,server處理時間不到1秒,當然用戶端只是與伺服器串連之後,接受伺服器的歡迎訊息而已,並沒有做其他的通訊。雖然程式比較簡單,但是在我們考慮伺服器高並發時也提供了一個思路。在這個程式中,我已經把所有的調試資訊和一些與epoll無關的資訊幹掉,並添加必要的注釋,應該很容易理解。

  程式共包含2個標頭檔和3個cpp檔案。其中3個cpp檔案中,每一個cpp檔案都是一個應用程式,server.cpp:伺服器程式,client.cpp:單個用戶端程式,tester.cpp:類比高並發,開啟10000個用戶端去連伺服器。

  utils.h標頭檔,就包含一個設定socket為不阻塞函數,如下:

int setnonblocking(int sockfd){    CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));    return 0;}

  local.h標頭檔,一些常量的定義和函數的聲明,如下:

#define BUF_SIZE 1024                 //預設緩衝區#define SERVER_PORT 44444             //監聽連接埠#define SERVER_HOST "192.168.34.15"   //伺服器IP地址#define EPOLL_RUN_TIMEOUT -1          //epoll的逾時時間#define EPOLL_SIZE 10000              //epoll監聽的用戶端的最大數目#define STR_WELCOME "Welcome to seChat! You ID is: Client #%d"#define STR_MESSAGE "Client #%d>> %s"#define STR_NOONE_CONNECTED "Noone connected to server except you!"#define CMD_EXIT "EXIT"//兩個有用的宏定義:檢查和賦值並且檢測#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}//================================================================================================//函數名:                  setnonblocking//函數描述:                設定socket為不阻塞//輸入:                    [in] sockfd socket標示符//輸出:                    無//返回:                    0//================================================================================================int setnonblocking(int sockfd);//================================================================================================//函數名:                  handle_message//函數描述:                處理每個用戶端socket//輸入:                    [in] new_fd socket標示符//輸出:                    無//返回:                    返回從用戶端接受的資料的長度//================================================================================================int handle_message(int new_fd);

server.cpp檔案,epoll模型就在這裡實現,如下:

#include "local.h"#include "utils.h"using namespace std;// 存放用戶端socket描述符的listlist<int> clients_list;int main(int argc, char *argv[]){    int listener;   //監聽socket    struct sockaddr_in addr, their_addr;      addr.sin_family = PF_INET;    addr.sin_port = htons(SERVER_PORT);    addr.sin_addr.s_addr = inet_addr(SERVER_HOST);    socklen_t socklen;    socklen = sizeof(struct sockaddr_in);    static struct epoll_event ev, events[EPOLL_SIZE];    ev.events = EPOLLIN | EPOLLET;     //對讀感興趣,邊沿觸發    char message[BUF_SIZE];    int epfd;  //epoll描述符    clock_t tStart;  //計算程式已耗用時間    int client, res, epoll_events_count;    CHK2(listener, socket(PF_INET, SOCK_STREAM, 0));             //初始化監聽socket    setnonblocking(listener);                                    //設定監聽socket為不阻塞    CHK(bind(listener, (struct sockaddr *)&addr, sizeof(addr))); //綁定監聽socket    CHK(listen(listener, 1));                                    //設定監聽    CHK2(epfd,epoll_create(EPOLL_SIZE));                         //建立一個epoll描述符,並將監聽socket加入epoll    ev.data.fd = listener;    CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev));    while(1)    {        CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT));        tStart = clock();        for(int i = 0; i < epoll_events_count ; i++)        {            if(events[i].data.fd == listener)                    //新的串連到來,將串連添加到epoll中,並發送歡迎訊息            {                CHK2(client,accept(listener, (struct sockaddr *) &their_addr, &socklen));                setnonblocking(client);                ev.data.fd = client;                CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev));                clients_list.push_back(client);                  // 添加新的用戶端到list                bzero(message, BUF_SIZE);                res = sprintf(message, STR_WELCOME, client);                CHK2(res, send(client, message, BUF_SIZE, 0));            }else             {                CHK2(res,handle_message(events[i].data.fd)); //注意:這裡並沒有調用epoll_ctl重新設定socket的事件類型,但還是可以繼續收到用戶端發送過來的資訊            }        }        printf("Statistics: %d events handled at: %.2f second(s)\n", epoll_events_count, (double)(clock() - tStart)/CLOCKS_PER_SEC);    }    close(listener);    close(epfd);    return 0;}int handle_message(int client)  {    char buf[BUF_SIZE], message[BUF_SIZE];    bzero(buf, BUF_SIZE);    bzero(message, BUF_SIZE);    int len;    CHK2(len,recv(client, buf, BUF_SIZE, 0));  //接受用戶端資訊    if(len == 0)   //用戶端關閉或出錯,關閉socket,並從list移除socket    {        CHK(close(client));        clients_list.remove(client);    }    else          //向用戶端發送資訊    {         if(clients_list.size() == 1)         {             CHK(send(client, STR_NOONE_CONNECTED, strlen(STR_NOONE_CONNECTED), 0));                return len;        }                sprintf(message, STR_MESSAGE, client, buf);        list<int>::iterator it;        for(it = clients_list.begin(); it != clients_list.end(); it++)        {           if(*it != client)           {                 CHK(send(*it, message, BUF_SIZE, 0));           }        }    }    return len;}

tester.cpp檔案,類比伺服器的高並發,開啟10000個用戶端去串連伺服器,如下:

#include "local.h"#include "utils.h"using namespace std;char message[BUF_SIZE];     //接受伺服器資訊list<int> list_of_clients;  //存放所有用戶端int res;clock_t tStart;int main(int argc, char *argv[]){    int sock;     struct sockaddr_in addr;    addr.sin_family = PF_INET;    addr.sin_port = htons(SERVER_PORT);    addr.sin_addr.s_addr = inet_addr(SERVER_HOST);        tStart = clock();    for(int i=0 ; i<EPOLL_SIZE; i++)  //產生EPOLL_SIZE個用戶端,這裡是10000個,類比高並發    {       CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));       CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);       list_of_clients.push_back(sock);       bzero(&message, BUF_SIZE);       CHK2(res,recv(sock, message, BUF_SIZE, 0));       printf("%s\n", message);    }       list<int>::iterator it;          //移除所有用戶端    for(it = list_of_clients.begin(); it != list_of_clients.end() ; it++)       close(*it);    printf("Test passed at: %.2f second(s)\n", (double)(clock() - tStart)/CLOCKS_PER_SEC);     printf("Total server connections was: %d\n", EPOLL_SIZE);        return 0;}

我就不給出程式的執行結果的截圖了,不過下面這張截圖是代碼作者自己測試的,可以看出,並發10000無壓力呀 

  單個用戶端去串連伺服器,client.cpp檔案,如下:

#include "local.h"#include "utils.h"using namespace std;char message[BUF_SIZE];/*    流程:        調用fork產生兩個進程,兩個進程通過管道進行通訊        子進程:等待客戶輸入,並將客戶輸入的資訊通過管道寫給父進程        父進程:接受伺服器的資訊並顯示,將從子進程接受到的資訊發送給伺服器*/int main(int argc, char *argv[]){    int sock, pid, pipe_fd[2], epfd;    struct sockaddr_in addr;    addr.sin_family = PF_INET;    addr.sin_port = htons(SERVER_PORT);    addr.sin_addr.s_addr = inet_addr(SERVER_HOST);    static struct epoll_event ev, events[2];     ev.events = EPOLLIN | EPOLLET;    //退出標誌    int continue_to_work = 1;    CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));    CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);    CHK(pipe(pipe_fd));        CHK2(epfd,epoll_create(EPOLL_SIZE));    ev.data.fd = sock;    CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev));    ev.data.fd = pipe_fd[0];    CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd[0], &ev));    // 調用fork產生兩個進程    CHK2(pid,fork());    switch(pid)    {        case 0:                   // 子進程            close(pipe_fd[0]);    // 關閉讀端            printf("Enter 'exit' to exit\n");            while(continue_to_work)            {                bzero(&message, BUF_SIZE);                fgets(message, BUF_SIZE, stdin);                // 當收到exit命令時,退出                if(strncasecmp(message, CMD_EXIT, strlen(CMD_EXIT)) == 0)                {                    continue_to_work = 0;                }                else                {                                CHK(write(pipe_fd[1], message, strlen(message) - 1));                }            }            break;        default:                 // 父進程            close(pipe_fd[1]);   // 關閉寫端            int epoll_events_count, res;            while(continue_to_work)             {                CHK2(epoll_events_count,epoll_wait(epfd, events, 2, EPOLL_RUN_TIMEOUT));                for(int i = 0; i < epoll_events_count ; i++)                {                    bzero(&message, BUF_SIZE);                    if(events[i].data.fd == sock)   //從伺服器接受資訊                    {                        CHK2(res,recv(sock, message, BUF_SIZE, 0));                        if(res == 0)               //伺服器已關閉                        {                            CHK(close(sock));                            continue_to_work = 0;                        }                        else                         {                            printf("%s\n", message);                        }                    }                    else        //從子進程接受資訊                    {                        CHK2(res, read(events[i].data.fd, message, BUF_SIZE));                        if(res == 0)                        {                            continue_to_work = 0;                         }                        else                        {                            CHK(send(sock, message, BUF_SIZE, 0));                        }                    }                }            }    }    if(pid)    {        close(pipe_fd[0]);        close(sock);    }else    {        close(pipe_fd[1]);    }    return 0;}


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.