1 简介
最近在看Nginx,着实为它的超高并发能力所折服,感觉随着“物联网”的发展,接入的设备越来越多,Nginx发展潜力巨大呀!
看的过程中也暴露了很多问题,比如IO就是其中之一,之前对IO的了解非常少,本篇就从IO分类说起,大致讨论一下Linux下IO多路复用的几种形式,看一下为什么Nginx选择epoll作为IO
2 IO分类及Linux下实现
2.1 IO分类
- 同步
- 异步
- 阻塞
- 非阻塞
区别:
阻塞/非阻塞:这是对IO的调用端而言的,调用端如果调用之后挂起,直到结果返回,就是阻塞。否则就是非阻塞。 (好比烧水和炒菜,烧水时可以走开做其他的事,但是炒菜要一直看着,不能做其他的事。例子有点牵强,好尴尬⊙﹏⊙‖∣)
同步/异步:这是对数据的传递方式而言,如果数据是调用端自己去取,就是同步,如果是处理端送达,就是异步。 (好比下馆子打包和叫外卖,打包是自己把菜带回家,叫外卖是别人送过来的,哈哈,这个例子还不错吧!)
搭配:
一般情况下,异步与非阻塞搭配,同步和阻塞、非阻塞均有搭配,下面的的内容就是同步的实例呀!
2.1 Linux <非>阻塞非>
改变Linux下IO的阻塞类型有2种方式:
方式一:打开时候设置
open("/dev/xxxx",O_RDWR);//阻塞
open("/dev/xxxx",O_RDWR|O_NONBLOCK );//非阻塞
//socket的fd目录:/proc/PID/fd目录
方式二:fcntl函数,动态改变
fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK);//阻塞>非阻塞
fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)&^O_NONBLOCK);//非阻塞>阻塞
注意: Linux下阻塞与非阻塞的概念,是针对文件而言,并不针对特定的读写函数。 非阻塞情况下,如果(errno==EAGAIN)表示文件读取完毕!
3 select/poll/epoll分析
3.1 优缺点及原理分析
3.1.1 select
每次执行select都需要查询整个句柄数组,先看看代码框架:
while true
{
select()//挂起
foreach(i in a[])
handle a[i]
}
select的实现模式有3大缺点:
- fd数目受限,在linux/posix_types.h头文件有这样的声明:#define __FD_SETSIZE 1024
- 每次select,都需要将fd数组整个拷贝到内核态
- 每次处理都要遍历整个fd数组,即使存在大量不需要处理的事件
3.1.2 poll
poll在select的基础上改进了:fd的数目有限的问题,fd上限是最大可以打开文件的数目,具体数目可以cat /proc/sys/fs/file-max来看,也可以自定义设置:
struct rlimit rt;
rt.rlim_max=rt.rlim_cur=MAXEPOLL
setrlimit(RLIMIT_NOFILE,&rt)
poll依然使用select的阻塞IO来实现。
3.1.3 epoll
epoll是event poll的缩写,在poll的基础上继续改进,epoll通过3个方法来实现高效性:
//创建一个epoll句柄:
epollfd=epoll_create(MAXEPOLL);
//注册要监听的事件类型;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev)
//则是等待事件的产生
eventfds = epoll_wait(epollfd,events,MAXEPOLLEVENT,0)
实现原理
1:每次注册新的事件到epoll句柄中时,只会拷贝对应的fd进内核,保证了每个fd在整个过程中只会拷贝一次。
2:epoll内部使用红黑树组织所有fd句柄,确保log(n)的查找效率,当对应事件发生时,通过回调函数的方法将树结点加到链表中。
3:使用双向链表保存所有的触发的事件, epoll_wait只需从链表中查找相应的事件,减少了遍历数组的大小。
epoll代码结构:
epoll_create()
epoll_ctl();
while true
{
epoll_wait(&b[])
foreach(i in b[])
handle b[i]
}
3.2 epoll触发类型
EPOLL触发类型可以分为:
- 边沿触发(ET)
- 等级触发(LT)
ET触发:只在缓冲区状态发生改变时触发相应事件(EPOLLIN/EPOLLOUT) 所以ET一次触发需要把所有数据循环读/写完!改变缓冲粗状态,方便下一次触发的识别。ET触发必须使用非阻塞IO
LT触发:只要缓冲区有数据(读)或不满(写)时,均会一直触发(EPOLLIN/EPOLLOUT) LT触发不要求IO类型。
区别:
ET相对高效,但编程难度相对较高,容易丢失数据!(比如我至今还没搞明白设置类型为EPOLLIN | EPOLLOUT,在处理EPOLLOUT时,会不会影响EPOLLIN的触发) |
LT通过冗余触发,提高了数据的可靠性,但是降低了效率!
结束
select/epoll为同步阻塞式,epoll可以同步阻塞也可以同步非阻塞。 epoll将所有fd保存内核空间中,触发的事件以“快捷方式”的形式进行副本保存,传到用户空间!虽然在同一时刻需要保存2份,但是在其极大地提高效率面前,这些都可以忽略不计啦!
PS:本篇内容,大部分都是看的别人的学习笔记,可能有很多理解不妥的地方,还望各路大神指正!