高级I/O总结(二):select、poll、

回顾部分
同步通信 VS 异步通信

同步就是发出一个调用时,在没有得到结果之前,该调用就不返回。调用者会主动等待调用的结果。

异步就是调用者发出调用之后,调用直接返回了,没有结果。后续有了结果,会以状态、信号等方式通知调用者。

阻塞 VS 非阻塞

阻塞就是指调用结果返回之前,当前线程被挂起,直到返回结果。

非阻塞就是指不能立刻返回结果之前,该调用不会阻塞当前线程。

select

概念:
是用来监视多个文件描述符状态变化的函数。程序会停在select这里等待,直到被监视的文件描述符有一个或者多个发生状态变化。

函数原型:

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
          fd_set *exceptfds, struct timeval *timeout);

函数返回值:

  • 成功则返回⽂文件描述词状态已改变的个数。
  • 返回0代表在描述词状态改变前已超过timeout时间,没有返回。
  • 错误时返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout
    的值变成不可预测。

**fd_set结构:**其实这个结构就是一个整数数组, 更严格的说, 是一个 “位图”。使⽤用位图中对应的位来表⽰示要监视的⽂文件描述符。

提供了一组操作fd_set的接口, 来比较方便的操作位图:

void FD_CLR(int fd, fd_set *set); // 清除描述词组set中相关fd 的位
int FD_ISSET(int fd, fd_set *set); // 测试描述词组set中相关fd 的位是否为真
void FD_SET(int fd, fd_set *set); // 设置描述词组set中相关fd的位
void FD_ZERO(fd_set *set); // 清除描述词组set的全部位

理解select的执行过程:

也就是理解底层实现原理。select底层是fd_set,fd_set长度为字节1,每一个bit对应一个文件描述符fd,那么一个字节fd_set可以最大对应8个fd。

select的特点:

1、可监视的文件描述符最多个数就是fd_set的大小sizeof(fd_set);如果一个服务器sizeof(fd_set) = 512,每个bit表示一个文件描述符,支持的最大文件描述符就是512*8=4096。

2、将fd加入到select监视集。使用一个array保存放到select监视集中的fd。一是select返回后,array作为源数据和fdset进行fd_set进行判断。二是select返回后会把以前加入的,但没有事件发生的fd清空。每次开始select前重新从array中取得fd逐一扫描,取到fd的最大值,用于select的第一个参数。

select的缺点:

1、调用select前,要把fd集合从用户态拷贝到内核态。

2、每次调用select都需要在内核遍历传递进来的所有fd。

3、select支持的文件描述符大小数量有限。

poll

函数原型:

#include <poll.h>
int poll(struct pollfd *fds,  nfds_t nfds,  int timeout);
// pollfd结构
struct pollfd {
    int fd;     /* file descriptor */
    short events;     /* requested events */
    short revents;     /* returned events */
};

参数说明:

  • fds是⼀个poll函数监听的结构列表。每一个元素中, 包含了三部分内容: 文件描述符,监听的事件集
    合, 返回的事件集合。

  • nfds表示fds数组的⻓长度。

  • timeout表⽰示poll函数的超时时间, 单位是毫秒。

返回值:

  • 小于0, 表示出错。
  • 等于0, 表示poll函数等待超时。
  • 大于0, 表示poll由于监听的文件描述符就绪而返回。

poll的优点:

select使用三个位图表示,poll使用pollfd的指针实现。

1、pollfd结构包含监视的event和发生的event。接口使用方便。

2、poll没有文件描述符数量限制,但过多性能也会下降。

poll的缺点:

前提:fd增多时

1、和select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。

2、每次调用poll要将大量的pollfd结构从用户态拷贝到内核中。

3、连接大量客户端在一个时刻可能只有很少的处于就绪状态,那么文件描述符数量增多,效率也会下降。

epoll

几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll_create
int epoll_create(int size);

创建一个epoll句柄。注意用完要close()关闭。

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

select是告诉内核要监听什么类型的事件。epoll先注册需要监听的事件类型。

函数参数
1、epoll句柄。

2、动作(用三个宏表示:注册新的fd到epfd、修改fd监听事件、从epfd中删除一个fd)。

3、需要监听的fd。

4、告诉内核需要监听什么事件(可读、可写、发生错误、被挂断)。

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

收集在epoll监视的事件中已经发送的事件。
如果函数调用成功,返回对应I/O上已准备好的⽂文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败。

epoll的工作原理

底层实现:一颗红黑树,一张准备就绪句柄链表,少量的内核cache。

为什么epoll高效

epoll比select/poll的优越:
epoll的高效就在于,当我们调用epoll_ctl往里塞入百万个句柄时,epoll_wait仍然可以飞快的返回,并有效的将发生事件的句柄给我们用户。这是由于我们在调用epoll_create时,内核除了帮我们在epoll文件系统里建了个file结点,在内核cache里建了个红黑树用于存储以后epoll_ctl传来的socket外,还会再建立一个list链表,用于存储准备就绪的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。

而且,通常情况下即使我们要监控百万计的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已,如何能不高效?!

这个准备就绪list链表是怎么维护的呢?当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。

如此,一颗红黑树,一张准备就绪句柄链表,少量的内核cache,就帮我们解决了大并发下的socket处理问题。执行epoll_create时,创建了红黑树和就绪链表,执行epoll_ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

epoll的两种模式(ET & LT)

LT:

水平触发模式是缺省的工作方式,并且同时支持 block 和 non-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。

ET:

边缘触发模式是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,等到下次有新的数据进来的时候才会再次出发就绪事件。

LT和ET优缺点:

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。

ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

askunix_hjh

谢谢请我吃糖~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值