IO多路转接之epoll详解

关于对epoll的概念我们可以参考《Linux高性能服务器编程》:epoll是特有的IO复用函数。它在实现上和select、poll有很大差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户所关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集(轮询)。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表

关于epoll,它是用三个系统调用来实现的。
int epoll_create(int size);
返回一个文件描述符,这个描述符标识内核中的事件表,其他epoll的系统调用都要通过这个文件描述符来实现。size参数现在并不起作用,只是给内核一个提示,事件表需要多大。
我们有了事件表,并且能对之操作之后,需要注册事件,用到系统调用 int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
epfd即是事件表对应的文件描述符(epoll_create返回的文件描述符)
op表示操作类型: EPOLL_CTL_ADD添加事件、EPOLL_CTL_MOD修改事件、EPOLL_CTL_DEL删除事件。
fd即为要操作的描述符。
event参数代表事件,它是一个epoll_event结构体类型

IO多路转接之epoll详解_第1张图片

IO多路转接之epoll详解_第2张图片
结构体epoll_event有两个成员,events代表事件类型,data则是事件的数据。同样的,data是一个联合体类型,这个联合体类型使用最多的是fd,代表事件所从属的目标文件描述符。
events可以是以下几个宏的集合:
IO多路转接之epoll详解_第3张图片

epoll_ctl成功返回0,失败返回-1并设置errno。

int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
关于该函数的参数,epfd就是内核中事件表对应的文件描述符(即epoll_create返回的文件描述符);maxevents代表最多监听多少个事件,必须大于0;timeout代表设置的超时时间,如果设置为0代表立即返回,-1代表永久阻塞直到事件就绪,单位为毫秒。
对于events参数,如果epoll_wait检测到事件就绪,将所有的就绪事件从内核事件表中复制到events指向的结构体数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,也用于输出内核检测到的就绪事件,这样极大提高了效率。

epoll工作原理:

在我们调用epoll_create的时候,内核会帮我们创建一个eventpoll结构体(与上面讲过的event_poll不是一回事),这个结构体中有两个成员与epoll工作原理密切相关,分别是一个红黑树结构和一个链表结构。
 

struct eventpoll{ 
 .... 
 /*红⿊树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ 
 struct rb_root rbr; 
 /*双链表中则存放着将要通过epoll_wait返回给⽤户的满⾜条件的事件*/ 
 struct list_head rdlist; 
 .... 
};

每一个epoll对象都有一个epollevent结构体,用于存放通过epoll_ctl向epoll对象中添加进来的事件,这些事件被内核存放到红黑树中,如此,重复添加的事件就可以通过红⿊树⽽⾼效的识别出来。在内核里除了创建了一个红黑树来存放事件之外,还会创建一个链表,用于存储准备就绪的事件,当epoll_wait调用时,观察这个链表中有没有数据即可,如果有数据就将事件复制到用户态,同时将事件数量返回给用户。
那么上述过程在系统中是怎么维护的呢?
内核不仅会将事件存放到红黑树中,还会将所有的epoll对象与设备驱动程序(中断处理程序)建立回调关系(注册回调函数),当一个事件的中断到达,就把事件存放到链表中。所以当一个事件就绪时,内核把网卡上的数据拷贝到内核中后就把事件存放到就绪链表中,这样,一棵红黑树,一个链表就解决了大量并发的描述符问题。

IO多路转接之epoll详解_第4张图片

epoll的两种工作模式:

水平触发模式(LT)和边缘触发模式(ET)。
假如有这样⼀个例⼦:
我们已经把⼀个tcp socket添加到epoll描述符,这个时候socket的另⼀端被写⼊了2KB的数据,调⽤epoll_wait,并且它会返回,说明它已经准备好读取操作,然后调⽤read, 只读取了1KB的数据,继续调⽤epoll_wait。
⽔平触发:
epoll默认状态下就是LT⼯作模式.
当epoll检测到socket上事件就绪的时候, 可以不⽴刻进⾏处理. 或者只处理⼀部分.
如上⾯的例⼦, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第⼆次调⽤ epoll_wait 时,epoll_wait 仍然会⽴刻返回并通知socket读事件就绪。直到缓冲区上所有的数据都被处理完, epoll_wait 才不会⽴刻返回.
⽀持阻塞读写和⾮阻塞读写。
边缘触发Edge Triggered⼯作模式
如果我们在第1步将socket添加到epoll描述符的时候使⽤了EPOLLET标志, epoll进⼊ET⼯作模式.
当epoll检测到socket上事件就绪时, 必须⽴刻处理.
如上⾯的例⼦, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第⼆次调⽤ epoll_wait 的时候, epoll_wait 不会再返回了。也就是说, ET模式下, ⽂件描述符上的事件就绪后, 只有⼀次处理机会。
ET的性能⽐LT性能更⾼( epoll_wait 返回的次数少了很多). Nginx默认采⽤ET模式使⽤epoll.
只⽀持⾮阻塞读写。
ET模式下数据就绪只会通知一次,也就是说我们必须一直读取数据,直到读完或者出错。(在tcp服务器中可以用循环读取来实现)

用epoll编写tcp服务器,实现单线程处理多个客户端数据请求。
LT模式:

#include
#include
#include
#include
#include
#include
#include

int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        printf("usage: ./name port");
    }
    int i,j;
    struct sockaddr_in server_addr;
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(atoi(argv[1]));
    server_addr.sin_addr.s_addr=INADDR_ANY;
    socklen_t len=sizeof(server_addr);
    int listen_fd=socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd<0)
    {
        perror("create socket error!");
        return 1;
    }
    int ret=bind(listen_fd,(struct sockaddr*)&server_addr,len);
    if(ret<0)
    {
        perror("bind error!");
        return 1;
    }
    ret=listen(listen_fd,5);
    if(ret<0)
    {
        perror("listen error!");
        return 1;
    }
    int epoll_fd=epoll_create(10);
    if(epoll_fd<0)
    {
        perror("epoll create error!");
        return 1;
    }
    struct epoll_event event;
    event.data.fd=listen_fd;
    event.events=EPOLLIN;
    ret=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&event);
    if(ret<0)
    {
        perror("epoll ctl error!");
        return 1;
    }
    while(1)
    {
        struct epoll_event events[10];
        int size=epoll_wait(epoll_fd,events,10,-1);
        if(size<0)
        {
            perror("epoll wait error!");
            continue;
        }
        if(size==0)
        {
            printf("time out!\n");
            continue;
        }
        printf("sockfd ready!\n");
        for(i=0;i

ET模式

#include
#include
#include
#include
#include
#include
#include
#include
#include

void SetNonblock(int fd)
{
    int ret=fcntl(fd,F_GETFL);
    if(ret<0)
    {
        perror("fcntl error!");
        return;
    }
    fcntl(fd,F_SETFL,ret|O_NONBLOCK);
}
int loop_recv(int fd,char buff[])
{
    int total_size=0,cur_size=0;
    while(1)
    {
        cur_size=recv(fd,buff+total_size,1024,0);
        total_size+=cur_size;
        if(cur_size<1024||errno==EAGAIN)
        {
            break;
        }
    }
    buff[total_size]=='\0';
    return total_size;
}
int main(int argc,char* argv[])
{
    int i;
    if(argc!=2)
    {
        printf("Usage: ./name port!\n");
        return 1;
    }
    struct sockaddr_in server_addr;
    socklen_t len=sizeof(server_addr);
    server_addr.sin_family=AF_INET;
    server_addr.sin_port=htons(atoi(argv[1]));
    server_addr.sin_addr.s_addr=INADDR_ANY;
    int listen_fd=socket(AF_INET,SOCK_STREAM,0);
    if(listen_fd<0)
    {
        perror("create socket error!");
        return 1;
    }
    int ret=bind(listen_fd,(struct sockaddr*)&server_addr,len);
    if(ret<0)
    {
        perror("bind error!");
        return 1;
    }
    ret=listen(listen_fd,5);
    if(ret<0)
    {
        perror("listen error!");
        return 1;
    }
    int epoll_fd=epoll_create(10);
    if(epoll_fd<0)
    {
        perror("create epoll error!");
        return 1;
    }
    SetNonblock(listen_fd);
    struct epoll_event event;
    event.data.fd=listen_fd;
    event.events=EPOLLIN|EPOLLET;
    ret=epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&event);
    if(ret<0)
    {
        perror("epoll ctl listen fd error!");
        return 1;
    }
    while(1)
    {
        struct epoll_event events[10];
        int size=epoll_wait(epoll_fd,events,10,-1);
        if(size<0)
        {
            perror("epoll wait error!");
            continue;
        }
        else if(size==0)
        {
            printf("time out!\n");
            continue;
        }
        for(i=0;i

 

你可能感兴趣的:(网络,Linux)