I/O多路转接select/poll/epoll

I/O多路转接(多路复用)又被称为“事件驱动”,是操作系统提供的一个功能,当你关心的文件(如socket)可读、可写时(称为事件就绪)采用某种方式通知你,只有收到通知时你才去执行read/write操作,这样在每次读或写时就不会阻塞,即I/O操作中等的部分交给操作系统内核去完成,而read/write之类的操作只需要在事件就绪时完成数据拷贝。等的过程由select/poll/epoll等系统调用触发,这些函数可同时监视多个描述符上的事件是否就绪,因此可以在一个线程内不发生阻塞的交替完成多个文件的I/O操作。复用是指复用同一个线程。

1.I/O多路转接之select

函数声明:

int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

参数解释:

  • nfds 需要关注的最大文件描述符编号加1;通知指定所关注的最大描述符,内核就只需监视此范围内文件描述符。
  • readfs/writefds/exceptfds分别是关注的可读文件描述符集合、可写文件描述符集合、处于异常条件的描述符集合。由参数位置确定关住的事件类型。
  • timeout结构用来设置select的等待时间。timeout=NULL则一直等下去,直到收到信号或有事件就绪才返回;为0时,根本不等待,检测一遍描述符状态,立即返回;特定的时间值,如果在特定的时间内关注的描述符之一已准备好则立即返回,并且timeout将被更新为剩余时间,若指定时间内没有事件发生,select将超时返回。

函数返回值:

  • 返回值为正值:已经事件就绪的文件描述符数,如果一个文件同时读写就绪,则会被计两次数;
  • 0值:没有事件就绪,没有一个事件就绪,等待时间就到了。此时3个描述符集都会被置0;
  • -1:出错,例如在一个事件都没就绪时捕捉到一个信号。此时不会对文件描述符集做修改。

文件描述符集fd_set结构本质上是一个位图,其中比特位值为1/0代表对于该文件描述符的某种事件关注/不关注,对fd_set结构的操作有:

void FD_CLR(int fd, fd_set *set); //清除fd_set中相关的fd位
int  FD_ISSET(int fd, fd_set *set);//检测fd位是否为真,事件是否就绪
void FD_SET(int fd, fd_set *set);//将fd添加到fd_set中
void FD_ZERO(fd_set *set);//将fd_set清0

注意:函数中文件描述符集参数既是输入型参数也是输出型参数,因此需要在每次调用select之前被重新设定。因此也就需要用户维护一个容器保存关注的所有文件描述符。

select的特点:

typedef struct fd_set {
     fd_mask fds_bits[howmany(FD_SETSIZE, NFDBITS)];
 } fd_set;
  • 由于fd_set结构大小固定,即位图大小固定,因此select可监控的文件描述符个数有上限,取决于sizeof(fd_set),上限为sizeof(fd_set)*8;
  • 需要维护一个容器来保存所有关注的文件描述符,(1)在select调用前先使用FD_ZERO将fd_set清0,再遍历该容器设置fd_set,并获得文件描述符最大值,用来设置select第一个参数;(2)在select返回时,遍历其使用FD_ISSET检测是否事件就绪。

select缺点:

  • 每次调用前需要手动设置fd_set,使用非常不便;
  • 每次调用时,会将fd_set从用户态拷贝至内核态,当关注的文件描述符很多时,是很大的开销;
  • 在select返回时需要遍历查询那个文件描述符的那个事件就绪了,这也是很大的开销;
  • select可监视的文件描述符个数有上限。

用例:select_server

2.I/O多路转接之poll

函数声明:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
     int   fd;         /* file descriptor */
     short events;     /* requested events */
     short revents;    /* returned events */
};

函数参数:

  • fds为一个结构体数组,每个元素为一个pollfd结构体,fd为文件描述符,events为用户关注的事件集合(输入型),由用户设置,revents为已就绪的事件集合(输出型),由内核设置;对于events/revents类型short,每一种类型的事件关心与否可以一个比特位0/1表示,因此事件类型不会超过16种,够用。
  • nfds为fds数组的有效元素个数;
  • timeout不同于select中,此处单位为毫秒,整形值(0、-1、>0)。

返回值:

  小于0表示出错,等于0表示超时,大于0,有几个事件就绪。

poll的优点:

  • 不同于select,poll的参数只需维护一个pollfd结构体数组;
  • poll没有最大数量限制,因为pollfd数组可由用户扩容。但poll返回时需要遍历数组检测就绪事件,因此也不易过大。

poll的缺点:

  • poll返回时,需要遍历pollfd来获取就绪事件及描述符;
  • 每次调用poll都需要将pollfd结构从用户态拷贝至内核态;
  • 同时关注的众多描述符可能只有很少处于就绪状态,而每次返回都有挨个遍历也会使其效率下降。

以上缺点在监听的文件描述符数目增多时都会影响效率。

用例:poll_server

3.I/O多路转接之epoll

epoll是为了处理大批量句柄而做了优化的poll(man手册),几乎修正了select和poll的所有缺点。

相关系统调用:

#include <sys/epoll.h>

int epoll_create(int size); //Since Linux 2.6.8, the size argument is ignored, but must be greater than zero;

创建一个epoll的句柄,返回一个描述符指向这个epoll句柄。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll事件注册函数,epoll_ctl通过(EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL)三个操作来分散对需要监控的fds集合的修改,做到了有变化才变更,将select或poll高频、大块内存拷贝(集中处理)变成epoll_ctl的低频、小块内存的拷贝(分散处理),避免了大量的内存拷贝。

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

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数events是由用户提前分配好的一个满足需求足够大小的结构体数组,maxevents为该结构体数组的容量,timeout同poll,当epoll_wait返回时内核将所有已就绪事件epoll_event拷贝至events数组内(epoll_event结构体内容与用户设置时相同),返回值为存在就绪事件描述符个数。

epoll工作原理

当调用epoll_create时,内核会创建一个eventpoll结构体,该结构体中有两个重要的成员:

  • 一个红黑树的根结点,这棵树中存储着所有通过epoll_ctl注册到epoll模型中的事件,每个节点代表一个文件描述符。利用红黑树可以在调用epoll_ctl时高效的进行增删查改,存在重复的事件也可高效的识别出来。
  • 一个双向链表,用来存储已经就绪的事件。

当执行epoll_ctl时,除了将事件增加到红黑树中,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个事件就绪了,就将该事件添加到就绪链表中。因此在调用epoll_wait时,只需检查就绪链表内是否有数据,有数据立即返回,没有数据,就在timeout时间内阻塞。epoll_wait不同于select/epoll的是,不需要在被调用时遍历自己所关注的事件,查看是否有就绪的,因为提前注册事件,这些工作都被内核中回调函数与就绪链表替代,epoll_wait只需检测链表是否为空,等待就好。

epoll使用过程:

  • epoll_create:创建一个epoll对象;
  • epoll_ctl:将需要监控的文件描述符及事件注册到epoll对象;
  • epoll_wait:等待事件就绪。

epoll优点:

  • 接口使用方便:不需要像select每次调用都要设置关注的描述符及事件;
  • 数据轻量拷贝:只需要在关注的事件有变化时,调用epoll_ctl进行增删改,拷贝epoll_event结构至内核,而poll/select都是每次调用都要拷贝所有关注的事件结构至内核。
  • 事件回调机制:避免使用遍历,使用回调函数将就绪的描述符结构加入就绪队列中,epoll_wait只需直接访问就绪队列。效率不随着文件描述符的增多而下降。
  • 没有数量限制。

epoll工作方式:以socket为例

水平触发LT

  当读事件就绪,此时接受缓冲区内有数据,若不讲接受缓冲区内数据处理完,则会一直触发读事件就绪;

  当写事件就绪,此时发送缓冲区中有空间,若未将发送缓冲区打满,则会一直触发写事件就绪。

边缘触发ET

  当接收缓冲区为满或有新的数据到来,会触发一次读事件就绪,如果此后再也没有数据到来,就再也不会触发读事件就绪,即使缓冲区内依然有之前收到的数据未处理完;

  当发送缓冲区为空或剩余空间变化时,会触发一次写事件就绪,如果发送缓冲区剩余空间一直不变,就一直不触发,即使还有剩余空间。

可见在ET模式下,在读事件就绪时,需要一次将所有数据全部读取,因为如果再无读事件就绪,就再也无法读到未读完的数据。所以需要循环式的读取缓冲区中内容,直到实际读取大小小于期待读取大小或读到空为止,因此在ET模式下,描述符必须设为非阻塞,如果读到空,可避免阻塞。对于单线程而言,倘若阻塞,一直没有数据到来,就挂了。

例:客户端向服务器发送10k数据(触发服务器一次读事件就绪),服务器处于ET模式下,服务器由于某种原因一次只读取了1k数据,剩余9k处于仍接受缓冲区内,只有下次客户端再向服务器发送数据(再触发读事件就绪),服务器才能读到那9k数据。由于服务器没能读到完整信息,无法给客户端响应,客户端没收到响应,也不会发送下一个请求,该连接将处于僵持状态。为避免上述状况(一次read未能将数据读完),可以采用非阻塞轮询的方式来读取缓冲区,确保一定能将数据全部读出。

select/poll工作在LT模式下,epoll默认工作方式为LT,也支持ET。

用例:epoll_server(ET)

epoll的使用场景:

  对于多连接的,且只有一部分连接比较活跃时,比较适合用epoll。

多路复用,事件驱动本质上还是IO事件,适宜于IO密集型工作,一个工作进程就可完成。但对于计算密集型事务,事件驱动并不合适,一个计算需要CPU耗时2秒,这两秒对于整个进程是完全阻塞的,即使有事件就绪也只能等待。这是多进程多线程就体现出优势,每个线程各做各的事情互补干扰。因此事件驱动适宜IO密集型业务,多进程多线程适宜计算密集型业务。

原文地址:https://www.cnblogs.com/dabai56/p/11279835.html

时间: 2024-10-19 17:41:37

I/O多路转接select/poll/epoll的相关文章

高性能服务器——I/O多路转接的三种模式(select &poll& epoll)

一.简单的服务器I/O模型 最简单的的TCP服务器,有三种模式: 1.单执行流,一个server端连接一个client端 2.多进程,一个server端通过多进程的方式,每个进程连接一个client端 3.多线程,一个server端通过多进程的方式,每个线程连接一个client端 (http://zhweizhi.blog.51cto.com/10800691/1830267) 这里实现过 要提升服务器性能,其实就是想要让一个server端能在负载允许的情况下,连接尽可能多的client端. 因

Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select poll epoll udp组播 线程池

[本文谢绝转载原文来自http://990487026.blog.51cto.com] Linux统系统开发12 Socket API编程3 TCP状态转换 多路IO高并发select  poll  epoll udp组播 线程池 TCP 11种状态理解: 1,客户端正常发起关闭请求 2,客户端与服务端同时发起关闭请求 3,FIN_WAIT1直接转变TIME_WAIT 4,客户端接收来自服务器的关闭连接请求 多路IO转接服务器: select模型 poll模型 epoll模型 udp组播模型 线

I/O多路复用之select,poll,epoll简介

一.select 1.起源 select最早于1983年出现在4.2BSD中(BSD是早期的UNIX版本的分支). 它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作. 2.select的优点 目前几乎在所有的平台上支持,具有良好的跨平台支持. 3.select的缺点 单个进程能够监视的文件描述符的数量存在最大限制.默认情况下,在Linux上单个进程能够打开的最

Linux内核中网络数据包的接收-第二部分 select/poll/epoll

和前面文章的第一部分一样,这些文字是为了帮别人或者自己理清思路的,而不是所谓的源码分析,想分析源码的,还是直接debug源码最好,看任何文档以及书都是下策.因此这类帮人理清思路的文章尽可能的记成流水的方式,尽可能的简单明了. Linux 2.6+内核的wakeup callback机制 Linux 内核通过睡眠队列来组织所有等待某个事件的task,而wakeup机制则可以异步唤醒整个睡眠队列上的task,每一个睡眠队列上的节点都拥有一个 callback,wakeup逻辑在唤醒睡眠队列时,会遍历

几种典型的服务器网络编程模型归纳(select poll epoll)

1.同步阻塞迭代模型 同步阻塞迭代模型是最简单的一种IO模型. 其核心代码如下: bind(srvfd); listen(srvfd); for(;;) { clifd = accept(srvfd,...); //开始接受客户端来的连接 read(clifd,buf,...); //从客户端读取数据 dosomthingonbuf(buf); write(clifd,buf)//发送数据到客户端 } 上面的程序存在如下一些弊端: 1)如果没有客户端的连接请求,进程会阻塞在accept系统调用处

Linux下select&amp;poll&amp;epoll的实现原理(一)

最近简单看了一把Linux linux-3.10.25 kernel中select/poll/epoll这个几个IO事件检测API的实现.此处做一些记录.其基本的原理是相同的,流程如下 先依次调用fd对应的struct file.f_op->poll()方法(如果有提供实现的话),尝试检查每个提供待检测IO的fd是否已经有IO事件就绪 如果已经有IO事件就绪,则直接所收集到的IO事件返回,本次调用结束 如果暂时没有IO事件就绪,则根据所给定的超时参数,选择性地进入等待 如果超时参数指示不等待,则

select/poll/epoll on serial port

In this article, I will use three asynchronous conferencing--select, poll and epoll on serial port to transmit data between PC and Raspberry pi. Outline Character device file of serial port Naive serial communication Asynchronous conferencing Select

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

# 进程/线程/协程 # IO:同步/异步/阻塞/非阻塞 # greenlet gevent # 事件驱动与异步IO # Select\Poll\Epoll异步IO 以及selectors模块 # Python队列/RabbitMQ队列

1 # 进程/线程/协程 2 # IO:同步/异步/阻塞/非阻塞 3 # greenlet gevent 4 # 事件驱动与异步IO 5 # Select\Poll\Epoll异步IO 以及selectors模块 6 # Python队列/RabbitMQ队列 7 8 ############################################################################################## 9 1.什么是进程?进程和程序之间有什么