Linux網絡編程---I/O複用模型之epoll

1. epoll模型簡介
epoll是Linux多路服用IO接口select/poll的增強版,e對應的英文單詞就是enhancement,中文翻譯爲加強,增強,提升,充實的意思。因此epoll模型會顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率。vim

epoll把用戶關心的文件描述符上的時間放在內核的一個事件表中,無需像select和poll那樣每次調用都重複傳入文件描述符集。
epoll在獲取事件的時候,無需遍歷整個被監聽的文件描述符集合,而是遍歷那些被內核IO事件異步喚醒而加入ready隊列的描述符集合。
因此,epoll是Linux大規模高併發網絡程序的首選模型。緩存

2.epoll模型的API
epoll使用一組函數來完成任務服務器

2.1 函數epoll_create

建立一個epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。網絡

#include <sys/epoll.h>

int epoll_create(int size);
//返回值:若成功,返回一個非負的文件描述符,若出錯,返回-1。
  • 該函數返回一個文件描述符,用來惟一標示內核中這個事件表,sizeof參數提示內核要監聽的文件描述符個數,這與內存大小有關。
  • 返回的文件描述符將是其餘全部epoll系統調用的第一個參數,以指定要訪問的內核時間表,因此用該返回的文件描述符至關與其餘epoll調用的把手、把柄同樣。

查看進程可以打開的最大數目的文件描述符併發

cat /proc/sys/fs/file-max
1215126
//該值與內存大小有關

修改最大文件描述符限制app

vim /etc/security/limits.conf
//重啓生效

2.2 函數epoll_ctl

該函數用來操做epoll的內核事件表異步

#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//返回值:若成功,返回0,若出錯返回-1。

epfd就是函數epoll_create建立的句柄。
op是指定操做類型,有一下三種 
EPOLL_CTL_ADD,向epfd註冊fd的上的event
EPOLL_CTL_MOD,修改fd已註冊的event
EPOLL_CTL_DEL,從epfd上刪除fd的event 
     1.fd是操做的文件描述符
     2.event指定內核要監聽事件,它是struct epoll_event結構類型的指針。epoll_event定義以下:socket

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

events成員描述事件類型,將如下宏定義經過位或方式組合函數

EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉)
POLLOUT:表示對應的文件描述符能夠寫
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)
EPOLLERR:表示對應的文件描述符發生錯誤
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的
EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏
data用於存儲用戶數據,是epoll_data_t結構類型,該結構定義以下:高併發

typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
} epoll_data_t;
  • epoll_data_t是一個聯合體,fd指定事件所從屬的目標文件描述符。ptr能夠用來指定fd相關的用戶數據,但二者不能同時使用。

2.3 函數epoll_wait

函數epoll_wait用來等待所監聽文件描述符上有事件發生

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//返回值:若成功,返回就緒的文件描述符個數,若出錯,返回-1,時間超時返回0

epfd就是函數epoll_create建立的句柄
timeout是超時事件,-1爲阻塞,0爲當即返回,非阻塞,大於0是指定的微妙
events是一個 傳入傳出參數,它是epoll_event結構的指針,用來從內核獲得事件的集合
maxevents告知內核events的大小,但不能大於epoll_create()時建立的size
3. LT和ET模式
LT(Level Triggered,電平觸發):LT模式是epoll默認的工做模式,也是select和poll的工做模式,在LT模式下,epoll至關於一個效率較高的poll。 
採用LT模式的文件描述符,當epoll_wait檢測到其上有事件發生並將此事件通知應用程序後,應用程序能夠不當即處理此事件,當下一次調用epoll_wait是,epoll_wait還會將此事件通告應用程序。
ET(Edge Triggered,邊沿觸發):當調用epoll_ctl,向參數event註冊EPOLLET事件時,epoll將以ET模式來操做該文件描述符,ET模式是epoll的高效工做模式. 
對於採用ET模式的文件描述符,當epoll_wait檢測到其上有事件發生並將此通知應用程序後,應用程序必須當即處理該事件,由於後續的epoll_wait調用將不在嚮應用程序通知這一事件。ET模式下降了贊成epoll事件被觸發的次數,效率比LT模式高。
4. LT和ET的服務端和客戶端代碼
4.1 服務器端
 

#include <sys/epoll.h>
#include <fcntl.h>
#include "wrap.h"

#define MAX_EVENT_NUM           1024
#define BUFFER_SIZE             10
#define true                    1
#define false                   0

int setnonblocking(int fd)
{
        int old_opt = fcntl(fd, F_GETFD);
        int new_opt = old_opt | O_NONBLOCK;
        fcntl(fd, F_SETFD, new_opt);

        return old_opt;
}//將文件描述符設置爲非阻塞的

void addfd(int epollfd, int fd, int enable_et)
{
        struct epoll_event event;
        event.data.fd = fd;
        event.events = EPOLLIN;
        if(enable_et){
                event.events |= EPOLLET;
        }
        epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
//      setnonblocking(fd);
}//將文件描述符fd的EPOLLIN註冊到epollfd指示的epoll內核事件表中,enable_et表示是否對fd啓用ET模式

void lt(struct epoll_event *events, int num, int epollfd, int listenfd)
{
        char buf[BUFFER_SIZE];
        for(int i = 0; i < num; i++){
                int sockfd = events[i].data.fd;
                if(sockfd == listenfd){
                        struct sockaddr_in clientaddr;
                        socklen_t clilen = sizeof(clientaddr);
                        int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
                        addfd(epollfd, connfd, false);//對connfd使用默認的lt模式
                }else if(events[i].events & EPOLLIN){//只要socket讀緩存中還有未讀的數據,這段代碼就會觸發
                        printf("event trigger once\n");
                        memset(buf, '\0', BUFFER_SIZE);
                        int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                        if(ret <= 0){
                                Close(sockfd);
                                continue;
                        }
                        printf("get %d bytes of content:%s\n", ret, buf);
                }else{
                        printf("something else happened\n");
                }
        }
}

void et(struct epoll_event *event, int num, int epollfd, int listenfd)
{
        char buf[BUFFER_SIZE];
        for(int i = 0; i < num; i++){
                int sockfd = event[i].data.fd;
                if(sockfd == listenfd){
                        struct sockaddr_in clientaddr;
                        int clilen = sizeof(clientaddr);
                        int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);
                        addfd(epollfd, connfd, true);//多connfd開啓ET模式
                }else if(event[i].events & EPOLLIN){
                        printf("event trigger once\n");
                        while(1){//這段代碼不會重複觸發,因此要循環讀取數據
                                memset(buf, '\0', BUFFER_SIZE);
                                int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
                                if(ret < 0){
                                        if((errno == EAGAIN) || (errno == EWOULDBLOCK)){
                                                printf("read later\n");
                                                break;
                                        }
                                        Close(sockfd);
                                        break;
                                }else if(ret == 0){
                                        Close(sockfd);
                                }else{
                                        printf("get %d bytes of content:%s\n", ret, buf);
                                }
                        }
                }else{

                        printf("something else happened \n");
                }
        }
}

int start_ser(char *ipaddr, char *port)
{
        int sock = Socket(AF_INET, SOCK_STREAM, 0);

        struct sockaddr_in serveraddr;
        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(atoi(port));
        inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);

        Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

        Listen(sock, 128);

        return sock;
}

int main(int argc, char *argv[])
{
        int listenfd = start_ser(argv[1], argv[2]);

        struct epoll_event events[MAX_EVENT_NUM];
        int epollfd = epoll_create(5);
        if(epollfd < 0){
                perr_exit("epoll_create err");
        }
        addfd(epollfd, listenfd, true);
        while(1){
                int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);
                if(ret < 0){
                        printf("epoll failure\n");
                        break;
                }

                lt(events, ret, epollfd, listenfd);//lt模式
                //et(events, ret, epollfd, listenfd);//et模式
        }
        Close(listenfd);
        return 0;
}
//warp.h文件是將socket,bind,listen等函數封裝爲第一個字母大寫的頭文件

4.2 客戶端

#include "wrap.h"                                                            

int main(int argc, char *argv[])
{
        int connfd;
        struct sockaddr_in serveraddr;
        char buf[1024];

        connfd = Socket(AF_INET, SOCK_STREAM, 0);

        bzero(&serveraddr, sizeof(serveraddr));
        serveraddr.sin_family = AF_INET;
        serveraddr.sin_port = htons(atoi(argv[2]));
        inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);

        Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

        while(fgets(buf, 1024, stdin) != NULL){
                Write(connfd, buf, strlen(buf));
        }

        Close(connfd);
        return 0;
}
相關文章
相關標籤/搜索