【后台开发】【网络IO模型】select、poll、epoll

select()

select函数原型

函数原型是:int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

这里面用到了两个结构体,fd_set和timeval。结构体fd_set可以理解为一个集合,存放着文件描述符(即文件句柄),这可以认为是常说的普通意义的文件;而结构体timeval用来表示时间值,它有两个成员,一个是秒数一个是毫秒数。

fd_set类型可以简单理解为按bit位标记句柄的队列,例如要在某fd_set中标记一个值为16的句柄,则该fd_set的第16个bit位被标记为1。具体的置位、验证可使用FD_SET、FD_ISSET等宏实现。在select()函数中,readfds、writefds、execptfds同时作为输入参数和输出参数。如果用输入的readfds标记了16号句柄,则select()将检测16号句柄是否可读。在select()返回后,可以通过检查readfds有否标记16号句柄,来判断该“可读事件”是否发生。

select的各个参数含义:
  1. maxfdp:一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1。
  2. readfds:指向fd_set结构的指针,这个集合中应该包括文件描述符。如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读。如果没有文件可读,则根据timeout参数再判断是否超时:若超出timeout的时间,select返回0;若发生错误返回负值;也可传入NULL值,表示不关系任何文件的读文件。
  3. writefds:同readfds参数的意图,用来监视文件可写变化。
  4. errorfds:同readfds和writefds参数的意图,用来监视文件错误异常。
  5. timeout:是select的超时时间,这个参数至关重要,它可以使select处于3种状态:
    (1)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    (2)若将时间值设为0,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立即返回继续执行,文件无变化返回0,有变化返回一个正值;
    (3)timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
  6. 返回值:准备就绪的描述符数,若超时则返回0,若出错则返回-1。

poll

poll函数原型

poll所需的头文件和函数原型如下所示:

#include<poll.h>
int poll(struct pollfd * fds, unsigned int nfds, int timeout);

pollfd结构体定义如下所示:

struct pollfd{
	int fd;	//文件描述符
	short events;	//等待的事件
	short revents;	//实际发生了什么事
};

每一个pollffd结构体指定了一个被监视的文件描述符,可以传递多个结构体指示poll()监视多个文件描述符。

每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域的属性。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域;并且events域中请求的任何事件都可能在revents域中返回。

timeout参数指定等待的毫秒数,无论IO是否准备好,poll都会返回。timeout指定为负数值时(对应select中timeval传入NULL)表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好IO的文件描述符,但并不等待其它的事件。在这种情况下,poll的返回值一旦被选举出来,立即返回。

成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1。


epoll

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

epoll接口

使用epoll必须包含头文件:#include<sys/epoll.h>

epoll操作过程需要3个接口:

int epoll_create(it size);	//1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);	//2
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);	//3
  1. 创建一个epoll句柄,size用来告诉内核要监听的数目。
  2. epoll的事件注册函数,它不同于select()在监听事件时告诉内核要监听什么类型的事件,而是先注册要监听的事件类型。
  3. 等待事件的发生,类似于select()调用。

select、poll和epoll的区别

select、poll和epoll都是多路IO复用的机制。多路IO复用就是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(读、写、异常),就能通知程序进行相应的操作。但select、poll和epoll本质上都是同步IO,因为它们都需要在读写事件就绪后自己负责进行读写,即是阻塞的;而异步IO则无须自己负责进行读写,异步IO的实现会负责把数据从内核拷贝到空间中。

select()和poll():

对于网络编程来说,一般认为poll()比select()要高级一些:

  1. poll()在应付大数目的文件描述符时速度更快:因为对于select()来说内核需要检查大量描述符对应的fd_set中的每一个比特位,比较费时。
  2. poll()可以监控的文件数目远大于select()。
  3. 对于select()来说,所监视的fd_set在select()返回之后会发生变化,所以在下一次进入select()之前都需要重新初始化需要监控的fd_set;poll()函数将监控的输入和输出事件分开(结构体pollfd中的events和revents),允许被监控的文件数组被复用而不需要重新初始化。
  4. select()每次在超时之后在下一次进入到select()之前也需要重新设置超时参数。
epoll()优点:
  1. 支持一个进程打开大数目的socket描述符(FD)。

select()可以监控的文件描述符数目是固定的(由FD_SETSIZE的默认值是1024/2048),epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目(这个数目和系统内存关系很大)。

  1. IO效率不随文件描述符数目增加而线性下降。

传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延迟,任一时间只有部分的socket是“活跃”的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈线性下降。
但是epoll不存在这个问题,它只会对“活跃”的socket进行操作——这是因为在内核中实现epoll是根据每个fd上面的callback函数实现的。那么只有“活跃”的socket才会主动去调用callback函数,其它idle状态socket则不会,在这点上,epoll实现了一个“伪”AIO。

  1. 使用mmap1加速内核与用户空间的消息传递。

无论是select、poll还是epoll都需要内核把fd消息通知给用户空间,如何避免不必要的内存拷贝就显得尤为重要。在这点上,epoll是通过内核与用户空间mmap处于同一块内存实现的。


  1. mmap内存映射文件,详见深度分析mmap:是什么 为什么 怎么用 性能总结 ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值