io模型
原图连接:https://download.****.net/download/qq_31443653/10707615
io
发起任务后,本身的一个状态
同步
异步
等待结果的过程中的状态
阻塞
非阻塞
处理请求的过程
服务器的网络驱动接收到消息,去内核上申请空间;并等待完整的数据包到达(有可能分组传送,没传完…),复制到内核空间;
数据从内核空间拷贝到用户空间
用户程序进行处理
5大网络模型(小红旗 餐馆排队)
同步阻塞IO
- 排队的过程哪也去不了,如果你还没有带手机,排队的过程中就只能干瞪眼了
同步非阻塞
- 如果小红旗的老板搞了一个点菜机,来点单的顾客把自己想吃的划上,然后等着老板去做,自己可以在这一个小时的时间里去周围商场溜达下。但是由于没有任何通信方式,只能不停的回来问老板,做好了没有。
- 回来询问的时间是由顾客自己掌控的,如果时间很短,那么可以尽量早的知道臭豆腐炸好没,但是也会影响逛街的体验;如果时间很长,有可能臭豆腐早就做好了…结果放的时间长了,反而不好吃了。
- 因此非阻塞IO基于状态轮训的方式,虽然能让程序在等待的过程中做点其他的事情,但是频繁的切换运行程序,反而会造成很大的压力。
IO多路复用/事件驱动
- 小红旗老板升级了系统,放弃使用点菜机,改用麦当劳那种点餐大屏。同样是点餐,但是一个大屏里面显示了很多人的臭豆腐进度,即节省了资源,也避免大家不停的询问。
- 其实Nio或者Netty就是基于这种模式,一个线程就可以监听很多IO操作,这样在IO等待上就高效多了。具体实现是依赖于操作系统的,windows和linux都有不同的实现方式。最初的select或者poll,都有并发数的限制,并且NIO的select还有空轮训的问题;epool则突破了连接数的限制,一个线程就可以监听大量的IO操作。这个感兴趣的朋友,可以深入了解下select、poll、epool的原理。
- 都是IO复用,I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
- io触发 LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
- LT(level triggered)
- 同时支持block和no-block socket内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的
- ET (edge-triggered)
- 只支持no-block socket,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了 (only once)
- LT(level triggered)
- I/O多路复用
- poll
- select
- 缺点
- (1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
- (2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
- (3)select支持的文件描述符数量太小了,默认是1024
- 缺点
- epoll
- epoll_create
- 创建一个epoll的句柄
- epoll_ctl
- 它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
- 每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
- poll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表
- epoll_wait
- 在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)
- 消息传递
- 使用mmap加速内核与用户空间的消息传递
- 它所支持的FD上限是最大可以打开文件的数目
- epoll_create
- io触发 LT模式只要有事件未处理就会触发,而ET则只在高低电平变换时(即状态从1到0或者0到1)触发。
- 都是IO复用,I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
- 其实Nio或者Netty就是基于这种模式,一个线程就可以监听很多IO操作,这样在IO等待上就高效多了。具体实现是依赖于操作系统的,windows和linux都有不同的实现方式。最初的select或者poll,都有并发数的限制,并且NIO的select还有空轮训的问题;epool则突破了连接数的限制,一个线程就可以监听大量的IO操作。这个感兴趣的朋友,可以深入了解下select、poll、epool的原理。
信号驱动IO
- 小红旗老板又时髦了,搞了一个升级版的美味不用等。顾客基于微信小程序点菜,菜做好了自动提醒顾客取餐…这个提醒的过程,就像是发射了一个特殊的信号。
异步非阻塞IO
- 一对小情侣李雷和韩梅梅,韩梅梅口味很重,特别喜欢吃臭豆腐,但是李雷完全不感兴趣,闻到味道就想吐。于是李雷就跟韩梅梅约定,让韩梅梅自己去吃,李雷跑到旁边的咖啡厅喝咖啡。韩梅梅自己去排队买臭豆腐,买完顺便吃完,然后回来找李雷…
- 这个过程就是异步非阻塞的,消息的等待和处理都在服务器端完成,用户只要最后接收到消息处理完的通知就行了。强调事情已经做完
nio
thread
- selector
- channel
- buffer
- capacity 容量
- limit 剩余
- position 位置
- mark 标记位
- flip(初始化为写模式,切换到读模式)
- buffer
- channel1
- channel2
- channel
XMind: ZEN - Trial Version