概念准备
任务队列:这里指线程的任务队列,维护着需要CPU运行的线程。
等待队列:socket文件描述符上的一个数据结构,如果想要监听socket是否数据过来,就需要将进程放到等待队列中,如果有数据产生,那么就将线程从等待队列中移除,重新放到任务队列里面,等待CPU进行调度。
socket描述符:可以理解为一个链接就可以转化为一个socket描述符
select
select的系统调用,主要的参数是一个fds数组
fds:存放需要监听的socket链接的数组
调用select的核心步骤
假如fds有socket1、socket2、socket3
1、将当前的调用进程注册到socket1、socket2、socket3各自的等待队列中,并且将当前进程从CPU的任务队列中移除
2、select会轮询的查看socket1、socket2、socket3的缓冲区中是否有数据到来
3、如果存在数据,就会将等待队列中的进程唤醒,将进程从等待队列中移除,加入到任务队列里面
4、但是进程不知道是哪个socket准备好了,因此他需要循环fds,然后对数据进行处理
图解
缺陷
- 每次调用select都会将进程重新注册到socket的等待队列中去
- 需要进行两次循环调用。
- 第一次是将进程注册到socket的等待队列
- 第二次是查看哪个socket的缓冲区中有数据
poll
poll的实现思路跟select基本差不多,只不过是将fds的数据结构改为链表。
打破了个数的限制,select限制最多可以监听1024个socket,如果想要修改,需要重新编译内核。
epoll
epoll是对select的缺陷进行优化
1、不需要重新注册,只需要注册一次感兴趣的事件
2、只需要一次循环调用即可
优化措施
epoll_create:内核会创建一个 eventpoll 对象(专用的文件描述符,也就是程序中 epfd 所代表
的对象)eventpoll 对象也是文件系统中的一员,和 socket 一样,它也会有等待队列。
epoll_ctl:添加待监控的socket
如果通过 epoll_ctl 添加 sock1、sock2 和 sock3 的监视,内核会将三个 socket 添加到 eventpoll
监听队列。
epoll_wait:阻塞等待
进程 A 运行到了 epoll_wait 语句之后,进程A会进入eventpoll 的等待队列。
epoll_create
主要是创建一个eventpoll对象,他和socket一样,也有等待队列
eventpoll对象主要的数据结构
- rbr:红黑树,存放需要监听的socket
- rdllist:一个双向链表,存放已经发生IO事件的socket
- poll_wait:等待队列,进程就是注册到poll_wait中
epoll_ctl
添加待监控的socket
会将待监控的socket添加到eventpoll对象的rbr中,因为rbr是一个红黑树,所以epoll监听的连接数,可以远远大于1024个
当socket收到数据后,中断程序会将收到数据的socket添加到eventpoll的就绪队列rdlist中
epoll_wait
当程序执行到epoll_wait时,如果rdlist非空则返回,如果rdlist为空,阻塞进程
当socket接收到数据,中断程序一方面将其插入rdlist,另一方面唤醒eventpoll等待队列中的进程,进程A再次进入内核的工作队列。进程A进行运行状态。
图解
可以认为eventpoll当一个中介的作用
优化概括
- 使用红黑树rbr,打破了select监听socket个数的限制。
- 将要监听的socket存放到eventpoll的rbr中,然后将eventpoll注册到各个socket的等待队列中,使得eventpoll当一个中介的作用,因此只需要注册一次即可。
- 使用rdllist存放已经发生事件的socket,无需重复遍历要监听的socket。
ET和LT
ET边沿触发:有数据进入到缓冲区中,那么就会触发事件,如果在这次事件中,没有读完缓冲区的数据,那么下次再来读,也不会读取数据,只有当数据再次到缓冲区中才可以再次处理。
LT水平触发:缓冲区中有数据,就会一直读取,直到缓冲区中没有数据为止。