Linux下C语言多路复用——epoll函数

本文详细介绍了Linux下的epoll函数,作为select和poll的增强版,epoll在大数据、高并发场景下表现出色。epoll克服了select的文件描述符数量限制和内存拷贝问题,采用边缘触发或水平触发方式。文章还解析了epoll_create、epoll_ctl和epoll_wait等关键函数及其工作原理。
摘要由CSDN通过智能技术生成

epoll函数用途比select和poll广,且功能强大,在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽

select的缺点:

  1. 单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫 描文件描述符,文件描述符数量越多,性能越差;
  2. 内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
  3. select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
  4. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调 用还是会将这些文件描述符通知进程。

poll函数使用链表保存文件描述符,因此没有了监视文件数量的限制,但是其它缺点依然存在。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它的优点在于能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,还有一个优点就是不需要每次都遍历所有的被监听的文件描述符集,只需要遍历正在被使用的文件描述符集就行了。

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

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

设计思路和函数解析:
epoll的设计和实现与select完全不同,epoll通过在Linux内核中申请一个简易的文件系统,把原先的select/poll调用分成了3 个部分:

  1. 调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)
  2. 调用epoll_ctl向epoll对象中添加连接的套接字
  3. 调用epoll_wait收集发生的事件的连接

epoll_create()函数:

#include <sys/epoll.h> 
int epoll_create(int size)

函数若成功返回文件描述符,若出错返回-1。
参数size指定了我们想要通过epoll实例来检查的文件描述符个数。

epoll_ctl()函数:

#include <sys/epoll.h> 
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *ev);

若成功返回0,若出错返回-1。

参数:
第一个参数epfd是epoll_create()的返回值;

第二个参数op用来指定需要执行的操作,它可以是如下几种值:
1,EPOLL_CTL_ADD:将描述符fd添加到epoll实例中的兴趣列表中去。对于fd上我们感兴趣的事件,都指定在ev所指向的 结构体中。如果我们试图向兴趣列表中添加一个已存在的文件描述符,epoll_ctl()将出现EEXIST错误;
2,EPOLL_CTL_MOD:修改描述符上设定的事件,需要用到由ev所指向的结构体中的信息。如果我们试图修改不在兴趣列表 中的文件描述符,epoll_ctl()将出现ENOENT错误;
3,EPOLL_CTL_DEL:将文件描述符fd从epfd的兴趣列表中移除,该操作忽略参数ev。如果我们试图移除一个不在epfd的兴 趣列表中的文件描述符,epoll_ctl()将出现ENOENT错误。关闭一个文件描述符会自动将其从所有的epoll实例的兴趣列表 移除;

第三个参数fd指明了要修改兴趣列表中的哪一个文件描述符的设定。该参数可以是代表管道、FIFO、套接字、POSIX消息队 列、inotify实例、终端、设备,甚至是另一个epoll实例的文件描述符。但是,这里fd不能作为普通文件或目录的文件描述符;

第四个参数ev是指向结构体epoll_event的指针,结构体的定义如下:

typedef union epoll_data 
{
       
    void        *ptr;    /* Pointer to user-defind data */    
    int          fd;    /* File descriptor */    
    uint32_t     u32;    /* 32-bit integer */    
    uint64_t     u64;    /* 64-bit integer */ 
} epoll_data_t;

struct epoll_event 
{
       
    uint32_t events; /* epoll events(bit mask) */    
    epoll_data_t data; /* User data */ 
};
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
可以使用epoll函数来实现高效的I/O多路复用,从而建立一个高性能的服务器。以下是一个简单的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAX_EVENTS 10 int main(int argc, char *argv[]) { int listen_fd, conn_fd, epoll_fd, nfds, n, i; struct sockaddr_in serv_addr, cli_addr; socklen_t cli_len = sizeof(cli_addr); struct epoll_event ev, events[MAX_EVENTS]; // 创建监听套接字 if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket error"); exit(EXIT_FAILURE); } // 设置地址重用 int optval = 1; setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)); // 绑定地址 memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(8080); if (bind(listen_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) { perror("bind error"); exit(EXIT_FAILURE); } // 监听 if (listen(listen_fd, 10) == -1) { perror("listen error"); exit(EXIT_FAILURE); } // 创建epoll实例 if ((epoll_fd = epoll_create1(0)) == -1) { perror("epoll_create1 error"); exit(EXIT_FAILURE); } // 添加监听套接字到epoll实例中 ev.events = EPOLLIN; ev.data.fd = listen_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) { perror("epoll_ctl error"); exit(EXIT_FAILURE); } // 循环等待事件 while (1) { nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait error"); exit(EXIT_FAILURE); } // 处理所有事件 for (i = 0; i < nfds; i++) { if (events[i].data.fd == listen_fd) { // 有新连接 conn_fd = accept(listen_fd, (struct sockaddr *)&cli_addr, &cli_len); if (conn_fd == -1) { perror("accept error"); exit(EXIT_FAILURE); } // 将新连接添加到epoll实例中 ev.events = EPOLLIN; ev.data.fd = conn_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) { perror("epoll_ctl error"); exit(EXIT_FAILURE); } } else { // 有数据可读 char buf[1024]; n = read(events[i].data.fd, buf, sizeof(buf)); if (n == -1) { perror("read error"); exit(EXIT_FAILURE); } else if (n == 0) { // 连接关闭 if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL) == -1) { perror("epoll_ctl error"); exit(EXIT_FAILURE); } close(events[i].data.fd); } else { // 回显数据 if (write(events[i].data.fd, buf, n) == -1) { perror("write error"); exit(EXIT_FAILURE); } } } } } return 0; } ``` 这个示例代码使用epoll实现了一个简单的回显服务器,可以在Linux下编译运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值