一:概念的理解:
首先在网络编程的时候,我们常常见到同步/异步,阻塞/非阻塞四中调用方式:
1同步和异步主要针对C(client)端
同步:
所谓的同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用步返回,也就是说必须一件一件事做,等前一件事完了之后才做后一件事。
如:普通的B/S模式(同步):提交请求->等待服务器处理->处理完毕返回,这期间客户端浏览器不能干任何事
异步:
与同步相对。当C端一个异步过程调用发出之后,调用者不能立即得到结果,实际处理这个调用的部件在完成后,通过状态,通知和回调来通知调用者。
如:请求通过事件触发->服务器处理(浏览器仍然可以做其他事情)->处理完毕
阻塞和非阻塞主要针对S端(server)
阻塞
阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态,cpu不会分配时间片,线程暂停运行)函数只有得到结果返回。
阻塞调用和同步调用的区别:对同步来说,很多时候当前线程还是激活的,只是逻辑上没有返回,如,在socket编程中调用recv函数,如果缓冲区没有数据,这个函数就会一直等待,直到有数据返回。而此前当前线程还有可能继续处理各种各样的消息。
阻塞的例子:比如去取A楼一层(假设是内核缓冲区)取快递,但是比不知道什么时候来,你有不能干别的事情,只能死等着但是可以睡觉(进程处于休眠状态),因为你知道快递把货送来时一定会给比大电话
非阻塞:
非阻塞与阻塞概念想对应,指在不能立即得到结果之前,该函数不会阻塞当前线程,而会立即返回。
非阻塞的例子:还是等快递,如果用轮询的方式,每隔5分钟去A楼一层(内核缓冲区)去看快递来了没,没来,立即返回,如果快递来了,就放到A楼一层,等你去取。
对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但不是一一对应的。阻塞对象上可以有非阻塞的调用方式,我们可以通过轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞,而对于非阻塞对象,调用函数可以进入阻塞调用,对于select:
1:同步
我客户端(C端调用者)一个功能,该功能没有结束前,我死等结果。
2:异步,我(c端调用者)调用一个功能,不知道该功能结果,该功能有结果后通知我,即回调通知
同步和异步主要针对c端,但是跟s端不是完全没关系,同步和异步必须s端配合才能实现,同步和异步由c端控制,但是s端是否为阻塞还是非阻塞,c端不关心。
3:阻塞,就是调用我(s端被调用者,函数),我(s端被调用者,函数)没有完全接受完数据或者没有得到结果之前,我不会返回。
4:非阻塞,就是调用我(s端被调用者,函数),我(s端被调用者,函数)立即返回,通过select通知调用者
二:同步I/O和异步I/O
同步I/O与异步I/O的区别在与数据访问的时候进程是否阻塞
阻塞I/O与非阻塞I/O的区别在与:应该程序的调用是否立即返回。
阻塞和非阻塞是指server端的进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时时直接返回还是等待就绪。
就同步和异步是指client端访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后再读写额时候必须阻塞,异步则指主动请求数据后便可以继续处理其他任务,随后等待I/O,操作完毕的通知。
三:五种I/O模型:
1)阻塞I/O
2)非阻塞I/O
3)I/O复用(select和poll)
4)信号驱动I/O(SIGIO)
5)异步I/O
前四中是同步,最后一种是异步。
阻塞I/O模型:
简介:进程会一直阻塞,直到数据拷贝完成
应用程序调用一个I/O函数,导致应用程序阻塞,等待数据准备好,如果数据没有准备好,一直等待。。数据准备好,从内核拷贝到用户空间,I/O函数返回成功
阻塞I/O模型图:在调用recv()/recvfrom()函数,发生在内核中等待数据和复制数据过程。
当调用recv()函数时,系统首先检查是否有准备好的数据,如果数据没有准备好,那么系统就处于等待状态,当数据准备好后,将数据从系统缓冲区复制到用户空间,然后函数返回。在套接应用程序中,当调用recv()函数时,未必用户空间就已经存在数据,那么此时recv()函数处于等待状态
非阻塞I/O模型:
简介:我们把一个套接口设置为非阻塞就是告诉内存,当所请求的I/O操作无法完成时,不要进程睡眠,而是返回一个错误,I/O函数会不断的测试数据是否准备好,没有准备好,继续测试,直到数据准备好为止。在测试的过程中会占用大量的CPU时间。
I/O多路复用模型:
简介:主要是select和epoll;对于一个I/O端口,两次调用,两次返回,比阻塞I/O并没有什么优势,只是能实现同时对多个I/O端口进行监听。
I/O多路复用模型会调用select,poll函数,这几个函数也会使进程阻塞,但是和阻塞I/O不同的,这个函数可以同时阻塞多个I/O操作,而且可以同时对多个读操作,多个写操作的I/O函数进行检测,直到有数据可读或可写时,才真正调用I/O操作函数。
IO多路复用适用场合:
当客户端处理多个描述符时(请求)(一般是交互式输入和网络套接口),必须使用I/O复用
当一个客户端同时处理多个套接字时,此情况可能的但很少出现
当一个TCP服务器既要处理监听套接字,又要处理已连接套接字,一般也要用到I/O复用 (使用最多的场景)
当一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用
当一个服务器要处理多个服务或多个协议,一般要使用I/O复用
信号驱动I/O
简介:两次调用,两次返回
首先允许套接口进行信号驱动I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
异步I/O模型:
简介:数据拷贝的时候进程无需阻塞
当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态,通知和回调通知调用者输入输出操作。
四:五种I/O模型的比较
五:I/O模型的具体实现
主要实现方式有以下几种:
Select:Linux实现对应,I/O复用模型,BSD4.2最早实现
Poll:Linux实现,对应I/O复用模型,System V unix最早实现 (改进版的select)
Epoll:Linux实现,对应I/O复用模型,具有信号驱动I/O模型的某些特性
Kqueue:FreeBSD实现,对应I/O复用模型,具有信号驱动I/O模型某些特性
/dev/poll:SUN的Solaris实现,对应I/O复用模型,具有信号驱动I/O模型的某些特性
Iocp Windows实现,对应第5种(异步I/O)模型
六:select/poll/epoll的比较:
1,select
优点
Select:POSIX所规定,目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理
缺点
单个进程可监视的fd数量被限制,即能监听端口的数量有限 cat /proc/sys/fs/file-max
对socket是线性扫描,即采用轮询的方法,效率较低
select 采取了内存拷贝方法来实现内核将 FD 消息通知给用户空间,这样一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
2, poll
本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态
其没有最大连接数的限制,原因是它是基于链表来存储的
大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义
poll特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd (通知多次)
边缘触发:只通知一次,性能更好
3,epoll
在Linux 2.6内核中提出的select和poll的增强版本
优点:
支持水平触发LT和边缘触发ET,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次
使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
没有最大并发连接的限制:能打开的FD的上限远大于1024(1G的内存能监听约10万个端口)
效率提升:非轮询的方式,不会随着FD数目的增加而效率下降;只有活跃可用的FD才会调用callback函数,即epoll最大的优点就在于它只管理“活跃”的连接,而跟连接总数无关
内存拷贝,利用mmap(Memory Mapping,内存映射)加速与内核空间的消息传递;即epoll使用mmap减少复制开销