ITPub博客

首页 > Linux操作系统 > Linux操作系统 > epoll

epoll

原创 Linux操作系统 作者:liiinuuux 时间:2016-04-05 11:03:00 0 删除 编辑
poll和select相比最大优势在于可以管理更多的文件描述符。
epoll和poll相比最大的有时是速度更快,减少了大量文件描述符从内核到用户态的拷贝。

使用epoll的大体框架是
1 创建epoll instalce,得到epoll的文件描述符
调用epoll_create可以在内核创建一个epoll instance,返回这个instance的文件描述符。size参数已经被忽略了。
       int epoll_create(int size);
       int epoll_create1(int flags);
epoll_create1的参数flags值得一说。它可以取0或者FD_CLOEXEC。如果设置了FD_CLOEXEC,那么epoll_create1返回的文件描述符就会被字段设置FD_CLOEXEC标记。于是当进程以后调用fork的时候,epoll_create1所返回的文件描述符在子进程里是被关闭的,用不了。
例如
int epollfd = epoll_create1(0);

2 利用上面得到的epollfd,添加要监控的文件描述符
struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN|EPOLLET;
if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) goto err;
其中:
第一个参数是epoll_create返回的fd
第二个参数是要做的事情,包括:
     EPOLL_CTL_ADD
     EPOLL_CTL_MOD
     EPOLL_CTL_DEL
第三个参数是要监控的fd
第四个参数是struct epoll_event用来描述要监控哪些时间,以及当时间发生时传回哪些数据。

EPOLLIN 表示要监控文件描述符的可读性
epoll_event.data是一个union,除了设置fd,也可以当作void*来使用,执行自定义的数据。当文件描述符准备好时,epoll会把这个指针再“吐出来”。

3 设置好要监控的fd和事件后,就可以等待事件发生了。
struct epoll_event *evs;
evs = calloc(64, sizeof(ev));
while(1)
{
     int n = epoll_wait(epollfd, evs, 64, -1);
     ...
第一个参数是epoll_create返回的文件描述符
第二个参数是提供给epoll_wait的struct epoll_event数组,epoll会把准备好的文件描述符和时间,以及之前ADD时自定义的数放到这个数组里。
第三个参数是数组长度
第四个参数是超时时间
返回值n是此次检测到已就绪的文件描述符数量。之后可以依次取出evs[0]到evs[n-1]来处理。

4 处理得到的struct epoll_event数组
对于数组的每个成员,需要先检查文件描述符发生的什么事件,有没有错误产生。
if(e->events & EPOLLERR || e->events & EPOLLHUP )
....



epoll和poll都是检测文件描述符的缓冲区有没有数据,来判断文件描述符是否准备好了。
但是epoll的事件触发方式更灵活,有边缘触发(ET)和水平触发(LT)两种。
这两个概念和数字电路里的一样。
边缘触发: 当状态从0->1的瞬间触发一次。即缓冲区从空变成有数据的时候返回。
水平触发: 状态为1时始终触发。即只要缓冲区有数据,就会返回。

epoll的默认行为是水平触发,此时的行为和poll一直,就是只要缓冲区有数据没读完,调用epoll就会立刻返回。
边缘触发的性能更好,但是有个问题,就是只当缓冲期从空变成有数据的那一刻触发一次,之后再调用epoll,它就会阻塞,一直等待下一次缓冲区由空到非空的事件。
因此如果需要采用性能更好的边缘触发模式,需要做到以下两点:
1 只支持非阻塞文件描述符,也就是说必须设置NONBLOCK标记。
    int fl = 0;
    int r  = 0;
    if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;
    if((r  = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;

2 必须确保每一次触发后,都要处理完缓冲区的全部数据。
通常用一个while(1)来解决。
由于已经将文件描述符设置为NONBLOCK了,因此可以用死循环。
如果返回-1,需要需要判断如果errno是EAGAIN,则是正常的,说明缓冲区的数据处理完了。如果是其它的errno,就是真的出错了。
while(1)
{
    if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)
    {
        if(errno != EAGAIN) close(evs[i].data.fd);
        break;
    }
    else if (*buf == EOF)
    {
        close(evs[i].data.fd);
        break;
    }
...
...



例子:
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 
#include <sys/socket.h> 
#include <netdb.h> 
#include <fcntl.h> 
#include <sys/epoll.h> 
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

int check_epoll_err(struct epoll_event* ee)
{
    if(ee == NULL) return -1;
    if(
        ee->events & EPOLLERR ||
        ee->events & EPOLLHUP
    )
    {
        return -1;
    }
    return 1;
}


int no_block(int fd, char* errbuf)
{
    int fl = 0;
    int r  = 0;
    if((fl = fcntl(fd, F_GETFL, 0)) < 0) goto err;
    if((r  = fcntl(fd, F_SETFL, fl|O_NONBLOCK)) < 0) goto err;
    return 0;

    err:
    strcpy(errbuf, strerror(errno));
    return errno * -1;
}

int do_accept(int sock, char* errbuf)
{
    int ep = 0;
    int r = 0;
    struct epoll_event ev;
    struct epoll_event *evs;

    if(listen(sock, 1024) < 0) goto err;   
    if((ep = epoll_create1(0)) < 0) goto err;

    ev.data.fd = sock;
    ev.events = EPOLLIN|EPOLLET;
    if(epoll_ctl(ep, EPOLL_CTL_ADD, sock, &ev) < 0) goto err;

    evs = calloc(64, sizeof(ev));

    int n, i;
    struct sockaddr_in caddr;
    socklen_t addrlen;
    int cfd;
    while(1)
    {
        n = epoll_wait(ep, evs, 64, -1);
        for(i=0; i<n; i++)
        {
            if(check_epoll_err(&(evs[i])) < 0)
            {
                 close(evs[i].data.fd);
                continue;
            }
            else if(sock == evs[i].data.fd)
            {
                while(1) //loop try one non_block fd.
                {
                    if((cfd = accept(sock, (struct sockaddr*)&caddr, &addrlen)) < 0)
                    {
                        if(errno == EAGAIN || errno == EWOULDBLOCK)
                            break; // all connection accepted.
                        else
                            goto err;
                    }
                    printf("accept client: %s:%d\n", inet_ntoa(caddr.sin_addr), caddr.sin_port);

                    // make client fd non_block.
                    if((no_block(cfd, errbuf)) < 0) goto err;

                    // add client into epoll.
                    ev.data.fd = cfd;
                    ev.events = EPOLLIN|EPOLLET;
                    if(epoll_ctl(ep, EPOLL_CTL_ADD, cfd, &ev) < 0) goto err;
                    continue;
                }
                puts("accept ok");
            }
            else
            {
                // client fd ready.
                if(evs[i].events & EPOLLIN == EPOLLIN)
                {
                    int cnt;
                    char buf[BUFSIZ];
                    int finish = 0;
                    char hello[10] = "hello: ";
                    while(1)
                    {
                        if((cnt = read(evs[i].data.fd, buf, BUFSIZ)) < 0)
                        {
                            if(errno != EAGAIN) close(evs[i].data.fd);
                            break;
                        }
                        else if (*buf == EOF)
                        {
                            close(evs[i].data.fd);
                            break;
                        }
                        printf("from client: %s", buf);
                        if(write(evs[i].data.fd, hello, strlen(hello)) < 0) goto err;
                        if(write(evs[i].data.fd, buf, cnt) < 0) goto err;
                    }
                }
            }
        }
    }

    return 0;

    err:
    strcpy(errbuf, strerror(errno));
    return errno * -1;
}

int do_listen(int port, char* errbuf)
{
    int r = 0;
    int sock = -1;

    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    inet_pton(AF_INET, "0.0.0.0", &servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) goto err;
    else if((r = bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr))) != 0) goto err;

    int opt = 1;
    setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    // make client fd non_block.
    if((no_block(sock, errbuf)) < 0) goto err;
    return sock;

    err:
    strcpy(errbuf, strerror(errno));
    return errno * -1;
}



int main()
{
    int port = 1111;
    int r = 0;
    char errbuf[BUFSIZ];
    int sock = do_listen(port, errbuf);

    if(sock < 0)
    {
        puts(errbuf);
        exit(errno);
    }

    r = do_accept(sock, errbuf);
    if(r < 0)
    {
        puts(errbuf);
        exit(errno);
    }

    return 0;
}

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/26239116/viewspace-2075385/,如需转载,请注明出处,否则将追究法律责任。

上一篇: poll与socket
下一篇: 信号量
请登录后发表评论 登录
全部评论

注册时间:2012-11-12

  • 博文量
    94
  • 访问量
    309038