<Linux>解析以及实现epoll服务器

今天我们来了解一下关于epoll服务器的知识。

1、什么是epoll?

epoll是linux内核为处理大批量句柄而作了改进的poll,它是Linux下多路复用IO接口select/poll的增强版本,
epoll在底层实现了自己的高速缓存区,并且建立了一个红黑树用于存放socket,另外维护了一个就绪队列用来存放准备就绪的事件。

2、epoll的实现原理

首先我们来看3个函数

(1)epoll_create()
函数原型:

int epoll_create(int size);

此函数调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

(2)epoll_ctl()

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

通过调用此函数向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

(3)epoll_wait()

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

通过调用此函数收集在epoll监控中已经发生的事件。

  当系统执行epoll_ create时,就创建了红黑树和就绪队列,执行epoll_ ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后调用回调函数,用于当中断事件来临时向就绪队列中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

3、实现epoll服务器的具体代码

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


#define SIZE 64

static void usage(const char*proc)
{
    printf("Usage:%s [local_ip] [local_port]\n",proc);
}

int startup(const char *ip,int port)//创建套接字
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(3);
    }

    int opt=1;//如果客户端在,服务器主动断开,服务器进入time_out状态导致链接断开,用setsockopt函数
    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(4);
    }
    if(listen(sock,10)<0)
    {
        perror("listen");
        exit(5);
    }
    return sock;
}
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(256);//创建epoll模型

    if(epfd<0)
    {
        perror("epoll_create");
         return 2;    
    }

    printf("sock: %d ,epfd: %d\n",listen_sock,epfd);
    //关心listen_socket

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

    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);//让红黑树有一个节点

    int num =0;
    int timeout =-1;
    struct epoll_event revs[SIZE];
    while(1)
    {
        switch(num=epoll_wait(epfd,revs,SIZE,timeout))//查找红黑树是否有就绪事件
        {
            case 0:
                printf("timeout...\n");
                break;
            case -1:
                perror("epoll_wait");
                break;
            default:
                { //至少有一个事件就绪
                    int i=0;
                    for(;i<num;i++)
                    {
                        int fd=revs[i].data.fd;
                        if(fd==listen_sock&& (revs[i].events&EPOLLIN))
                            //listen_sock ready!
                        {
                            struct sockaddr_in client;
                            socklen_t len =sizeof(client);
                             int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
                             if(new_sock<0)
                             {
                                    perror("accept");
                                    continue;
                             }
                             printf("get a new client [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
                          //添加文件描述符到epoll模型中
                          ev.events=EPOLLIN;
                          ev.data.fd=new_sock;
                          epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);
                        }
                        else if(fd!=listen_sock)
                            //其他文件描述符
                        {
                            if(revs[i].events&EPOLLIN)//normal sock read ev ready!
                             {
                                 char buf[1024];
                                 ssize_t s=read(fd,buf,sizeof(buf)-1);
                                if(s>0)
                                {
                                     buf[s]=0;
                                     printf("%s",buf);
                                     //read done!start write
                                     ev.events=EPOLLOUT;
                                     ev.data.fd=fd;
                                     epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
                                }
                                else if(s==0)
                                {
                                    close(fd);
                                    epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                                    printf("client quit!\n");
                                }
                                else
                                {
                                    close(fd);
                                    epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                                    perror("read");
                                }

                          }
                            else if(revs[i].events&EPOLLOUT)//normal sock write ev ready!
                            {
                                  const char *echo="HTTP/1.0 200 OK\r\n\r\n<html><hl>hello epoll!!</htl></html><br/>";
                                   write(fd,echo,strlen(echo));
                                   close(fd);//写完关闭链接
                                   epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                            }
                            else//other ev
                            {

                            }
                        }
                        else
                        {

                        }

                    }
                }
              break;

        }
    }
    close(listen_sock);
    close(epfd);
    return 0;
}

当代码运行成功时结果如下:

这里写图片描述

然后我们在自己的浏览器上输入自己的ip地址:端口号就可以看到如下结果:

这里写图片描述

4、epoll模型的优点

(1)epoll维护的描述符数目无上限,性能不会随着描述符数目的增加而下降
(2)epoll先通过epoll_ctl注册一个描述符到内核中,并一直维护着而不像poll每次操作都将所有要监控的描述符传递给内核
(3)在描述符读写就绪时,通过回掉函数将自己加入就绪队列中,之后epoll_wait返回该就绪队列,所以用户不需要遍历整个文件描述符判断哪些事件就绪。
(4)支持ET高效模式。

总结:

select,poll实现需要自己不断轮询所有事件集合直到设备就绪,select和poll要遍历整个fd集合,而epoll只要判断一下就绪队列是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值