Linux epoll 用法

来源:互联网 发布:梨园淘宝城贵吗 编辑:程序博客网 时间:2024/06/05 06:05
前言
早期linux网络编程中,很长一段时间用的是 select 来监听事件。在linux 2.6内核中,已经有了一种新的机制,即epoll。和 select 模型对比,epoll 最大的好处在于它不会随着监听的文件描述符数目的增长而降低效率。因为在内核中的 select 实现中,它是采用轮询来处理的,轮询的文件描述符数目越多,自然耗时越多。并且在linux/posix_types.h 头文件有这样的声明:
#define __FD_SETSIZE 1024

这就表示 select 最多同时监听 1024 个文件描述符,虽然可以通过修改头文件再重编译内核来扩大这个数目,但这似乎然并卵。

epoll接口
往往简单的东西都是强大的,epoll 也是如此,其操作接口只有三个。
1. int epoll_create(int size);

这个函数创建一个 epoll 的句柄, size 用来告诉内核这个监听的数目一共有多大。需要注意的是,当创建好 epoll 句柄后,它会一直占用一个文件描述符,直到调用 close() 关闭才会释放。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
这个函数是 epoll 的事件注册函数,相当于告诉 epoll 你要监测什么样的事件。

第一个 参数是 epoll_create() 函数创建的句柄
第二个 参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件;
EPOLL_CTL_DEL:从 epfd 中删除一个fd;
第三个 参数是需要监听的 fd;
第四个 参数是告诉内核需要监听什么事, struct epoll_event 结构如下:
typedef union epoll_data {  
void* ptr;  
int fd;  
__uint32_t u32;  
__uint64_t u64;  
} epoll_data_t;  

struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events 可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读(包括对端正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将 EPOLL 设为边缘触发 (Edge Triggered) 模式,这是相对于水平触发 (Level Triggered) 来说的。
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于 select() 调用。参数 events 用来从内核得到事件的集合,maxevents 告诉内核这个 events 有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的 size ,参数 timeout 是超时时间(单位:毫秒, 0 会立即返回, -1 会一直阻塞)。该函数返回需要处理的事件数目,如返回 0 表示已超时。

EPOLL 事件有两种模型:
Edge Triggered (ET) 边缘触发,当缓冲区状态发生变更时才会触发。
Level Triggered (LT) 水平触发,只要缓冲区中有数据就会触发。
在这里我们暂时只讨论水平触发,这也是常用的模式。

参考代码
#include <stdio.h>#include <unistd.h>#include <errno.h>#include <sys/epoll.h>int main(int argc, char** argv){    int ep = epoll_create(1024);    if (ep < 0) {        printf("epoll_create failed\n");        return 1;    }    epoll_event event;    event.events = EPOLLIN;    event.data.fd = STDIN_FILENO;    //添加读事件    int status = epoll_ctl(ep, EPOLL_CTL_ADD, STDIN_FILENO, &event);    if (status < 0) {        printf("epoll_ctl failed\n");        return 1;    }    epoll_event events[1024];    for (;;) {        int nsd = epoll_wait(ep, events, 1024, -1);        if (nsd > 0) {            for (int i = 0; i < nsd; i++) {                if (events[i].data.fd == STDIN_FILENO) {                    char buf[100] = {0};                    read(events[i].data.fd, buf, 100);                    printf("STDIN: %s\n", buf);                }            }        }    }    return 0;}



结果:
[root@localhost epoll]# ./epoll
hello world
STDIN: hello world
原创粉丝点击