天天看點

多路轉接-EPOLL 及簡單的EPOLL伺服器實作

EPOLL簡介

EPOLL是linux下公認的最好用的I/O就緒通知方式。

epoll是Linux核心為處理大批量檔案描述符而作了改進的poll,是Linux下多路複用IO接口select/poll的增強版本,它能顯着提高程式在大量并發連接配接中隻有少量活躍的情況下的系統CPU使用率。另一點原因就是擷取事件的時候,它無須周遊整個被偵聽的描述符集,隻要周遊那些被核心IO事件異步喚醒而加入Ready隊列的描述符集合就行了。epoll除了提供select/poll那種IO事件的水準觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得使用者空間程式有可能緩存IO狀态,減少epoll_wait/epoll_pwait的調用,提高應用程式效率。

EPOLL 相關的三個系統調用

int epoll_create(int size) 
           

建立epoll句柄,占用一個fd值,使用完epoll 後必須調用close關閉

int epoll_ctl(int epfd, int op, int fd, struct epoll_events* event) 
           

epoll的事件注冊函數,告訴核心監聽什麼事件。

  1. 第一個參數epfd是上一個函數create的傳回值。
  2. 第二個參數表示動作,用三個宏表示EPOLL_CTL_ADD注冊新的fd到epfd中。

    EPOLL_CTL_MOD修改已注冊的fd監聽事件。EPOLL_CTL_DELepfd中删除一個fd事件。

  3. 第三個參數是需要監聽的fd
  4. 第四個參數是需要監聽什麼事件。
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;
    events可以是以下幾個宏的集合
    EPOLLIN :   表示對應的檔案描述符可以讀(包括對端SOCKET正常關閉)
    EPOLLOUT:   表示對應的檔案描述符可以寫
    EPOLLPRI:   表示對應的檔案描述符有緊急的資料可讀(這裡應該表示有帶外資料到來)
    EPOLLERR:   表示對應的檔案描述符發生錯誤
    EPOLLHUP:   表示對應的檔案描述符被挂斷;
    EPOLLET:    将EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水準觸發(Level Triggered)而言的
    EPOLLONESHOT:隻監聽一次事件,當監聽完這次事件之後,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列裡
           
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
           

timeout是逾時時間(0會立即傳回,-1将永久阻塞)。如果調用成功,則傳回對應IO上已準備好檔案描述符數目。

工作原理

epoll隻告知那些已經就緒的檔案描述符,通過調用epoll_wait()函數獲得就緒的檔案描述符時,傳回的不是實際的檔案描述符,而是就緒描述符數量的值,隻需要去epoll指定一個數組去獲得這些檔案描述符,省去了這些描述符在系統調用時的開銷。

*************************************************************************
    > File Name: epoll.c
    > Author: weierxiao
    > Mail: @qq.com 
    > Created Time: Tue  Jul  :: PM CST
 ************************************************************************

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

#define EPOLL_REVS_SIZE 64

static void Usage(const char* proc)
{
    printf("Usage : %s, [local ip], [local port]\n", proc);
}

int startup(char *ip, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, );
    if (sock < )
    {
        perror("sock");
        exit();
    }
    int opt = ;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,  &opt, sizeof(opt));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if (bind(sock, (struct sockaddr*)&local, sizeof(local))<)
    {
        perror("bind");
        exit();
    }

    if (listen(sock, ) < )
    {
        perror("listen");
        exit();
    }
    return sock;
}

int main(int argc, char *argv[])
{
    if (argc != )
    {
        Usage(argv[]);
        return ;
    }

    int listen_sock = startup(argv[],atoi(argv[]));

    int epfd = epoll_create();
    if (epfd < )
    {
        perror("epoll create!\n");
        return ;
    }

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = listen_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev );

    int nums = -;
    struct epoll_event revs[EPOLL_REVS_SIZE];
    int timeout =-;
    while ()
    {
    switch ( (nums = epoll_wait(epfd,revs, EPOLL_REVS_SIZE, timeout )))

        {
            case :
               printf("time out ...\n");
               break;

            case -:
               perror("epoll wait\n");
               break;
            default:
               {
                    int i =;
                    for (;i< nums; i++)
                    {
                        int sock = revs[i].data.fd;
                        if (sock == listen_sock && (revs[i].events & EPOLLIN))
                        {
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);

                            int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
                            if(new_sock < )
                            {
                                perror("accept !\n");
                                continue;
                            }

                            printf(" get client :[%s] [%d]\n", inet_ntoa(client.sin_addr),htons(client.sin_port));
                            ev.events = EPOLLIN;
                            ev.data.fd = new_sock;
                            epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);


                        }
                        else if(sock != listen_sock)
                        {
                            if (revs[i].events & EPOLLIN)
                            {
                                char *buf[];
                                ssize_t s = read(sock, buf, sizeof(buf)-);
                                if (s > )
                                {
                                    printf("client : %s\n", buf);
                                    ev.events = EPOLLOUT;
                                    ev.data.fd = sock;
                                    epoll_ctl(epfd, EPOLL_CTL_MOD, sock, &ev);
                                }
                                else if (s == )
                                {
                                    printf("client quit\n");
                                    close(sock);
                                    epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
                                }
                                else{
                                    perror("read");
                                    close(sock);
                                    epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
                                }
                            }

                            else if (revs[i]. events & EPOLLOUT)
                            {
                                const char *msg = "HTTP/1.0  OK 200 \r\n\r\n<html><h1>WX    EPOLL  </h1></html>";
                                 write(sock,msg, strlen(msg));
                                close(sock);
                                epoll_ctl(epfd, EPOLL_CTL_DEL,sock,NULL);

                            }
                            else
                            {}
                        }
                    }
               }
               break;

        }

    }
    return ;
}

           

打開手機或電腦的浏覽器,連接配接同一個區域網路,輸入綁定的伺服器IP位址:綁定的端口号

多路轉接-EPOLL 及簡單的EPOLL伺服器實作

繼續閱讀