Linux的select poll epoll的IO多路复用

IO多路复用

什么是IO多路复用呢?我们可以把诸如标准输入输出、套接字等看成是IO的一路,当任何一路有事件发生时,内核就通知应用程序去获取事件。Linux下有select,poll,epoll三种IO多路复用技术,本来就介绍一下他们的用法。

select

select函数的原型如下:

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);//返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1

1、maxfd是待测试描述符集合的最大描述符+1。为什么是最大描述符+1呢?我们接下来分析。
2、读描述符集合readset、写描述符集合writeset与异常描述符集合exceptset分别告诉内核需要检测哪些描述符可读、可写以及是否有异常发生。
3、timeout表示超时时间。其定义如下:

struct timeval {
  long   tv_sec; /* seconds */
  long   tv_usec; /* microseconds */
};

当设置空时,表示一直堵塞知道有指定描述符的指定事件发生;当设置全0时,表示检测完毕好,不管有没有事件发生,都立即返回;当设置非0值时,如果有事件发生,立即返回,如果没有事件发生,最长等待设置的值。
那么我们如何设置描述符集合呢?Linux提供了如下宏定义:

void FD_ZERO(fd_set *fdset);      
void FD_SET(int fd, fd_set *fdset);  
void FD_CLR(int fd, fd_set *fdset);   
int  FD_ISSET(int fd, fd_set *fdset);

很多系统是用一个整型数组来表示一个描述字集合的,一个 32 位的整型数可以表示 32 个描述字,例如第一个整型数表示 0-31 描述字,第二个整型数可以表示 32-63 描述字,以此类推。默认最大可以表示1024个描述符。
FD_ZERO 用来将数组的所有元素都设置成 0;
FD_SET 用来把对应套接字 fd所在数组中的位设置为1;
FD_CLR 用来把对应套接字 fd 所在数组中的位设置成 0;
FD_ISSET 对这个向量进行检测,判断出对应套接字所在数组中的位是 0 还是 1。
这是不是很像编程语言中的bitmap呢?maxfd用来告诉操作系统最多需要扫描多少位,这就是为什么maxfd的值为描述符集合中最大描述符的值+1了。
当应用程序调用select返回后,Linux会修改描述符集合,用于通知应用程序哪些描述符有事件待处理。应用程序可以使用FD_ISSET来判断哪些描述字有待处理的事件。
对于读描述符集合,当检测到有事件待处理时,意味着发生了如下的事件:
1、读缓冲区有数据待读取,此时调用read函数会返回读取到的字节数;
2、对方发送了FIN报文,关闭了写,此时调用read函数回返回0;
3、描述符发生了错误,此时调用read函数会返回-1,我们可以用errno与strerror获取错误类型;
4、对于监听套接字,则意味着有新连接建立,此时我们可以调用accept函数无堵塞的获取新连接。
对于写描述符集合,当检测到有待处理事件时,意味着:
1、发送缓冲区缓冲区有剩余空间,此时可以无堵塞的调用write函数,往发送缓冲区写数据,函数返回实际写入的字节数;
2、发生错误了,此时可以无堵塞的调用write函数,函数返回-1,通过errno与strerror()可以获取到错误信息;
3、对于连接发起方,当使用非堵塞socket建立连接时,如果只可写,说明连接已经建立了;如果可读可写说明可能出错,也可能连接成功了;否则就是出错了。

poll

poll是另一种常见的IO多路复用技术,其名声虽然没有select响亮,但其能力一点都不比select差。其函数原型如下:

int poll(struct pollfd *fds, unsigned long nfds, int timeout); //返回值:若有就绪描述符则为其数目,若超时则为0,若出错则为-1

其中 pollfd 的结构如下:

struct pollfd {
    int    fd;       /* file descriptor */
    short  events;   /* events to look for */
    short  revents;  /* events returned */
 };

1、fd表示检测的描述符,当不需要检测时,可以将其设置为-1;
2、events表示待检测的事件类型,其可以取如下的值,但我们一般取POLLIN与POLLOUT。

#define    POLLIN    0x0001    /* any readable data available */
#define    POLLPRI   0x0002    /* OOB/Urgent readable data */
#define    POLLOUT   0x0004    /* file descriptor is writeable */

和 select 非常不同的地方在于,poll 每次检测之后的结果不会修改原来的传入值,而是将结果保留在 revents 字段中,这样就不需要每次检测完都得重置待检测的描述字和感兴趣的事件。revents表示待处理的事件,其可以取如下的值:

#define POLLIN     0x0001    /* any readable data available */
#define POLLPRI    0x0002    /* OOB/Urgent readable data */
#define POLLRDNORM 0x0040    /* non-OOB/URG data available */
#define POLLRDBAND 0x0080    /* OOB/Urgent readable data */
#define POLLOUT    0x0004    /* file descriptor is writeable */
#define POLLWRNORM POLLOUT   /* no write type differentiation */
#define POLLWRBAND 0x0100    /* OOB/Urgent data can be written */
#define POLLERR    0x0008    /* 一些错误发送 */
#define POLLHUP    0x0010    /* 描述符挂起*/
#define POLLNVAL   0x0020    /* 请求的事件无效*/

对于读事件,我们一般用POLLIN即可,对于写事件一般用POLLOUT即可。这里的不同事件代表的意义与select类似,这里就不在赘述。
3、timeout如果是一个 <0 的数,表示在有事件发生之前永远等待;如果是 0,表示不阻塞进程,立即返回;如果是一个 >0 的数,表示 poll 调用方等待指定的毫秒数后返回。
4、关于返回值,当有错误发生时,poll 函数的返回值为 -1;如果在指定的时间到达之前没有任何事件发生,则返回 0,否则就返回检测到的事件个数,也就是“returned events”中非 0 的描述符个数。
和select对比一下,select随着fd_set的定义,其能支持的描述符的数量已经固定了,而poll是通过应用程序传入数组来实现的,这就意味着其可以支持的描述符数量不受限制;另外select返回后,会改变描述符集合,这样每次调用都要重新赋值,而poll不会改变描述符集合,只会改变revents的值。显而易见poll优势挺多的。

epoll

epoll_create

int epoll_create(int size);
int epoll_create1(int flags);//返回值: 若成功返回一个大于0的值,表示epoll实例;若返回-1表示出错

epoll_create() 方法创建了一个 epoll 实例,从 Linux 2.6.8 开始,参数 size 被自动忽略,但是该值仍需要一个大于 0 的整数。这个 epoll 实例被用来调用 epoll_ctl 和 epoll_wait,如果这个 epoll 实例不再需要,比如服务器正常关机,需要调用 close() 方法释放 epoll 实例,这样系统内核可以回收 epoll 实例所分配使用的内核资源。
epoll_create1() 的用法和 epoll_create() 基本一致,如果 epoll_create1() 的输入 flags 为 0,则和 epoll_create() 一样,内核自动忽略。可以增加如 EPOLL_CLOEXEC 的额外选项。

epoll_ctl

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//返回值: 若成功返回0;若返回-1表示出错

第一个参数 epfd是epoll_create 创建的 epoll 实例描述字。
第二个参数有三个选项可供选择:

EPOLL_CTL_ADD: 向 epoll 实例注册文件描述符对应的事件;
EPOLL_CTL_DEL:向 epoll 实例删除文件描述符对应的事件;
EPOLL_CTL_MOD: 修改文件描述符对应的事件。

第三个参数是注册的事件的文件描述符,比如一个监听套接字。
第四个参数表示的是注册的事件类型,并且可以在这个结构体里设置用户需要的数据,其中最为常见的是使用联合结构里的 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 */
 };

对于events字段可以取如下的值,其与poll差不多,就不在赘述

EPOLLIN
              The associated file is available for read(2) operations.
EPOLLOUT
              The associated file is available for write(2) operations.
EPOLLRDHUP
              Stream  socket peer closed connection, or shut down writing half
              of connection.  (This flag is especially useful for writing sim-
              ple code to detect peer shutdown when using Edge Triggered moni-
              toring.)
EPOLLPRI
              There is urgent data available for read(2) operations.
EPOLLERR
              Error condition happened  on  the  associated  file  descriptor.
              epoll_wait(2)  will always wait for this event; it is not neces-
              sary to set it in events.
EPOLLHUP
              Hang  up   happened   on   the   associated   file   descriptor.
              epoll_wait(2)  will always wait for this event; it is not neces-
              sary to set it in events.
EPOLLET
              Sets  the  Edge  Triggered  behavior  for  the  associated  file
              descriptor.   The default behavior for epoll is Level Triggered.
              See epoll(7) for more detailed information about Edge and  Level
              Triggered event distribution architectures.
EPOLLONESHOT (since Linux 2.6.2)
              Sets  the  one-shot behavior for the associated file descriptor.
              This means that after an event is pulled out with  epoll_wait(2)
              the  associated  file  descriptor  is internally disabled and no
              other events will be reported by the epoll interface.  The  user
              must  call  epoll_ctl() with EPOLL_CTL_MOD to re-enable the file
              descriptor with a new event mask.

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);//返回值: 成功返回的是一个大于0的数,表示事件的个数;返回0表示的是超时时间到;若出错返回-1。

epoll_wait() 函数类似之前的 poll 和 select 函数,调用者进程被挂起,在等待内核 I/O 事件的分发。
第一个参数 epfd是epoll_create 创建的 epoll 实例描述字。
第二个参数返回给用户空间需要处理的 I/O 事件,这是一个数组,数组的大小由 epoll_wait 的返回值决定,这个数组的每个元素都是一个需要待处理的 I/O 事件,其值与 epoll_ctl 可设置的值一样。
第三个参数是一个大于 0 的整数,表示第二个参数的数组的大小。
第四个参数是 epoll_wait 阻塞调用的超时值,其含义与pull一样。

edge-triggered VS level-triggered

level-triggered即条件触发,只要满足事件的条件,比如有数据需要读,就一直不断地把这个事件传递给用户;
edge-triggered即边缘触发,只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了。一般来说边缘触发的效率比条件触发的效率要高。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值