目录
select和poll接口 -- 多路转接之select(fd_set介绍,参数详细介绍,优缺点),实现非阻塞式网络通信(代码+思路)_fdset大小-CSDN博客
多路转接之poll(接口介绍,struct pollfd介绍,实现原理,实现非阻塞网络通信代码)-CSDN博客
epoll接口
epoll_create
创建一个epoll模型
- 用完之后,需要调用close()关闭
size
已经在linux2.6.8被废除了,参数不重要
- 只要>0即可
返回值
- 成功返回文件fd
- 失败返回-1,并设置错误码
epoll_wait
等待 epoll 实例中的事件发生,获取已经就绪的文件及其上面的事件
epfd
epoll_create()的返回值
- 该返回值本质上是epoll模型对应的文件描述符(后面细说)
events
输出型参数,返回已经就绪的文件fd和上面的事件
struct epoll_event
结构体中的两个字段:
- 事件类型 -- 位图的形式,传递标记位(和poll中的events/revents是一样的,只是名字换成了EPOLL*)
- 保存用户级数据(是联合体,可以是这些字段的其中之一) -- 方便我们后期得知是哪一个文件就绪,帮助我们进行事件处理(具体在代码中会体现)
maxevent
events数组的大小
- 即一次可以接收的最大事件数
- 大小多少都可以,按需设置
timeout
超时时间,单位 -- 毫秒
- 和poll中的作用一样
返回值
和select,poll中的一样,如果等待就绪事件成功,返回的是已就绪事件的个数
- 该参数在select,poll中没什么作用,但在这里有大用
前面说了,events数组中会存储已就绪的fd及其事件信息
- 所以,返回值对应着events数组中有效数据的个数
- 可以用这个作为遍历数组的依据
其他两种情况,也与select/poll的返回值一样
- 如果等待超时,返回0
- 等待过程中有错误,返回<0的值
epoll_ctl
对系统要关心的文件/关心的事件做修改
- 总之就是管理工作
epfd
依然是epoll_create()的返回值
op
选项,指定操作类型
- 增/改/删
fd
要操作的文件fd
event
要操作的是文件上的哪个/哪些事件
- 这里传递的是该结构的指针
返回值
- 成功返回0
- 失败返回-1,并设置错误码
select和poll都是用户自己维护了一个数组,来管理fd
os定期检查该进程的文件描述符表,查看是否有文件就绪
如果没有,则会被挂起,到等待队列中,到下一个时刻继续检查
所以,os需要主动检查是否就绪
epoll模型
网络数据的处理
当网络数据到达网卡时:
- 网卡是个外设,不会自己对数据做处理,需要依靠os
- 那os如何得知网卡上是否有数据?
- 一旦数据就绪,会发生硬件中断
- os为了处理中断 -> 查询中断向量表,执行注册好的处理程序 -> 将网卡数据读取到网卡驱动的数据链路层缓冲区(调用驱动层的方法,将数据交付到链路层)
- 驱动层和链路层是对应的,因为网络是os的一部分:
- 然后继续向上交付,直到传输层,就该轮到用户层通过系统调用读取数据了(可以选择调用recv/read阻塞等待数据到来,或是使用select/poll/epoll等机制,数据到来后再通知上层来读)
为了支持epoll的特性,os会维护两个结构:
红黑树
os会在内部维护一棵红黑树
- 以fd为键值
- 每个结点代表要关注的一个文件及其相关事件(对应select/poll中用户自己维护的数组)
就绪队列
如果红黑树中的某个结点上有事件就绪(对应某文件上有事件就绪),就把它链入到就绪队列中
- 是形成一个新的结点,而不是直接把结点拿过去 (一个结点可以存在于多个结构中)
以上操作由os自发完成
除了两个结构,os还提供了回调函数
回调函数
网卡允许os注册回调函数
当链路层有数据(以sk_buff的形式存在)就绪,就会调用该函数,该函数的作用:
- 将报文向上交付,解包,把有效载荷交付给tcp的接收缓冲区(也就是链入到struct sock(表示一个tcp连接)中的接收队列中)
- 确认该数据是否和红黑树中的结点关联(每个struct sock 与 一个struct file相互关联,通过struct file中的fd可以知道当前数据属于哪个文件(也就是套接字)),并确认此时的事件是否是os关注的
- 如果此时这份数据恰好是os关注的,就会构建就绪结点,插入到就绪队列中
- linux底层设计 -- 网络协议栈的部分底层实现(文件->网络,网络的开始),file结构中的private_data字段,socket结构体,sk_buff结构体,封装报头/解包/上下传递报文的本质_报文 file字段-CSDN博客
所以,之后用户只需要从就绪队列中拿取已经就绪的事件即可
以上三套机制,叫做epoll模型
总结
所以,epoll_create所做的创建epoll模型,说的就是要创建这两个内核数据结构
- 这些结构会被统一管理起来,因为可能会存在多套epoll模型(只要调用多次epoll_create就能做到)
- 如何组织起来?
- 将就绪队列的头结点,红黑树的根结点的指针放在一块就好了,回调函数只要注册在链路层就不用管了
当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体
那这套模型如何被用户层使用呢?
- 因为linux中"一切皆文件"的理念,我们把它也当做文件,创建一个struct file与它关联,分配到进程的文件描述符表中
- 只要知道fd,就能找到file结构体,里面有指针指向epoll模型,就能操作epoll相关结构了
这就是为什么epoll_create返回的是一个fd,因为它底层是以struct file被管理:
所以,epoll和前面学的两个方案(select,poll)是完全不一样的
- 内核为了支持epoll会专门设计结构
- 而那俩只是简单的遍历fd表,结构需要自己维护
接口和模型的关系
epoll_create
- 创建出上面介绍的两个结构(红黑树和就绪队列),挂接到文件结构体上
- 将file结构添加到调用该函数的进程的文件描述符表中
- 最后返回给用户层fd
epoll_ctl
- 根据epfd参数,查找当前进程fd表,从而找到epoll模型对应的file结构
- file和epoll模型一一对应,所以可以找到红黑树
- 而ctl的三种操作 -- 增删改,就是对红黑树的操作
epoll_wait
- 和ctl一样,有epfd,就可以找到epoll模型结构,这里用到的是就绪队列
- events是输出型参数,wait操作会将就绪队列中的一定量元素(就绪的文件及其上面的事件)放入数组中
- 如果队列中元素个数>数组大小(也就是wait中的maxevents参数),那就下次再说(在下一次操作时,再拿取剩下的已就绪信息)
epoll优点(和select/poll的对比)
内核层面
数据从接收 到 通知我们 的过程中,是一路被回调过来的
- 也就是事件驱动,不需要os主动轮询检测
os检测是否就绪
- 时间复杂度是O(1),因为只需要判断就绪队列是否为空
- 而不像来select/poll,需要遍历文件描述符表,检查每个fd的状态
os获取就绪事件并传给用户层
- 时间复杂度为O(n) ,因为os只需遍历就绪队列,将队列中结点拷贝到用户层即可(wait中传入的events数组中)
- 另外两种机制也是O(n),os需要更新fd_set集合
用户层面
用户层获取就绪事件
- wait拿到的events数组,本身就存放了连续的就绪事件
- 所以,用户层不需要遍历+判断(是否是有效fd),直接拿就行
- 也就是,epoll的遍历不会有无效操作
可以关注的文件上的事件无上限
- 因为本质上这些事件都是红黑树上的结点,而红黑树本身可以扩容
- 所以,上限在于内存是否足够,而不在于epoll机制
- epoll里的红黑树正对应select/poll里用户维护的数组,数组是有上限的,虽然也可以手动让它扩容,但总归是不方便,而且一旦数量过多,因为遍历效率低成本高,所以扩容太多也不好,这里就没有这个缺点,因为从机制上就不一样
epoll接口使用 -- epoll接口使用 -- 非阻塞式网络io(仅读事件)-CSDN博客