epoll服务器

10 篇文章 0 订阅

相比select、poll,epoll是I/O多路转接最高效的手段,它几乎具备了之前select、poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。epoll实现只有epoll_create()、epoll_ctl()、epoll_wait()三个系统调用函数。

1、epoll_create()函数:

int epoll_create(int size);
  • 含义:创建一个epoll句柄,占用一个fd,使用完成epoll需要关闭此fd;
  • 参数:size:从linux2.6.8之后,size参数是被忽略的;
  • 返回值:返回一个epfd.

2.epoll_ctl()函数:

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 含义:事件注册函数,向epoll模型中增加、修改、删除需要监听的fd,并指定对应关心的事件;
  • 参数:
  • epfd:epoll_create返回值;
  • op:表示动作,用三个宏来表示:(1)EPOLL_CTL_ADD:添加新的fd到epfd中;(2)EPOLL_CTL_MOD:修改以及注册fd监听事件;(3)EPOLL_CTL_DEL:从epfd中删除一个fd;
  • fd:需要监听的fd;
  • event:需要监听的事件:struct epoll_event结构如下:
struct epoll_event
{
    _unint32_t events;//关心的事件
    epoll_data_t data;
};
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队列里;

3.epoll_wait()函数:

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • 含义:等待epoll模型中哪些fd的事件就绪;
  • 参数:
  • epfd:epoll句柄;
  • events:结构体数组,输出型参数,epoll将会把发生的事件赋值到events数组中;
  • maxevents:告之内核这个events数组有多大,这个 maxevents的值不能大于创建epoll_create()时的size值;
  • timeout:超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞);
  • 返回值:函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

以上即为epoll最基本的操作,其中其最基本的原理为:

  1. epoll_create()在底层创建一个红黑树与就绪队列,返回一个epoll模型;
  2. epoll_ctl()即在红黑树插入新的结点,即关心的fd,当其事件就绪,采用回调机制激活;
  3. epll_wait()只关心就绪队列中的就绪fd,直接对对应fd进行相应事件处理;
  4. 当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,只需要去epoll指定的数组中依次取得相应数量的文件描述符即可,其使用了内存映射(mmap)技术,彻底省掉了这些文件描述符在系统调用时复制的开销。
  5. 于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
  6. Epoll的2种工作方式-水平触发(LT)和边缘触发(ET):LT:是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知,所以,这种模式编程出错误可能性要小一点,传统的select/poll都是这种模型的代表. ET:是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

epoll模型的优点高效性体现:

  1. select维护数组,epoll维护红黑树,其增删查改效率较高;
  2. select遍历数组,epoll只遍历就绪队列,时间复杂度为o(1),并且队列只存储就绪结点;
  3. epoll监视的fd数目无上限,由于其由红黑树描述的,可以一直创建;
  4. 其用户与内核采用内存映射机制;
  5. 就绪队列从0开始连续放置就绪的fd;
  6. IO效率不随FD数目增加而线性下降;
  7. 内核微调.

以下实现LT模式下的epoll服务器:

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

static void usage(const char* proc)
{
    printf("%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;
    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");
        close(sock);
        exit(4);
    }

    if(listen(sock,10)<0)
    {
        perror("listen");
        close(sock);
        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 efds=epoll_create(1024);             //创建epoll模型 

    struct epoll_event ev;
    ev.events=EPOLLIN;
    ev.data.fd=listen_sock;
    epoll_ctl(efds,EPOLL_CTL_ADD,listen_sock,&ev);     //将监听套接字加入epoll模型中,并关心其读事件,采用激活回调机制 

    struct epoll_event fds[1024];    //定义共享内存就绪队列大小 
    int maxnum=1024;    //定义最多可以监听多少个事件 
    int timeout=500;
    while(1)
    {
        int nums=epoll_wait(efds,fds,maxnum,timeout);    //一段时间内等待一组文件描述符的事件(实际则直接遍历就绪队列) 
        switch(nums)
        {
            case -1:
                perror("epoll_wait");
                break;
            case 0:
                printf("timeout\n");
                break;
            default:
                {
                    int i=0;
                    for( ;i<nums;++i)
                    {
                        int sock=fds[i].data.fd;
                        if(sock==listen_sock && fds[i].events&EPOLLIN)   //监听套接字就绪 
                        {
                            struct sockaddr_in client;
                            socklen_t len=sizeof(client);
                            int new_sock=accept(sock,(struct sockaddr*)&client,&len);  //建立连接 
                            if(new_sock<0)
                            {
                                perror("accept");
                                continue;
                            }

                            ev.events=EPOLLIN;
                            ev.data.fd=new_sock;
                            epoll_ctl(efds,EPOLL_CTL_ADD,new_sock,&ev);     //将新套接字加入efds模型关心其读事件 
                        }
                        else if(fds[i].events & EPOLLIN)   //nomal sock ready    //读事件就绪 
                        {
                            char buf[1024];
                            ssize_t s=read(sock,buf,sizeof(buf)-1);
                            if(s>0)
                            {
                                buf[s]=0;
                                printf("client# %s\n");

                                ev.events=EPOLLOUT;
                                ev.data.fd=sock;
                                epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev);     //读成功关心其写事件 
                            }
                            else if(s==0)
                            {
                                printf("client quit!!!\n");
                                close(sock);
                                epoll_ctl(efds,EPOLL_CTL_DEL,sock,NULL);
                            }
                            else
                            {
                                perror("read");
                                continue;
                            }
                        }
                        else if(fds[i].events & EPOLLOUT)   //写事件就绪 
                        {
                            char* msg="I am server!!!\n";
                            ssize_t s=write(sock,msg,strlen(msg));
                            if(s<0)
                            {
                                perror("write");
                                continue;
                            }

                            ev.events=EPOLLIN;
                            ev.data.fd=sock;
                            epoll_ctl(efds,EPOLL_CTL_MOD,sock,&ev);   //写成功关心其读事件 
                        }

                    }
                }
                break;
        }
    }

    close(listen_sock);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值