1. 程式人生 > >Linux網絡編程——多路復用之epoll

Linux網絡編程——多路復用之epoll

並發 到來 聊天 客戶端 緩存 urn 結果 pri user

目錄

  • Linux網絡編程——多路復用之epoll
    • 基礎API
    • 實例一、epoll實現在線聊天
    • 實例二、epoll實現在客戶端斷開後服務端能一直運行,客戶端可以多次重連

Linux網絡編程——多路復用之epoll

? epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量並發連接中只有少量活躍的情況下的系統CPU利用率,因為它會復用文件描述符集合來傳遞結果而不用迫使開發者每次等待事件之前都必須重新準備要被偵聽的文件描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。

? 目前epell是linux大規模並發網絡程序中的熱門首選模型。

? epoll除了提供select/poll那種IO事件的電平觸發(Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

基礎API

  1. 創建一個epoll句柄,參數size用來告訴內核監聽的文件描述符的個數,跟內存大小有關。

? #include <sys/epoll.h>

? int epoll_create(int size) size:監聽數目

  1. 控制某個epoll監控的文件描述符上的事件:註冊、修改、刪除。

? #include <sys/epoll.h>

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

? epfd: 為epoll_creat的句柄

? op: 表示動作,用3個宏來表示:

? EPOLL_CTL_ADD (註冊新的fd到epfd),

? EPOLL_CTL_MOD (修改已經註冊的fd的監聽事件),

? EPOLL_CTL_DEL (從epfd刪除一個fd);

? event: 告訴內核需要監聽的事件

? struct epoll_event {

? __uint32_t events; /* Epoll events */

? epoll_data_t data; /* User data variable */

? };

? typedef union epoll_data {

? void *ptr;

? int fd;

? uint32_t u32;

? uint64_t u64;

? } epoll_data_t;

? EPOLLIN : 表示對應的文件描述符可以讀(包括對端SOCKET正常關閉)

? EPOLLOUT: 表示對應的文件描述符可以寫

? EPOLLPRI: 表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來)

? EPOLLERR: 表示對應的文件描述符發生錯誤

? EPOLLHUP: 表示對應的文件描述符被掛斷;

? EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)而言的

? EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裏

  1. 等待所監控文件描述符上有事件的產生,類似於select()調用。

? #include <sys/epoll.h>

? int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

? events: 用來存內核得到事件的集合,

? maxevents: 告之內核這個events有多大,這個maxevents的值不能大於創建epoll_create()時的size,

? timeout: 是超時時間

? -1: 阻塞

? 0: 立即返回,非阻塞

? >0: 指定毫秒

? 返回值: 成功返回有多少文件描述符就緒,時間到時返回0,出錯返回-1

實例一、epoll實現在線聊天

tcp_server.c

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//點分十進制轉為32位的網絡字節序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//緩沖區的大小,一瞬間能夠放入的客戶端連接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
    ERROR_CHECK(new_fd, -1, "accept");
    printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
    char buf[128] = {0};
    int epfd = epoll_create(1);//參數size表示監聽的數目大小
    ERROR_CHECK(epfd, -1, "epoll_create");
    struct epoll_event event, evs[2];
    event.events = EPOLLIN; //表示對應的文件描述符可讀,監控讀事件
    event.data.fd = STDIN_FILENO;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次註冊永久生效
    ERROR_CHECK(ret, -1, "epoll_ctl");
    event.data.fd = new_fd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
    int i, readyFdNum;
    while(1){
        memset(evs, 0, sizeof(evs));
        readyFdNum = epoll_wait(epfd, evs, 2, -1);//大小為2,-1表示永久阻塞,返回值是需要處理的事件數目
        for(i = 0;i < readyFdNum;i++){
            if(evs[i].data.fd == new_fd){
                bzero(buf, sizeof(buf));
                ret = recv(new_fd, buf, sizeof(buf), 0);
                ERROR_CHECK(ret, -1, "recv");
                if(ret == 0){
                    printf("byebye!\n");
                    goto chatOver;
                }           
                printf("%s\n", buf);
            }
            if(0 == evs[i].data.fd){
                memset(buf, 0, sizeof(buf));
                ret = read(STDIN_FILENO, buf, sizeof(buf));
                if(ret == 0){
                    printf("byebye!\n");
                    goto chatOver;
                }
                ret = send(new_fd, buf, strlen(buf) - 1, 0);
                ERROR_CHECK(ret, -1, "send");
            }
        }
    }
chatOver:
    close(new_fd);
    close(socketFd);
    return 0;
}

實例二、epoll實現在客戶端斷開後服務端能一直運行,客戶端可以多次重連

#include <func.h>

int main(int argc,char* argv[])
{
    ARGS_CHECK(argc,3);
    int socketFd;
    socketFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(socketFd, -1, "socket");
    struct sockaddr_in ser;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(atoi(argv[2]));
    ser.sin_addr.s_addr = inet_addr(argv[1]);//點分十進制轉為32位的網絡字節序
    int ret;
    ret = bind(socketFd, (struct sockaddr*)&ser, sizeof(ser));
    ERROR_CHECK(ret, -1, "bind");
    listen(socketFd, 10);//緩沖區的大小,一瞬間能夠放入的客戶端連接信息
    int new_fd;
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    int addrlen = sizeof(client);
    char buf[128] = {0};
    int epfd = epoll_create(1);//參數size表示監聽的數目大小
    ERROR_CHECK(epfd, -1, "epoll_create");
    struct epoll_event event, evs[3];
    event.events = EPOLLIN; //表示對應的文件描述符可讀,監控讀事件
    event.data.fd = STDIN_FILENO;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);//一次註冊永久生效
    ERROR_CHECK(ret, -1, "epoll_ctl");
    event.data.fd = socketFd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, socketFd, &event);
    int i, readyFdNum;
    while(1){
        memset(evs, 0, sizeof(evs));
        readyFdNum = epoll_wait(epfd, evs, 3, -1);//大小為3,-1表示永久阻塞,返回值是需要處理的事件數目
        for(i = 0;i < readyFdNum;i++){
            if(evs[i].data.fd == socketFd){
                addrlen = sizeof(client);
                new_fd = accept(socketFd, (struct sockaddr*)&client, &addrlen);
                ERROR_CHECK(new_fd, -1, "accept");
                printf("client ip=%s, port=%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
                event.data.fd = new_fd;
                ret = epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &event);
                ERROR_CHECK(ret, -1, "epoll_ctl");
            }
            if(evs[i].data.fd == new_fd){  //new_fd中有數據
                memset(buf, 0, sizeof(buf));
                ret = recv(new_fd, buf, sizeof(buf), 0);
                ERROR_CHECK(ret, -1, "recv");
                if(ret == 0){
                    printf("byebye!\n");
                    event.events = EPOLLIN;
                    event.data.fd = new_fd;
                    ret = epoll_ctl(epfd, EPOLL_CTL_DEL, new_fd, &event);
                    //printf("line = %d\n", __LINE__);
                    ERROR_CHECK(ret, -1, "epoll_ctl");
                    continue;
                }
                printf("%s\n", buf);
            }
            if(0 == evs[i].data.fd){//標準輸入
                memset(buf, 0, sizeof(buf));
                ret = read(STDIN_FILENO, buf, sizeof(buf));
                if(ret == 0){
                    printf("byebye\n");
                    goto chatOver;
                }
                ret = send(new_fd, buf, strlen(buf) - 1, 0);
                ERROR_CHECK(ret, -1, "send");
            }
        }
    }
chatOver:
    close(new_fd);
    close(socketFd);
    return 0;
}

Linux網絡編程——多路復用之epoll