select,poll,epoll的区别以及使用方法

I/O多路复用是指:通过一种机制,可以 监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

原生socket客户端在与服务端建立连接时,即服务端调用accept方法时是阻塞的,同时服务端和客户端在收发数据(调用recv、send、sendall)时也是阻塞的。原生socket服务端在同一时刻只能处理一个客户端请求,即服务端不能同时与多个客户端进行通信,实现并发,导致服务端资源闲置(此时服务端只占据 I/O,CPU空闲)。

如果我们的需求是要让多个客户端连接至服务器端,而且服务器端需要处理来自多个客户端请求。很明显,原生socket实现不了这种需求,此时我们使用I/O多路复用机制就可以实现这种需求,可以同时监听多个文件描述符,一旦描述符就绪,能够通知程序进行相应的读写操作。

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大 量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候 将 再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描 述符数量的 值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在 系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll 中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某 个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

最终调用epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);函数等待事件到来,返回值是需要处理的事件数目,events表示要处理的事件集合。

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在”醒着”的时候要遍历整个fd集合,而epoll在”醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

epoll的接口非常简单,一共就三个函数。

/*
size:在 Linux最新的一些内核版本的实现中,这个 size参数没有任何意义。
返回值:返回值为一个文件描述符,作为后面两个函数的参数
*/
int epoll_create(int size)

此函数可以在内核中创建一个内核事件表,通过返回的内核事件表来管理

/*
epfd:操作内核时间表的文件描述符,即epoll_create函数的返回值
op:操作内核时间表的方式
    EPOLL_CTL_ADD(向内核时间表添加文件描述符,即注册);
    EPOLL_CTL_MOD(修改内核事件表事件);
    EPOLL_CTL_DEL (删除内核事件表中的事件);
fd:操作的文件描述符
event:指向struct epoll_event的指针
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

poll的事件注册函数,epoll_ctl向 epoll对象中添加、修改或者删除感兴趣的事件,返回0表示成功,否则返回–1,此时需要根据errno错误码判断错误类型。

event结构

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

typedef union epoll_data
{
    void *ptr;
    int fd;
    _uint32_t u32;
    _uint64_t u64;
}epoll_data_t;
/*
epfd:同上面函数
events:用于接收内核返回的就绪事件的数组
maxevents:用户最多能处理的事件个数
等待I/O的超时值(后面的编程设为-1,表示永不超时),单位为ms
返回值,指的是就绪事件的个数
*/
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表示已超时。如果返回–1,则表示出现错误,需要检查 errno错误码判断错误类型。

下面通过一个echo回射服务器的客户端和服务端案例介绍epoll的使用方法

服务端事件poll

    int epollFd;
    struct epoll_event events[MAX_EVENTS];
    int ret;
    char buf[MAXSIZE];
    memset(buf,0,MAXSIZE);
    //创建一个epoll描述符,通过这个描述管理多个描述符
    epollFd = epoll_create(FDSIZE);
    //添加监听描述符事件
    add_event(epollFd,listenFd,EPOLLIN);
    while(1){
        //获取已经准备好的描述符事件,阻塞
        ret = epoll_wait(epollFd, events, MAX_EVENTS,-1);
        //处理事件,ret是发生的事件个数
        handle_events(epollFd,events,ret,listenFd,buf);
    }
    close(epollFd);

客户端事件poll

    int                 sockfd;
    struct sockaddr_in  servaddr;
    sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = inet_addr(IPADDRESS);
    printf("start\n");
    if(connect(sockfd,(struct sockaddr*)&servaddr, sizeof(sockaddr_in)) < 0){
        perror("connect err: ");
        return 0;
    }
    else{
        printf("connect succ\n");
    }
    //处理连接
    handle_connection(sockfd);
    close(sockfd);
    return 0;

程序运行结果

客户端

./cli
start
connect succ
cli hello
epollfd 4, rdfd 0, sockfd 3, read 10
epollfd 4, wrfd 3, sockfd 3, write 10
epollfd 4, rdfd 3, sockfd 3, read 10
cli hello
epollfd 4, wrfd 1, sockfd 3, write 10
cli over
epollfd 4, rdfd 0, sockfd 3, read 9
epollfd 4, wrfd 3, sockfd 3, write 9
epollfd 4, rdfd 3, sockfd 3, read 9
cli over
epollfd 4, wrfd 1, sockfd 3, write 9
^C

服务端

./srv accept a new client: 127.0.0.1:37098, fd = 5read fd=5, num read=10read message is : cli hellowrite fd=5, num write=10read fd=5, num read=9read message is : cli overwrite fd=5, num write=9read fd=5, num read=0client close.^C

程序源代码详见公众号 xutopia77 的文章 《select,poll,epoll的区别以及使用方法》

Original: https://www.cnblogs.com/xutopia/p/15808573.html
Author: xutopia
Title: select,poll,epoll的区别以及使用方法

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612240/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • linux系统编码修改

    查看当前系统默认采用的字符集locale 查看系统当前编码echo $LANG如果输出为:en_US.UTF-8 英文zh_CN.UTF-8 中文 查看系统是否安装中文字符集loc…

    Linux 2023年6月6日
    099
  • 哨兵+redis主从部署架构-docker部署

    架构图 哨兵的介绍 sentinel , 中文是哨兵。 哨兵是redis 集群架构中非常重要的一个组件,主要功能如下: (1)集群监控:负责监控reidis master 和sla…

    Linux 2023年5月28日
    088
  • IDEA对数据库、表、记录的(增删改查可视化操作)、数据库安全性问题的演示

    演示脏读 一个事物里面读到了另外一个事物没有提交的数据: read uncommitted 1.开启A,B窗口 2.分别查询A,B的隔离级别 select @@tx_isolati…

    Linux 2023年6月6日
    0102
  • CH9344 Windows驱动安装与GPIO使用教程

    USB 转四串口芯片 CH9344 用于为 USB 主机扩展 4 路高速异步串口,支持串口波特率高达 12Mbps。芯片内部高度集成,外围精简,提供 VIO 电源引脚,部分串口 I…

    Linux 2023年6月7日
    089
  • 视图层

    目录 虚拟环境 django路由层版本区别 视图函数返回值 JsonResponse对象 上传文件 request方法 FBV与CBV CBV源剖析 模板语法传值 内容 虚拟环境 …

    Linux 2023年6月7日
    077
  • 微服务网关Gateway实践总结

    有多少请求,被网关截胡; 一、Gateway简介 微服务架构中,网关服务通常提供动态路由,以及流量控制与请求识别等核心能力,在之前的篇幅中有说过Zuul组件的使用流程,但是当下Ga…

    Linux 2023年6月14日
    0109
  • date 参数(option)-d

    记录这篇博客的原因是:鸟哥的linux教程中,关于date命令的部分缺少-d这个参数的介绍,并且12章中的shell编写部分有用到-d参数 date 参数(option)-d与&#…

    Linux 2023年6月6日
    099
  • 2021年3月-第02阶段-前端基础-移动WEB开发-移动WEB开发之_响应式布局

    移动端WEB开发之响应式布局 1.0 响应式开发原理 1.1 响应式开发原理 就是使用媒体查询针对不同宽度的设备进行布局和样式的设置,从而适配不同设备的目的。 设备的划分情况: 小…

    Linux 2023年6月8日
    0119
  • tomcat

    tomcat 一.简介 二.部署tomcat 一.简介 Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场…

    Linux 2023年6月7日
    0109
  • 2021年想做的最后挣扎

    一年的时间转眼间就过完,感觉没变,又感觉跟一年前的今天变化还是蛮多的,树立个小目标争取年前完成把 读书一本书看一篇文章: 《百年孤独》:我总感觉虽然是只单身狗是孤单的,理解不了孤独…

    Linux 2023年6月14日
    093
  • python中的cls和self区别

    self:Always use self for the first argument to instance methods self是作为类进行实例化传递的第一个参数,也就是我…

    Linux 2023年6月14日
    096
  • bash 中 echo & printf

    首先列一下今天收获的消息 sh是: Bourne Shell(/usr/bin/sh或/bin/sh) bash是: Bourne Again Shell(/bin/bash) p…

    Linux 2023年6月6日
    0107
  • MACOS Terminal终端:更改zsh模式到bash模式(切换shell)

    MACOS Terminal终端:更改zsh模式到bash模式(切换shell) 一、GUI界面切换: preferences… -> shell open wi…

    Linux 2023年5月28日
    0176
  • 使用Supervisor监控mysql

    监控文件配置: [program:mysql] ; 管理的子程序名字,要和项目有关联,不能乱写command=/usr/local/mysql/bin/mysqld_safe &#…

    Linux 2023年6月6日
    057
  • SQLI-LABS(Less-2)

    Less-2(GET-Error based-Intiger based) 打开 Less-2页面,可以看到页面中间有一句 Please input the ID as param…

    Linux 2023年6月6日
    091
  • centos进入rescue 失败

    环境:centos7 在机械盘 win10在固态盘 场景:centos7 中/etc/grub.d/下的30_os_prober 脚本删掉后导致 grub2 列举不出别的磁盘上系统…

    Linux 2023年6月6日
    098
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球