NIO TODO 归档到Linux下
结合Linux 了解socket原理 什么多路复用 selector epoll poll
时间: 1个星期(不知道什么时候创建的这些文件,反正感觉很久了,今天2019-11-14先初探一把),学习参照
https://www.jianshu.com/p/486b0965c296
https://www.jianshu.com/p/aed6067eeac9
https://juejin.im/post/5c725dbe51882575e37ef9ed
https://woshijpf.github.io/linux/2017/07/10/Linux-IO%E6%A8%A1%E5%9E%8B.html
https://tech.youzan.com/yi-bu-wang-luo-mo-xing/
背景知识一
同步与异步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列
。
如果异步调用,调用方获取异步任务结果方式
设置共享数据,执行方执行完毕后将结果设置到该共享数据中,调用方不断轮询查询该共享数据
执行方执行完毕后通知调用方
执行方执行完毕后通过回调函数将结果告知调用方(这不也是一种通知方式吗?)
阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知,不能够执行其他业务
。函数只有在得到结果之后才会返回。
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回
。虽然表面上看非阻塞的方式可以明显的提高CPU的利用率,但是也带了另外一种后果就是系统的线程切换增加
。增加的CPU执行时间能不能补偿系统的切换成本需要好好评估
。
同步/异步讨论的是调用方获取任务执行结果的方式
阻塞/非阻塞讨论的是调用方在等待任务结果时是否还能干其他事
按照上面两种维度来组合,就会有 同步阻塞, 异步阻塞 ,同步非阻塞 和异步非阻塞四种实现方案
背景知识二
用户空间与内核空间
在32位操作系统中,寻址空间(虚拟地址)是2^32(4G),为了保证内核安全,禁止用户进程直接操作内核,操作系统将虚拟地址空间划分为两部分,将0xC0000000到0xFFFFFFFF(1G)划给内核使用,称为内核空间(也就是内核态?),剩余的0x00000000到0xBFFFFFFF(3G)划分给用户进程使用,称为用户空间(也就是用户态?)
那64位操作系统岂不是可以管理2^64(17179869184G)?理论上是这样的,但实际中目前只用到了48位(262144G)
https://www.zhihu.com/question/28638698
进程阻塞
正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进程(获得CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用CPU资源的
。
Linux IO模型
网络IO的本质是socket的读取,socket在linux系统被抽象为流,IO可以理解为对流的操作
。
像Java世界中切皆对象一样,Linux中一切皆文件。socket也是一个文件又是怎么体现的?
https://blog.csdn.net/kingshown_WZ/article/details/52103327
https://blog.csdn.net/YEYUANGEN/article/details/6799575
对于tcp传输,需要源IP/端口,目标IP/端口四元组,每创建一个连接(socket?),客户端端内核会随机分配一个端口,但是服务端只有一个端口,那对于多个连接,服务端是怎么确定该数据包属于一个进程中的哪个连接呢?
对于一次IO访问(比如read),数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。所以,当一个read操作发生时,它会经历两个阶段:
- 第一阶段:等待数据准备
- 第二阶段:将数据从内核复制到进程中
对于socket流而言,
- 第一步:等待网络上的数据分组到达,然后被复制到内核的某个缓冲区
- 第二步:将数据从内核缓冲区复制到应用进程缓冲区
网络IO模型有以下5中
- 同步阻塞IO Blocking IO
- 同步非阻塞IO Non-Blocking IO
- 多路复用IO Multiplexing IO
- 信号驱动IO Signal Driven IO(实际中不常用)
- 异步IO Asynchronous IO
一、Blocking IO
在这个模型中,用户进程执行一个系统调用(recv/recvfrom),这会导致进程阻塞(不占用CPU时间片),直到数据准备好,并将数据复制到进程缓冲区,recv/recvfrom函数返回结果,最后进程再处理数据。这种模型从数据处理角度来讲是最及时的,因为数据被复制到进程缓冲区后,进程能及时处理。处理流程图如下:
应用进程在两个阶段都被阻塞了
关于recv和recvfrom的区别,见https://www.cnblogs.com/p2liu/archive/2013/04/17/6048755.html
二、Non-Blocking IO
以非阻塞模式打开,每隔一段时间调用recv/recvfrom,如果数据未准备好,会立即返回一个错误码。这个过程称为轮询。应用进程调用recv/recvfrom时是会被阻塞的,但是在两次调用之间是进程是未被阻塞的,可以获取CPU时间片。从内核角度来看,当收到recv/recvfrom调用时,如果数据未准备好,则立即返回错误码,如果数据已经准备好了,则将数据复制到进程缓冲区。
跟同步阻塞模型相比,同步非阻塞模型
优点:在轮询期间可以执行其他任务
缺点:因为是固定时间间隔轮询的(并且这个间隔不会太短),所以数据可能会在两次查询间隔时就已经准备好了,会导致响应时间加大,吞吐量下降。
三、Multiplexing IO
在讨论前面两种IO模型时,都是基于单个socket来讨论的,实际上一个系统同时会有多个socket(属于同一个用户进程或多个用户进程)。每个socket都需要轮询或被阻塞,如果有单独一个人(这里我不知道用进程、线程还是什么其他东西来描述更准确,等后续再来完善吧),如果数据准备好了,告诉用户进程,在这之前用户进程可以安心干其他事就好了。Linux下的select,poll,epoll就是来干”查看“的工作的,如果查询得知数据已经准备好了,就通知用户进程,用户进程再调用recv/recvfrom来获取数据
select ,poll,epoll三者功能是一样的,但是epoll是select和poll的改进版,具体区别见https://www.cnblogs.com/anker/p/3265058.html
我的理解,所谓的多路复用,就是将”查询数据状态“和”复制数据“两个操作分开来,并”查询操作“实现了批量化,可以一次查询多个socket的数据状态,将已经准备好的socket相关信息返回回来,用户进程再单独调用recv/recvfrom。跟同步非阻塞模型相比,节省了查询所做的工作量。
select 操作还是由用户进程调用,并且调用过程中也会被阻塞,一旦有一个socket的数据准备好了就会返回。来个不准确的总结,多路复用将部分工作批量化了,单个的操作跟同步非阻塞模型来说类似。
因此,对于单个socket来说,多路复用并没有什么优势,其优势体现在同时处理多个socket场景下,能实现高并发,什么C10K场景,比如Nginx,Redis?
从整个IO流程来看,前面三种模型都是用户进程主动等待并且向内核查询数据状态,因此三者都归为同步模型*
四、Asynchronous IO
相对于同步IO,异步IO不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情
。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知
。IO两个阶段,进程都是非阻塞的
。
使用场景
五、Signal Driven IO
暂时不了解了