高級I/O之多路轉接epoll

一.關於epoll函數

1.什麼是epoll

是爲了處理大量的句柄而作了改進的poll。被公認爲linux2.6下性能最好的多路I/O就緒通知方式。

2.epoll函數

epoll函數與select函數其中一個不同點是epoll分別用三個函數來實現多路轉接的功能,而select函數用一個函數實現。

  • epoll_creat()
    這裏寫圖片描述
    作用:創建一個epoll模型,返回的是epoll模型句柄
    參數:size參數在linux2.8.6之後可以被忽略,此處的size不建議寫太大。
    注意:當創建好epoll句柄時,它會佔用一個fd值,所以在使用完epoll後,必須調用close()關閉,否則會導致fd()耗盡。

    • epoll_ctl()
      這裏寫圖片描述
      作用:是向epoll模型中增加刪除或者修改對應文件描述符中的對應事件
      參數:
  • 第一個參數是epoll_creat()的返回值;
  • 第二個參數表示動作,可以用三個宏來表示:EPOLL_CTL _ ADD:註冊新的fd到epfd中去;EPOLL _ CTL _MOD:修改已經註冊的fd的監聽事件;EPOLL _CTL _DEL:從epfd中刪除一個fd;
  • 第三個參數是需要監聽的fd。
  • 第四個參數告訴內核需要監聽什麼事,struct epoll_event結構如下:
    這裏寫圖片描述
    events可以是以下幾個宏的集合:
    這裏寫圖片描述這裏寫圖片描述這裏寫圖片描述

二.epoll工作原理

這裏寫圖片描述
epoll_creat的實質是在創建紅黑樹,因爲遍歷紅黑樹要比select中的數組高效的多。
epoll_wait只負責在就緒隊列中拿節點時間複雜度爲O(1),若隊列爲空,則表示無就緒隊列,若不爲空,則放入的事件是按順序的。
epoll_ctl是向紅黑樹中添加節點(即添加文件描述符及文件描述符上的事件),當我們註冊完文件描述符及其事件,則操作系統會採用回掉的機制來通知我們哪些文件描述符上的哪些事件就緒,將就緒的事件放入隊列中去。epoll_ctl的實質是在修改內核態的紅黑樹

三.實現epoll服務器代碼

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

#define M 56


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,0);
    if(sock<0)
    {
        perror("socket");
        exit(2);
    }

    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))<0)
    {
        perror("bind");
        exit(3);
    }


    if(listen(sock,10)<0){
        perror("listen");
        exit(4);
    }

    return sock;
}

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


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



     int ep_fd = epoll_create(256);
     if(ep_fd<0){
         perror("epoll_create");
         exit(5);
     }

     struct epoll_event ev;
     ev.events = EPOLLIN;
     ev.data.fd = listen_sock;


    if(epoll_ctl(ep_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)
    {
        perror("epoll_ctl");
        exit(6);    
    }

    int timeout = 10000;
    int nums = -1;//就緒事件的個數
    int maxevent = M;
    struct epoll_event array[M];

    while(1){
        nums = epoll_wait(ep_fd,array,maxevent,timeout);
        switch(nums){
            case -1:
                perror("epoll_wait");//epoll_wait出錯
                break;
            case 0:
                printf("timeout...\n");//超時
                break;
            default:
                {
                    int i=0;
                    for(;i<nums;++i)
                    {//遍歷就緒事件
                        if(array[i].data.fd==listen_sock && array[i].events & EPOLLIN){//listen_sock的讀事件
                            struct sockaddr_in client;
                            socklen_t len = sizeof(client);
                            //創建new_sock
                            int new_sock = accept(listen_sock,\
                                    (struct sockaddr*)&client,&len);
                            if(new_sock<0){
                                perror("accept");
                                exit(7);
                            }
                            //建立客戶端的連接
                            printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),\
                                    ntohs(client.sin_port));
                            //listen_sock的讀事件完成後,此時客戶端可以向客戶端寫數據,將關心的寫時間加入句柄中
                            struct epoll_event event;
                            event.events = EPOLLIN;
                            event.data.fd = new_sock;
                            epoll_ctl(ep_fd,EPOLL_CTL_ADD,new_sock,&event);
                        }else if(array[i].data.fd != listen_sock){//普通事件
                            if(array[i].events & EPOLLIN)//讀就緒
                            {//read ready
                                int buf[1024];
                                ssize_t s = read(array[i].data.fd,buf,sizeof(buf)-1);//BUG!!!
                                if(s<0){
                                    perror("read");
                                    close(array[i].data.fd);
                                    epoll_ctl(ep_fd,EPOLL_CTL_DEL,array[i].data.fd,0);
                                    exit(8);
                                }else if(s==0){
                                    printf("clilent is quit\n");
                                    close(array[i].data.fd);
                                    epoll_ctl(ep_fd,EPOLL_CTL_DEL,array[i].data.fd,0);
                                }else{
                                    buf[s] = 0;
                                    printf("client#:%s\n",buf);
                                    array[i].events = EPOLLOUT; 

                                //讀 取成功,將該時間再改爲寫
                                epoll_ctl(ep_fd,EPOLL_CTL_MOD,\
                                array[i].data.fd,&array[i]);
                                }
                            }else if(array[i].events & EPOLLOUT){//write ready寫事件就緒
                                printf("hello\n");
                                const char* msg = "HTTP/1.0 OK 200\r\n\r\n\
                                    <html><h1>hello world</h1></html>" ;//在頁面顯示hello world
                                array[i].events = EPOLLIN;
                                write(array[i].data.fd,msg,strlen(msg));
                                close(array[i].data.fd);
                                epoll_ctl(ep_fd,EPOLL_CTL_MOD,\
                                          array[i].data.fd,&array[i]);
                                    }
                            }else{//other ready
                        }
                    }

                }
        }
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章