学习目的
打造网络知识的体系
学习方法
- 网络的初步了解 (DOING)
- 极客时间—— 趣谈网络协议
- netty api的熟悉和了解
- netty源码的学习
- netty demo (自研IM)
IO演进之路
下面讲述IO的演进之路,从简到繁,实现的复杂度越高,优化程度越明显。
铺垫
在unix系统中,一切皆文件(fd)
1、BIO 阻塞性IO
最原始的IO方式,客户端每次调用都要消耗服务端一个线程,服务端会被阻塞。在进程空间去,会去调用系统提供的recefrom函数,系统会一直被阻塞。直到网络套接字Socket的数据到达或者发送错误,系统才会从阻塞状态中唤醒。也就是说,系统从收到recefrom函数 -> 返回网络套接字,程序都是一直被阻塞的。
其示意图如下:
思考:
BIO的处理模型,设计非常简单,但是无法支撑高并发连接,如果面对成千上万的客户端连接,服务端的线程资源不足,很容易就会到达连接上限。
2、NIO 非阻塞性IO
系统在收到recefrom函数时候,不会阻塞,而是返回报错标志(EWOURLDBLOCK),系统可以去轮询检查这个报错标志的状态,一旦没有报错的标志,证明网络络套接字Socket的数据到达或者发送错误。
其示意图如下:
思考:
这种非阻塞IO的好处很明显,服务端程序使用recefrom函数调用后,如果从系统内核拿到的是报错标志,服务端程序可以开启一个后台线程一直轮询这个标志,在此期间,服务端程序不用一直阻塞,可以用其他线程去处理剩余的程序,等后台线程轮询的结果为成功,再去处理Socket返回的数据或者错误。
但是缺点也很明显,应用程序要额外去处理连接,并且处理的大小有限,因为应用程序的线程资源是有限的。无法处理过多的请求。
3、信号驱动IO (重点解析)
应用程序通过, linux系统提供的select/poll 方法,将套接字传给linux系统,然后应用系统会阻塞在select/poll 上,等待linux接受到网络套接字socket后,这个时候应用程序会被linux系统唤醒,调用recefrom的方法, Linux程序这个时候会把数据从内核态copy到用户态,应用程序就能拿到对应的socket套接字进行业务的操作了。
到达这里就会引入面试的热点, select模型和epoll模型,其实这两个模型都是基于信号驱动IO的一种实现,通过信号驱动的方式来进行通知应用程序。
先说结论: epoll模型是select模型的升级版本,主要是用来解决select模型查询低效的问题,select模型要轮询所有的套接字socket,时间复杂度为o(n),在面对成千上万的socket连接,效率会显得过慢,因此select也有1024个套接字socket的限制。 而epoll突破这种1024的限制,支持成千上万的连接,效率尤为高效。
3.1 select模型
其示意图如下,可以直接从第六步开始即可,select调用的逻辑。
可以看到select模型工作流程如下:
-
- 应用程序在等待socket的返回之时,进程会被进行阻塞,被系统进行挂起。
-
- 这时候远程的数据返回,socket数据准备就绪,会给进程去发一个通知信号,唤醒等待中的进程
-
- 进程马上去轮询,fd数组中,哪些socket已经准备好了
-
- 应用程序拿到需要的数据进行对应的程序处理。。。。
3.2 epoll模型
我们可以看到,因为select需要取轮询fd数组,在面对成千上万的连接,效率显得尤为低效,操作系统为了解决这种问题,提出了epoll的解决方案。
epoll是通过增加一个新的对象,eventpoll对象将socket存储在当中,具体的eventpoll数据结构是双向链表 + 红黑树。
当应用程序在等待socket返回的时候,系统会将socket保存到eventpoll对象中的等待队列上,这时候,应用程序也会被添加到 等待队列 上。
PS: 这里可以看到,所有的socket,应用程序的操作都在这个eventpoll对象上完成
这时候,第三方应用返回socket数据,会唤醒eventpoll对象中的某一个等待数据的 socket, 并且也会唤醒对应的进程, 将他们添加到eventpoll的就绪列表中。因为应用程序和准备好的socket都在就绪列表中,因此应用程序是清楚知道哪个socket是就绪好了的。 OK,这时候再对对应的socket进行数据操作。。。
4. AIO
AIO, 全异步回调,应用程序在等待socket返回的时候,无需阻塞进程,直接由操作系统将数据从内核态返回给用户态,应用程序只需要收回调即可。