I/O多路复用之epoll服务器

来源:互联网 发布:申万宏源交易软件下载 编辑:程序博客网 时间:2024/06/15 18:18

1、epoll服务器的函数
epoll是linux特有的I/O复用函数,它的实现和select和poll有很大的差异。epoll是使用一组函数来完成任务,而不是单个的函数。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,而无需向select和poll那样每次调用都要重复传入文件描述符集或事件集,但是epoll却需要一个额外的文件描述符,来唯一标识内核中的这个事件表。

1、epoll_create函数:
这里写图片描述
这个函数是用来创建epoll事件的模型,它只有一个参数来表示创建的这个事件表是需要多大的。函数返回的文件描述符作为epoll其他函数的第一个参数,用来指定要访问的内核时间表。

2、epoll_ctl函数:
这里写图片描述
这个函数有四个参数,epfd是epoll_create这个函数的返回值,fd是要操作的文件描述符,op参数是指定的操作类型,而操作类型有以下三种:
这里写图片描述
EPOLL_CTL_ADD:往事件表上注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件

event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:
这里写图片描述
events成员描述事件类型,而epoll_event中的data成员用于存储用户数据,而其中epoll_data_t定义如上图所示。
epoll_data_t是一个联合体,其中用的最多的是fd,它用来指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但是epoll_data_t是一个联合体我们不能同时使用ptr成员和fd成员。
epoll_ctl成功的时候返回的是0,失败就会返回-1,并设置errno。

3、epoll_wait函数:
这里写图片描述
第一个参数epfd还是epoll_create函数的返回值,maxevents表示的是最多监听的 事件,它是必须大于0的。
timeout是要等待的时间,它的含义与poll函数中的timeout的含义是相同的。
epoll_wait这个函数如果检测到事件,就将所有继续的事件从内核事件表复制到它的第二个参数events的数组中去。

2、ET模式和LT模式
epoll对文件描述符的操作有两种模式:LT模式(水平触发)和ET模式(边沿触发),LT是默认的工作模式,在这种模式下epoll相当于一个效率较高的epoll,当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作方式。
ET模式在很大程度上降低了同一个epoll事件的被重复触发的次数,因此它的效率要比LT模式要高。

3、三种服务器的比较

这里写图片描述

4、epoll服务器的优点

1、底层采用回调机制来激活节点,将已经就绪的文件描述符加到就绪队列中去。
2、当有了新的事件就绪的时候这个事件就会被加到epoll事件的红黑树中去,红黑树的增删查改的时间复杂度(O (N*lgN))是要比数组高很多
3、epoll_wait获得就绪的文件描述符是从就绪队列中获取的,它的时间复杂度为O (1)这是epoll的时间复杂度也是epoll高效的原因。
4、epoll关心的文件描述符是没有上限的
5、就绪队列中的节点会映射到epoll_wait中的events结构体中,节省了一次内核态到用户态的数据拷贝,这是用的mmap技术(内存映射)。
6、就绪事件的陈列的方式是不同的,epoll访问的是就绪队列避免了访问没价值的数据,而select是一个数组保存的。

5、代码实现
epoll.c:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<sys/socket.h>#include<sys/epoll.h>#include<sys/types.h>#include<fcntl.h>#include<arpa/inet.h>#include<netinet/in.h>#include<errno.h>#define MAX_EVENTS 10#define SIZE 64typedef struct epbuff{    int fd;    char buf[2048];    int index;}epbuff_t,*epbuff_p,**epbuff_pp;static epbuff_p alloc_buff(int fd) //此处开空间之后后面就要是ptr指向的一段空间{    epbuff_p ptr = (epbuff_p)malloc(sizeof(epbuff_t));    if(NULL == ptr)    {        perror("malloc");        exit(9);    }    ptr->fd = fd;    return ptr;}static void dealloc(epbuff_p ptr)//既然要malloc就得去free{    if(NULL != ptr)    {        free(ptr);        ptr = NULL;    }}static usage(const char* proc){    printf("Usage: [local_ip],[local_port]%s\n",proc);}int setnonblocking(int sock) //将文件描述符设置为非阻塞的{    int old_option = fcntl(sock,F_GETFL);    int new_option = old_option | O_NONBLOCK;    fcntl(sock,F_SETFL,new_option);    return old_option;}int startup(const char* ip,int port){    int sock = socket(AF_INET,SOCK_STREAM,0);    if(sock < 0)    {        perror("socket");        exit(2);    }    int opt = 1;    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)) < 0)    {        perror("bind");        exit(3);    }    if(listen(sock,10) < 0)    {        perror("listen");        exit(4);    }    return sock;}//实现的是ET版本的epoll服务器  要实现非阻塞版本int myread(int sock,char* buf){    int len = 0;    int total = 0;    while((len = read(sock,buf+total,1024) > 0) && (len = 1024 ))    {        total += len;    }    if(len > 0 && len< 1024)    {        total += len;    }    buf[total] = '\0';    return total;}int main(int argc,char* argv[]){    if(argc != 3)    {        usage(argv[0]);        return 1;    }    int listen_sock = startup(argv[1],atoi(argv[2]));    int epfd = epoll_create(SIZE);//先创建一个红黑树和队列    if(epfd < 0)    {        perror("epoll_create");        exit(5);    }    struct epoll_event ev;    ev.events = EPOLLIN | EPOLLET;    ev.data.ptr = alloc_buff(listen_sock);    setnonblocking(listen_sock);//由于是ET工作模式下  将listen_sock设置为非阻塞的状态    if(epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev) < 0)//往事件表上注册fd事件(把listen_sock)加到红黑树上去    {        perror("epoll_ctl");        exit(6);    }    int timeout = 5000;    int nums = -1;    struct epoll_event revs[MAX_EVENTS];    while(1)    {        nums = epoll_wait(epfd,revs,MAX_EVENTS,timeout);        switch(nums)        {            case 0:                printf("time out!!!...\n");                break;            case -1:                perror("epoll_wait");                break;            default:                {                    int i = 0;                    for(;i < nums ;i++)                    {                        int sock = ((epbuff_p)(revs[i].data.ptr))->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 < 0)                            {                                perror("myaccept");                                continue;                            }                            printf("get a new client! ip: %s port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));                            ev.events = EPOLLIN | EPOLLOUT;                            setnonblocking(new_sock);                            ev.data.ptr = alloc_buff(new_sock);                            int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);                            if(ret < 0)                            {                                perror("epoll_ctl");                                exit(7);                            }                        }                        else if(sock != listen_sock && (revs[i].events & EPOLLIN))                        {                            //char buf[1024];                            char* buf = ((epbuff_p)(revs[i].data.ptr))->buf;                            ssize_t s = myread(sock,buf);                            if( s > 0)                            {                                buf[s] = '\0';                                printf("client#:%s\n",buf);  //如果读成功之后就会直接关心其写操作                                ev.events=EPOLLOUT;                                epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev);                            }                            else if(s == 0) // 直接关闭连接                            {                                printf("client is close!\n");                                dealloc(revs[i].data.ptr);                                epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);                                close(sock);                            }                            else                            {                                perror("read");                                dealloc(revs[i].data.ptr);                                int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);                                if(ret < 0)                                {                                    perror("epoll_ctl");                                    exit(8);                                }                                close(sock);                            }                        }                        else if(sock != listen_sock && (revs[i].events &EPOLLOUT))                        {                            const char* msg = "HTTP/1.0 200 OK\r\n\r\n Hello epoll!  ";                            //const char* msg = "Hello epoll!\n";                            ssize_t s = write(sock,msg,strlen(msg));                            if(s < 0)                            {                                perror("write");                                exit(10);                            }                            dealloc(revs[i].data.ptr);                            epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);                            close(sock);                        }                    }                    break;                }        }    }    return 0;}

6、运行结果

服务器端:
这里写图片描述
客户端:
这里写图片描述