io多路复用之epoll

简介: io多路复用之epoll

1.什么是 epoll?

epoll是 Linux 内核提供的一种事件通知机制,用于管理大量文件描述符的I/O事件。它是目前在Linux系统上广泛使用的高性能事件驱动编程的关键组件之一,与传统的select和poll相比,epoll在处理大量并发连接时表现更出色。

2.epoll 原理

`epoll` 基于内核的事件通知机制,它的核心思想是:将文件描述符的状态变化注册到内核中,并通过回调机制通知应用程序。以下是`epoll`的核心原理:

1. 事件注册:

应用程序通过`epoll_ctl`系统调用将感兴趣的文件描述符(套接字)注册到`epoll`实例中。可以注册三种类型的事件:读、写、异常。

2. 事件通知:

当文件描述符上发生了注册的事件时,内核会将事件通知到`epoll`实例中。

3. 事件等待:

应用程序通过`epoll_wait`系统调用等待事件的发生。`epoll_wait`会阻塞进程,直到有事件发生或超时。

4. 事件处理:

一旦有事件发生,`epoll_wait`会返回,并将就绪的文件描述符以及事件类型传递给应用程序。

5. 应用程序处理事件:

应用程序处理事件并采取适当的操作。这可以是读取数据、写入数据、关闭连接等。

3.epoll 使用方法

基本的`epoll`使用方法:

1. 创建 epoll 实例:首先,需要创建一个`epoll`实例,使用`epoll_create`系统调用来完成。

int epollfd = epoll_create(1);

注意:

在使用epoll_create函数创建epoll实例时,参数的值通常被忽略,因此传递任何非负整数都可以,但这个值会影响epoll实例的大小,因此通常传递1或一个较小的正整数

在早期的Linux内核版本中,这个参数是用来指定epoll实例的大小的,它表示你希望监视的文件描述符的数量。但是自从Linux 2.6.8内核版本之后,内核会忽略这个参数,并会动态地调整epoll实例的大小以适应需要。

2. 注册事件:使用`epoll_ctl`来注册感兴趣的文件描述符和事件。

struct epoll_event event;
   event.events = EPOLLIN | EPOLLET; // 读事件,边缘触发模式
   event.data.fd = sockfd; // 要监视的文件描述符
   epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event);
  • EPOLL_CTL_ADD:用于向 epoll 实例中添加新的文件描述符并注册事件。
  • EPOLL_CTL_MOD:用于修改已经注册的文件描述符的事件。
  • EPOLL_CTL_DEL:用于删除已经注册的文件描述符。

3. 等待事件:使用`epoll_wait`来等待事件的发生。

struct epoll_event events[MAX_EVENTS];
   int num_events = epoll_wait(epollfd, events, MAX_EVENTS, timeout);

timeout参数:

阻塞模式:如果将 timeout 参数设置为负数,例如 -1,那么 epoll_wait 将会一直阻塞,直到有事件发生为止。

非阻塞模式:如果将 timeout 参数设置为 0,那么 epoll_wait 将会立即返回,不管文件描述符的状态如何。

超时模式:如果将 timeout 参数设置为一个正整数,例如 timeout 毫秒,那么 epoll_wait 将等待指定的毫秒数,然后返回。如果在超时前没有事件发生,它将返回 0 表示超时。

4. 处理事件:根据`epoll_wait`返回的事件,执行相应的操作。

for (int i = 0; i < num_events; i++) {
       if (events[i].events & EPOLLIN) {
           // 可读事件处理
       }
       if (events[i].events & EPOLLOUT) {
           // 可写事件处理
       }
       // 其他事件处理
   }

5. 关闭 epoll 实例:在程序结束时,不要忘记关闭`epoll`实例。

close(epollfd);

4.优点和适用场景

高性能:`epoll`适用于高并发的网络服务器,可以有效处理数千甚至数百万个连接。

效率:与`select`和`poll`相比,`epoll`在处理大量文件描述符时效率更高,因为它使用了事件通知机制,而不需要遍历整个文件描述符集合。

支持边缘触发(Edge Triggered)模式:`epoll`支持边缘触发模式,可以在事件发生时通知应用程序,而不是在文件描述符处于就绪状态时通知。

适用于非阻塞套接字:`epoll`特别适用于与非阻塞套接字一起使用,以避免阻塞操作。

5.结论

`epoll`是一个强大的事件驱动编程工具,对于构建高性能、高并发的网络应用程序非常有用。通过了解其原理和使用方法,开发者可以更好地利用`epoll`来处理大量的并发连接,提高应用程序的性能和可伸缩性。

边缘触发ET和水平触发LT的区别

边缘触发(Edge Triggered)和水平触发(Level Triggered)是两种不同的事件触发模式,用于描述在多路复用机制中文件描述符何时被认为是就绪的。

边缘触发(Edge Triggered):

1. 边缘触发模式仅在文件描述符的状态发生变化时通知应用程序,而不是在文件描述符处于就绪状态时持续通知。

2. 当文件描述符从未就绪变为就绪时,边缘触发模式触发事件通知。

3. 应用程序需要显式处理事件,确保不错过任何事件,因为一旦事件触发,它不会被持续触发,除非文件描述符的状态再次发生变化。

4. 边缘触发通常需要更高的处理复杂性,因为应用程序需要追踪文件描述符的状态变化。

5. 适用于高性能应用,可以减少事件通知的频率,从而减轻处理压力。

水平触发(Level Triggered):

1. 水平触发模式在文件描述符处于就绪状态时持续通知应用程序。

2. 只要文件描述符仍然就绪,就会持续触发事件通知。

3. 应用程序不需要主动处理事件,只要文件描述符仍然就绪,事件通知会持续发生。

4. 水平触发通常更容易使用,因为应用程序无需追踪状态变化,只需要处理已就绪的文件描述符。

5. 可能导致频繁的事件通知,需要谨慎处理以避免性能问题。

选择边缘触发还是水平触发取决于应用程序的需求和性能考虑。边缘触发适用于需要高性能且需要精确追踪状态变化的情况,而水平触发适用于更简单的应用程序,可以更容易地处理事件通知。

学习demo:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<fcntl.h>
#include<sys/epoll.h>
#define PORT 8848
#define POLL_SIZE 1024
#define BUFLEN 128
#define EVENT_SIZE 1024
int main(){
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(struct sockaddr_in));
    serveraddr.sin_family=AF_INET;
    serveraddr.sin_port=htons(PORT);
    serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    if(-1==bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))){
        printf("bind failed\n");
        exit(1);
    }
    listen(sockfd,10);
    int epfd=epoll_create(1);//历史原因,参数大于0即可
    struct epoll_event ev;
    ev.events=POLLIN;
    ev.data.fd=sockfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev); 
    struct epoll_event events[EVENT_SIZE]={0};
    while(1){
        int nready=epoll_wait(epfd,events,EVENT_SIZE,-1);//-1:阻塞 0:非阻塞 >0:等待固定时间
        //printf("nready:%d\n",nready);
        int i=0;
        for(;i<nready;i++){
            int connfd=events[i].data.fd;
            //printf("connfd:%d\n",connfd);
            if(sockfd==connfd){
                struct sockaddr_in clientaddr;
                socklen_t len=sizeof(clientaddr);
                int clientfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len);
                if(clientfd==-1){
                    printf("accept failed\n");
                    continue;
                }
                printf("new client:%d\n",clientfd);
                ev.events=EPOLLIN;
                ev.data.fd=clientfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
            }
            else if(events[i].events&EPOLLIN){
                char buffer[BUFLEN]={0};
                int ret=recv(connfd,buffer,BUFLEN-1,0);
                if(ret==0){
                    printf("client:%d closed\n",connfd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,connfd,NULL);
                    close(connfd);
                    continue;
                }
                else if(ret>0){
                    buffer[ret]='\0';
                    printf("recv from client:%d  %s\n",connfd,buffer);
                    send(connfd,buffer,strlen(buffer),0);
                }
            }
        }
    }
    exit(0);
}
目录
相关文章
|
2月前
|
网络协议 安全 Linux
Linux C/C++之IO多路复用(select)
这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。
95 0
|
2月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
91 1
Linux C/C++之IO多路复用(aio)
|
2月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
28 0
Linux C/C++之IO多路复用(poll,epoll)
|
2月前
|
Java Linux
【网络】高并发场景处理:线程池和IO多路复用
【网络】高并发场景处理:线程池和IO多路复用
47 2
|
2月前
|
监控 网络协议 Java
IO 多路复用? 什么是 IO 多路复用? 简单示例(日常生活)来解释 IO 多路复用 一看就懂! 大白话,可爱式(傻瓜式)教学! 保你懂!
本文通过日常生活中的简单示例解释了IO多路复用的概念,即一个线程通过监控多个socket来处理多个客户端请求,提高了效率,同时介绍了Linux系统中的select、poll和epoll三种IO多路复用的API。
132 2
|
3月前
|
消息中间件 NoSQL Java
面试官:谈谈你对IO多路复用的理解?
面试官:谈谈你对IO多路复用的理解?
49 0
面试官:谈谈你对IO多路复用的理解?
|
3月前
|
网络协议 Java Linux
高并发编程必备知识IO多路复用技术select,poll讲解
高并发编程必备知识IO多路复用技术select,poll讲解
|
5月前
|
安全 Java Linux
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
173 1
|
5月前
|
缓存 网络协议 算法
【Linux系统编程】深入剖析:四大IO模型机制与应用(阻塞、非阻塞、多路复用、信号驱动IO 全解读)
在Linux环境下,主要存在四种IO模型,它们分别是阻塞IO(Blocking IO)、非阻塞IO(Non-blocking IO)、IO多路复用(I/O Multiplexing)和异步IO(Asynchronous IO)。下面我将逐一介绍这些模型的定义:
232 2
|
5月前
|
存储 Java Unix
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。