epoll函数

本文详细介绍了epoll的三个核心函数epoll_create、epoll_ctl和epoll_wait,并通过一个服务器代码示例展示了epoll的实际应用。此外还对比了epoll与select/poll的主要区别,包括支持大量文件描述符、提高I/O效率、使用mmap加速消息传递等优势。

转自:
http://blog.youkuaiyun.com/penzo/article/details/5986574

epoll的几个函数的介绍。

1、epoll_create函数

/**  
 * @brief    该函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。 
 *  
 * @param    size    size就是你在这个epoll fd上能关注的最大socket fd数 
 *  
 * @return   生成的文件描述符 
 */  
int epoll_create(int size);  

 

2、epoll_ctl函数

/**  
 * @brief    该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。 
 *  
 * @param    epfd    由 epoll_create 生成的epoll专用的文件描述符 
 * @param    op      要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除 
 * @param    fd      关联的文件描述符 
 * @param    event   指向epoll_event的指针 
 *  
 * @return   0       succ 
 *           -1      fail 
 */  
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  

其中用到的数据结构结构如下:
op值:
EPOLL_CTL_ADD:注册新的fd到epfd中;

EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;

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 */
};

常用的事件类型:
EPOLLIN :表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 表示对应的文件描述符有事件发生;

例:

struct epoll_event ev;  
//设置与要处理的事件相关的文件描述符  
ev.data.fd=listenfd;  
//设置要处理的事件类型  
ev.events=EPOLLIN|EPOLLET;  
//注册epoll事件  
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); 






3、epoll_wait函数

 

/**  
 * @brief    该函数用于轮询I/O事件的发生 
 *  
 * @param    epfd        由epoll_create 生成的epoll专用的文件描述符 
 * @param    events      用于回传代处理事件的数组 
 * @param    maxevents   每次能处理的事件数 
 * @param    timeout     等待I/O事件发生的超时值;-1相当于阻塞,0相当于非阻塞。一般用-1即可 
 *  
 * @return   >=0         返回发生事件数 
 *           -1          错误 
 */  
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);  


好了,其实在epoll的使用中无非就用到了上面介绍的几个函数,下面贴一段用epoll实现的服务器代码:

 

#include <stdio.h>  
#include <stdlib.h>  
#include <errno.h>  
#include <string.h>  
#include <sys/types.h>  
#include <netinet/in.h>  
#include <sys/socket.h>  
#include <sys/wait.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <openssl/ssl.h>  
#include <openssl/err.h>  
#include <fcntl.h>  
#include <sys/epoll.h>  
#include <sys/time.h>  
#include <sys/resource.h>  
#define MAXBUF 1024  
#define MAXEPOLLSIZE 10000  
/* 
   setnonblocking - 设置句柄为非阻塞方式 
   */  
int setnonblocking(int sockfd)  
{  
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1)  
    {  
        return -1;  
    }  
    return 0;  
}  
/* 
   handle_message - 处理每个 socket 上的消息收发 
   */  
int handle_message(int new_fd)  
{  
    char buf[MAXBUF + 1];  
    int len;  
    /* 开始处理每个新连接上的数据收发 */  
    bzero(buf, MAXBUF + 1);  
    /* 接收客户端的消息 */  
    len = recv(new_fd, buf, MAXBUF, 0);  
    if (len > 0)  
    {  
        printf("%d接收消息成功:'%s',共%d个字节的数据/n",  
             new_fd, buf, len);  
    }  
    else  
    {  
        if (len < 0)  
            printf  
                ("消息接收失败!错误代码是%d,错误信息是'%s'/n",  
                 errno, strerror(errno));  
        close(new_fd);  
        return -1;  
    }  
    /* 处理每个新连接上的数据收发结束 */  
    return len;  
}  
int main(int argc, char **argv)  
{  
    int listener, new_fd, kdpfd, nfds, n, ret, curfds;  
    socklen_t len;  
    struct sockaddr_in my_addr, their_addr;  
    unsigned int myport, lisnum;  
    struct epoll_event ev;  
    struct epoll_event events[MAXEPOLLSIZE];  
    struct rlimit rt;  
    myport = 5000;  
    lisnum = 2;   
    /* 设置每个进程允许打开的最大文件数 */  
    rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;  
    if (setrlimit(RLIMIT_NOFILE, &rt) == -1)   
    {  
        perror("setrlimit");  
        exit(1);  
    }  
    else   
    {  
        printf("设置系统资源参数成功!/n");  
    }  
    /* 开启 socket 监听 */  
    if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1)  
    {  
        perror("socket");  
        exit(1);  
    }  
    else  
    {  
        printf("socket 创建成功!/n");  
    }  
    setnonblocking(listener);  
    bzero(&my_addr, sizeof(my_addr));  
    my_addr.sin_family = PF_INET;  
    my_addr.sin_port = htons(myport);  
    my_addr.sin_addr.s_addr = INADDR_ANY;  
    if (bind(listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr)) == -1)   
    {  
        perror("bind");  
        exit(1);  
    }   
    else  
    {  
        printf("IP 地址和端口绑定成功/n");  
    }  
    if (listen(listener, lisnum) == -1)   
    {  
        perror("listen");  
        exit(1);  
    }  
    else  
    {  
        printf("开启服务成功!/n");  
    }  
    /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */  
    kdpfd = epoll_create(MAXEPOLLSIZE);  
    len = sizeof(struct sockaddr_in);  
    ev.events = EPOLLIN | EPOLLET;  
    ev.data.fd = listener;  
    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0)   
    {  
        fprintf(stderr, "epoll set insertion error: fd=%d/n", listener);  
        return -1;  
    }  
    else  
    {  
        printf("监听 socket 加入 epoll 成功!/n");  
    }  
    curfds = 1;  
    while (1)   
    {  
        /* 等待有事件发生 */  
        nfds = epoll_wait(kdpfd, events, curfds, -1);  
        if (nfds == -1)  
        {  
            perror("epoll_wait");  
            break;  
        }  
        /* 处理所有事件 */  
        for (n = 0; n < nfds; ++n)  
        {  
            if (events[n].data.fd == listener)   
            {  
                new_fd = accept(listener, (struct sockaddr *) &their_addr,&len);  
                if (new_fd < 0)   
                {  
                    perror("accept");  
                    continue;  
                }   
                else  
                {  
                    printf("有连接来自于: %d:%d, 分配的 socket 为:%d/n",  
                            inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);  
                }  
                setnonblocking(new_fd);  
                ev.events = EPOLLIN | EPOLLET;  
                ev.data.fd = new_fd;  
                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0)  
                {  
                    fprintf(stderr, "把 socket '%d' 加入 epoll 失败!%s/n",  
                            new_fd, strerror(errno));  
                    return -1;  
                }  
                curfds++;  
            }   
            else  
            {  
                ret = handle_message(events[n].data.fd);  
                if (ret < 1 && errno != 11)  
                {  
                    epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);  
                    curfds--;  
                }  
            }  
        }  
    }  
    close(listener);  
    return 0;  
}  



转载:http://www.vimer.cn/2009/11/epoll%E4%BD%BF%E7%94%A8%E5%AE%9E%E4%BE%8B%E8%AF%B4%E6%98%8E.html

 

epoll与select/poll的区别

1.支持一个进程打开大数目的socket描述符(FD)

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显 然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完 美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。

2.IO效率不随FD数目增加而线性下降

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但 是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操 作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相 反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.使用mmap加速内核与用户空间的消息传递

这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很 重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。

4.内核微调

这一点其实不算epoll的优点了,而是整个linux平台的优点。也许 你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可 以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手 的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网 卡驱动架构LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表. 

epoll工作模式

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。

ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。 

epoll工作在LT模式下和传统的poll/select一模一样的,epoll,select和poll的fd集里面的fd是可以阻塞或者非阻塞的方式来访问fd。但是epoll工作在ET模式的时候就只能工作在非阻塞模式下面了。

 

转载:http://blog.chinaunix.net/u2/76430/showart_1275186.html 


<think>我们正在讨论Linux中的epoll函数。用户要求了解epoll的使用方法、原理以及详细解释。根据提供的引用,我们可以总结如下:epollLinux中用于I/O多路复用的机制,与select和poll相比,它在处理大量文件描述符时具有更高的效率。epoll通过三个系统调用实现:`epoll_create`、`epoll_ctl`和`epoll_wait`。 1. **`epoll_create`**:创建一个epoll实例,返回一个文件描述符。参数`size`在较新的内核中已忽略,但必须大于0(为了兼容性)。 2. **`epoll_ctl`**:用于向epoll实例中添加、修改或删除要监视的文件描述符及其事件。参数包括:- `epfd`: `epoll_create`返回的文件描述符。- `op`:操作类型(添加、修改、删除),分别对应`EPOLL_CTL_ADD`、`EPOLL_CTL_MOD`、`EPOLL_CTL_DEL`。- `fd`:要操作的文件描述符(如要监视的socket)。- `event`:指向`epoll_event`结构的指针,指定要监视的事件和关联的数据。 3. **`epoll_wait`**:等待事件的发生。参数包括:- `epfd`: epoll实例的文件描述符。-`events`:用于返回发生事件的数组。-`maxevents`:数组的最大长度(一次最多返回的事件数)。-`timeout`:超时时间(毫秒),-1表示阻塞,0表示立即返回,>0表示超时时间。 epoll的原理: -使用红黑树来存储要监视的文件描述符,这样在添加、删除和查找操作时效率较高(O(log n))。-使用就绪链表来存储已经就绪的事件,当调用`epoll_wait`时,只需检查这个链表是否为空即可,而不需要遍历所有文件描述符。-当有事件发生时,内核会通过回调函数将对应的文件描述符加入到就绪链表中,因此`epoll_wait`返回时只需要将链表中的事件复制到用户空间即可。epoll有两种工作模式:- **水平触发(LT)**:默认模式。只要文件描述符处于就绪状态(如读缓冲区非空,写缓冲区有空位),`epoll_wait`就会返回该事件。如果用户没有处理完数据,下次调用`epoll_wait`时还会再次通知。- **边缘触发(ET)**:只有状态变化时才会通知。用户必须一次处理完所有数据,因为后续不会再通知,直到有新的状态变化。ET模式效率更高,但需要非阻塞I/O并循环读取直到EAGAIN错误。根据引用[4],epoll反应堆模型是一种更高级的使用方式,它通过回调机制将事件处理逻辑与事件监视分离,提高程序的可扩展性。 接下来,我们将按照用户要求详细解释epoll的使用方法、原理,并给出示例代码。注意:在回答中,我们会引用提供的引用内容,并在引用段落末尾添加引用标识。</think>### Linux epoll 函数详解 #### 一、epoll 核心原理 epollLinux 高效 I/O 多路复用机制,其核心优势在于: 1. **红黑树管理描述符** - 使用红黑树存储监控的 fd($O(\log n)$ 时间复杂度) - 突破 select/poll 的 fd 数量限制(默认上限约 10 万)[^1][^3] 2. **就绪链表机制** - 内核通过回调函数将就绪 fd 加入链表 - `epoll_wait` 直接获取就绪链表,无需遍历所有 fd[^4] 3. **内存共享** - 内核与用户空间共享内存区域,避免数据拷贝[^2] #### 二、核心函数详解 ```c #include <sys/epoll.h> ``` 1. **`epoll_create(int size)`** - 创建 epoll 实例,返回 epoll 文件描述符 - `size`:内核事件表初始大小(Linux 2.6.8 后自动调整) - 示例:`int epfd = epoll_create(1);` 2. **`epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)`** - 操作事件监听表: | 操作类型 | 宏定义 | 功能 | |------------------|---------------------|--------------------------| | 添加事件 | `EPOLL_CTL_ADD` | 注册新 fd 到红黑树 | | 修改事件 | `EPOLL_CTL_MOD` | 修改已注册 fd 的监听事件 | | 删除事件 | `EPOLL_CTL_DEL` | 从红黑树中删除 fd | - `epoll_event` 结构: ```c typedef union epoll_data { void *ptr; int fd; // 目标文件描述符 uint32_t u32; uint64_t u64; } epoll_data_t; struct epoll_event { uint32_t events; // 监听事件集合 epoll_data_t data; // 用户数据 }; ``` - 事件标志: - `EPOLLIN`:数据可读 - `EPOLLOUT`:数据可写 - `EPOLLET`:边缘触发模式(默认水平触发) 3. **`epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)`** - 等待事件就绪: - `events`:输出参数,存储就绪事件数组 - `maxevents`:数组最大容量 - `timeout`:-1(阻塞)/0(非阻塞)/>0(超时毫秒) - 返回值:就绪 fd 数量 #### 三、工作模式对比 | 特性 | 水平触发(LT) | 边缘触发(ET) | |-----------------|-----------------------------------|-------------------------------------| | 触发条件 | 缓冲区有数据即触发 | 仅当 fd 状态变化时触发 | | 事件处理 | 可分批处理数据 | 必须一次性处理完所有数据 | | 代码复杂度 | 较低 | 需非阻塞 I/O + 循环读取 | | 性能 | 一般 | 更高(减少 epoll_wait 调用次数) | | 默认模式 | ✓ | 需显式设置 `EPOLLET` | **ET 模式关键代码**: ```c // 非阻塞读取直到 EAGAIN while((n = read(fd, buf, BUF_SIZE)) > 0) { process_data(buf, n); } if (n == -1 && errno == EAGAIN) { // 数据已读完 } ``` #### 四、epoll 反应堆模型 高级用法将事件处理抽象为: 1. 事件监听与回调分离 2. 事件触发时自动调用预设回调函数 3. 通过 `epoll_data.ptr` 携带处理上下文[^4] ```c struct event_handler { int fd; void (*callback)(int fd, void* arg); }; // 注册回调 event_handler* h = malloc(sizeof(event_handler)); h->fd = sockfd; h->callback = &read_callback; ev.data.ptr = h; epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); ``` #### 五、性能对比 $$ \text{时间复杂度} = \begin{cases} O(n) & \text{select/poll} \\ O(1) & \text{epoll(就绪 fd 比例恒定时)} \end{cases} $$ 当活跃连接数占比 $k$ 时,epoll 时间复杂度为 $O(k \cdot \log n)$,在 $k \ll n$ 时优势显著[^1][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值