欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

高级I/O之多路转接epoll

程序员文章站 2022-06-14 14:38:18
...

一.关于epoll函数

1.什么是epoll

是为了处理大量的句柄而作了改进的poll。被公认为linux2.6下性能最好的多路I/O就绪通知方式。

2.epoll函数

epoll函数与select函数其中一个不同点是epoll分别用三个函数来实现多路转接的功能,而select函数用一个函数实现。

  • epoll_creat()
    高级I/O之多路转接epoll
    作用:创建一个epoll模型,返回的是epoll模型句柄
    参数:size参数在linux2.8.6之后可以被忽略,此处的size不建议写太大。
    注意:当创建好epoll句柄时,它会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则会导致fd()耗尽。

    • epoll_ctl()
      高级I/O之多路转接epoll
      作用:是向epoll模型中增加删除或者修改对应文件描述符中的对应事件
      参数:
  • 第一个参数是epoll_creat()的返回值;
  • 第二个参数表示动作,可以用三个宏来表示:EPOLL_CTL _ ADD:注册新的fd到epfd中去;EPOLL _ CTL _MOD:修改已经注册的fd的监听事件;EPOLL _CTL _DEL:从epfd中删除一个fd;
  • 第三个参数是需要监听的fd。
  • 第四个参数告诉内核需要监听什么事,struct epoll_event结构如下:
    高级I/O之多路转接epoll
    events可以是以下几个宏的集合:
    高级I/O之多路转接epoll高级I/O之多路转接epoll高级I/O之多路转接epoll

二.epoll工作原理

高级I/O之多路转接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;
}