一、LINUX IO模型
网络IO本质是socket读取,socket在Linux系统被中抽取为流,IO可以理解为对流操作。对于一次IO访问,对read,数据先被拷贝到操作系统内核的缓冲区,然后才会从操作系统内核拷贝到应用程序的地址空间。
即:
- 第一阶段:等待数据
- 第二阶段:将数据从内核拷贝到进程
网络IO有五种模型:
- 1.阻塞IO
- 2.非阻塞IO
- 3.多路复用IO
- 4.信号驱动IO
- 5.异步IO
其中前四种为同步。
同步与异步、阻塞与非阻塞区别:
同步和异步只信号通知的方式
阻塞和非阻塞在于等待信号的方式
1、IO多路复用
IO多复用指的是内核一旦发现进程指定的一个或多个IO条件准备读取,它就通知该进程。
IO多路复用适用于如下场合:
-
- 1.当客户处理多个描述符时(交互式输入和网络套接口),必须使用IO复用。
- 2.当一个客户同时处理多个套接口。
- 3.若一个TCP服务器既要监听套接口,又要处理已连接套接口。
- 4.若一个服务既要处理TCP又要处理UDP,一般要使用IO复用。
- 5.若一个服务器要处理多个服务或多个协议。,一般使用。
与多进程和多线程相比,IO多路复用技术最大的优势就是系统开销小,系统不必创建进程/线程,也不必维护它们,从而大大减小了系统开销。
I/O多路复用的系统调用有select、pselect、poll、epoll,IO多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(读、写),就通知程序进行相应的操作。它们都是同步的,因为它们都需要在读和写事件就绪后自己负责进行读写
二、select函数
1、select()函数
该函数用于探测多个文件句柄的状态变化。
参数一是最大的文件描述符加1,参数二用于检测可读性,参数三检查可写性,参数四检查异常,参数五是一个指向timeval结构的指针,若这个时间内,需要监视的描述符没有书剑发送则函数返回,返回值为0。
下面的宏提供了处理这三种描述词组的方式:
- FD_CLR :用来清除描述词组set中相关fd的位
- FD_ISSET:用来测试描述词组中set相关fd的位是否为真
- FD_SET:用来设置词组中的set中相关的位
- FD_ZERO:用来清除描述词组set的全部位
对timeout,若设为NULL表示select一直被阻塞,直到某个文件描述符上发生了事件;当设置为0表示仅检查描述符集合的状态,然后立即返回,不等待外部事件的发送。当设置为>0的值表示在这个指定的时间段内若没有事件发生,则select超时返回。当select函数返回后,可以通过遍历fdset来找到就绪的描述符。
fd_set类型可以理解为按bit位标记句柄的队列(若要在某fd_set中标记一个值为16的句柄,则fd_set的第16个bit位被标 记为1)
例
① 执行fd_set set;FD_ZERO(&set);则set用位表示0000 0000
② 若fd=5,执行FD_SET(fd,&set),然后set变为0001 0000
③ 若再加入fd=2,fd=1,则set变为0001 0011
④ 执行select(6,&set,0,0,0)阻塞等待。
⑤ 若fd=1和fd=2都发生可读事件,则select返回,set变为0000 0011,fd=5没有事件发生将被清空
readfds、writefds、exceptfds同时作为输入参数和输出参数。
返回值>0表示就绪描述字的正数目,-1表示出错,0表示超时。
2.select模型特点
- 可监控文件描述符的个数取决于sizeof(fd_set)的值。
- 将fd加入select监控集的同时,还要再使用一个数据结构array保存存放到select监控集中的fd。
- select需要在select前循环array(加fd,取maxfd),返回后循环array(FD_ISSET判断是否有事件发生)
select缺点
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd多的时候开销大
- 每次调用select都需要在内核遍历传递进来的fd,当fd多的时候开销也很大
- select支持的文件描述符数量太小,32位默认是1024.
三、poll函数
poll和select本质上没有区别,它将用户传入的数据拷贝到内核空间,然后查询,然后查询每个fd对应的设备状态,若有设备就绪就在设备的等待队列中加入一项并继续遍历,若遍历完所有的fd后没有发现就绪就被,则挂起等待,直到设备就绪或主动超时,被唤醒后又要遍历fd,这个过程经历多次遍历。
select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket,然而,同时连接的大量客户端在同一时刻是由很少处于就绪状态,随着监视描述符的数量增长,其效率也会下降。
1、poll()
四、epoll函数
1、epoll是什么
相对于select和poll,epoll更加灵活,没有描述符限制,epoll用一个文件描述符管理多个,描述符,将用户关系的文件描述符事件存放到内核的一个事件表中,这样在用户空间和内核空间拷贝只需要一次。
epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程有哪些fd刚刚变为就绪态,并且只通知一次。
epoll使用“事件 ”的就绪通知方式,通过epoll_ctl来注册fd,一旦fd就绪,内核采用类似calback的回调机制来激活fd,epoll_wait收到通知。
2.epoll优点:
1.没有最大并发连接限制,能打开的fd上限远大于1024.
2.效率提升,不是轮询的方式,不会随着fd数目的增加效率下降,只有活跃的fd才会调用callback。即epoll只管理活跃的连接,跟连接的总数无关,所以epoll会比select、poll效率高
3.内存拷贝,利用mmap()文件映射内存加速与内核空间消息传递。即epoll使用mmap减少复制开销
3、epoll文件描述符的操作模式
LT(水平触发)和ET(边缘触发)。LT是默认模式。
1.LT模式:
当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件,下次调用epoll_wait时,再次相应应用程序并通知事件。
LT是缺省的工作方式,同时支持block和no-block socket。内核告诉你有一个文件描述符是否就绪,然后你可以不做任何操作,内核还会继续通知。
2.ET模式:
当epoll_wait检查到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理,若不处理,下次调用epoll_wait时,不会再次响应应用程序并通知次事件。
ET是高速工作方式,只支持no-block socket(以避免由于一个文件句柄阻塞操作把处理多个文件描述符的任务饿死)。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告知,并且假设你知道文件描述符就绪,然后不会再为这个文件描述符发送更多的就绪通知,直到你做了某些操作导致这个文件描述符不再为就绪状态。
ET模式在很大程序上减少了epoll事件被触发的次数,因此效率要比LT高。
4.epoll函数
①epoll_create函数
创建一个epoll句柄,当创建好epoll句柄后,它会占用一个fd值,所以在使用完epol后,必须调用close()关闭,否则可能会导致fd耗尽。
②epoll_ctl()
epoll事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而epoll是在这里先注册要监听的事件类型。
- 第一个参数是epoll_create的返回值
- 第二个参数是表示动作,由三个宏表示:
-
- EPOLL_CTL_ADD:注册新的fd到epfd中
- EPOLL_CTL_MOD:修改已经注册的fd监听事件
- EPOLL_CTL_DEL:从epid中删除一个fd
- 第三个参数是需要监听的fd
- 第四个参数是告诉内核需要监听什么事。
③epoll_wait
搜集epoll监控事件中已经发生的事件。参数events是分配好的epoll_event结构体数组epoll把发生的事件赋值到events数组中。maxevent告知内核这个event有多大,这个maxevents的值不能大于创建epoll_create()时的size。timeout是超时时间(0会立刻返回,-1表示永久阻塞,大于0 表示过多长时间返回)
函数调用成功但会对应IO已准备好的文件描述符数目,若返回0表示已超时
五、比较select、poll、epoll
1.支持一个进程所能打开的最大连接数
2.FD剧增后带来的IO效率问题
3.消息传递方式
- 表面上看epoll性能最好,但是在连接数少并且连接都十分活跃的情况下,select和poll的性能可能会好于epoll,因为epoll通知机制需要很多函数回调
- select低效是由于它每次都需要轮询,但是低效率也是相对的。