热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

大白话详解5种网络IO模型

1前言我们都知道,为了实现高性能的通信服务器,BIO在高并发的情况下会出现性能急剧下降的问题,甚至会由于创建过多线程而导致系统OOM。因此在Java业界,BIO的性能问题一直被开发

1 前言

我们都知道,为了实现高性能的通信服务器,BIO在高并发的情况下会出现性能急剧下降的问题,甚至会由于创建过多线程而导致系统OOM。因此在Java业界,BIO的性能问题一直被开发者所诟病,所幸的是,JDK1.4推出了NIO,NIO基本解决了BIO的性能问题,是目前实现Java高性能服务器的基础框架。NIO官方的叫法叫做New IO,而对应于操作系统层面来说其实也是Non-Blocking IO。

大名鼎鼎的Netty就是NIO框架,而目前很多开源框架比如Dubbo,RocketMQ,Seata,Spark,Flink都是采用Netty作为基础通信组件。因此,学好Netty很重要,但是NIO作为Netty的基础,这里想说的是学好NIO也一样重要!

学好NIO,那么必须先理解操作系统层面的5种网络IO模型。


2 5种IO模型
2.1 阻塞IO模型

阻塞IO模型如下图:

大白话详解5种网络IO模型

 

从上图可以看到,不管有无数据报到来,进程(线程)是阻塞于 recvfrom 系统调用的。这是什么意思呢?说白了就是假如我们要用套接字读取数据,此时我们必然会调用 read 方法,此时这个 read 方法就会触发操作系统内核的一次 recvfrom 系统调用,此时有两种情况:


  1. 内核还未接收到远端数据,此时数据报没有准备好,那么读取数据的线程就会一直阻塞,直到远端发来数据报,这一阻塞的过程对应上图序号1的过程;然后在数据报被从内核复制到用户空间这一过程中,该线程会再次阻塞,直到复制完成,这一过程对应上图的序号2的过程;

  2. 内核已经接收到远端数据,此时数据报已经准备好,那么数据报就会被从内核复制到用户空间,这一过程是阻塞的,对应上图序号2的过程。

可见,阻塞IO模型的话,读一次数据会发生一次 recvfrom 系统调用,整个过程都是阻塞的,即在内核的数据报还未准备好的时候,此时用户进程( 线程)阻塞;当内核的数据报准备好的时候,此时数据报要从内核拷贝到用户空间,此时用户进程(线程)也一直阻塞;直到数据报拷贝到用户空间后,此时用户进程(线程)才会醒过来,然后处理这些数据报即执行一些用户的业务逻辑。当然,如果用户进程(线程)在阻塞过程中,如果 recvfrom 系统调用被信号中断,此时阻塞也是会被唤醒的。

思考:这里的 recvfrom 系统调用被信号中断什么情况下会发生?这个信号中断指的是线程中断( Thread.interrupt() )么?自行思考。


2.2 非阻塞IO模型

非阻塞IO模型如下图:

大白话详解5种网络IO模型

 

如上图,根据内核中的数据报有无准备好,有以下两种情形:


  1. 当内核中的数据报还没准备好,此时 recvfrom 系统调用立即返回一个EWOULDBLOCK 错误,即不会将用户进程(线程)置于阻塞状态。我们拿Java的NIO来说,当我们配置 ServerSocketChannel.configureBlocking(false); 或SocketChannel..configureBlocking(false); 时,我们调用ServerSocketChannel.accept() 的 null 或 SocketChannel.read(buffer) 不会阻塞的,若没有新连接接入或内核中没有数据报准备好,此时会理解返回 null 或 0 的返回结果,说白了这个返回结果就是对应 EWOULDBLOCK 错误;

  2. 当内核中的数据报已经准备好时,此时 recvfrom 系统调用,用户进程(线程)还是会阻塞,直到内核中的数据报已经拷贝到了用户空间,此时用户进程(线程)才会被唤醒来处理接收的数据报。

非阻塞IO在用户数据报还没准备好的时候, recvfrom 系统调用不会阻塞,接着会继续进行下一轮的 recvfrom 系统调用看数据报有无准备好,周而复始,进程(线程)不断轮训,因此这是非常耗费CPU的。这种模型不是很常用,适合用在某台CPU专为某些功能准备的场合。


2.3 IO复用模型

IO复用模型如下图:

大白话详解5种网络IO模型

 

初步从以上IO复用模型来看,这不是跟IO阻塞模型差不多么?当内核无数据报准备好时,select系统调用会阻塞;当内核数据拷贝到用户空间时,此时 recvfrom 系统调用依然会阻塞,实在是看不到跟IO阻塞模型有啥区别?区别就是IO复用模型还比阻塞IO模型还多一次recvfrom 系统调用,这不是明摆着多浪费一次CPU资源么?

如果我们这么想,那为什么IO复用模型得到大规模广泛应用呢?其实IO复用模型真正占优势的地方在于 select 操作,这个 select 操作可以选择多个文件描述符,分别对应Java NIO中的 OP_CONNECT , OP_ACCEPT , OP_READ 和 OP_WRITE 就绪事件。正是基于 一次recvfrom系统调用中一个线程的select操作可以选择多个文件描述符 这个功能,我们现在用一个用户线程就能监听不同 channel 的 OP_CONNECT , OP_ACCEPT , OP_READ 和OP_WRITE 这些就绪事件,然后根据某个就绪事件拿到相应的 channel 来做对应的操作。而不用像 阻塞IO模型或非阻塞IO模型 那样, 一次recvfrom系统调用中一个线程就只能选择一个文件描述符 ,这样就严重限制了伸缩性。这么说很抽象,就比如拿阻塞IO模型来说,由于用户进程(线程)每一次 recvfrom 系统调用都是阻塞且只对应一个文件描述符,此时如果服务端线程阻塞于客户端A的读操作时,如果有另外的客户端B需要接入服务端,此时服务端线程由于阻塞于客户端A的读操作,因此无法处理客户端B的连接操作。此时,必然要一个线程一个文件描述符即服务端线程每 accept 了一个客户端连接,此时就需要新建一个线程去处理这个客户端连接的读写操作。我们都知道,线程是一种很昂贵的CPU资源,当开启成千上万的线程后,线程切换的成本很高,CPU性能肯定下降,说不定高并发下还会OOM。说到这里,也许有同学会说,对于阻塞IO模型,我们不一个线程一个socket,用线程池替代,当然,这是一个优化的点,但没解决阻塞IO模型的根本。怎么说呢?当线程池的所有线程都阻塞于客户端的读或写操作时,此时其他新接入的线程将会积压在线程池的队列中阻塞等待。


2.4 信号驱动IO模型

信号驱动IO模型如下图:

大白话详解5种网络IO模型

 

可见,信号驱动IO模型在等待数据报期间是不会阻塞的,即用户进程(线程)发送一个sigaction 系统调用后,此时立刻返回,并不会阻塞,然后用户进程(线程)继续执行;当数据报准备好时,此时内核就为该进程(线程)产生一个 SIGIO 信号,此时该进程(线程)就发生一次 recvfrom 系统调用将数据报从内核复制到用户空间,注意,这个阶段是阻塞的。

PS:网上找了下信号驱动IO模型的java代码,没找到,会码信号驱动IO模型代码的小伙伴们可以教教我。


2.5 异步IO模型

异步IO模型如下图:

大白话详解5种网络IO模型

 

异步IO模型也很好理解,即用户进程(线程)在等待数据报和数据报从内核拷贝到用户空间这两阶段都是非阻塞的,即用户进程(线程)发生一次系统调用后,立即返回,然后该用户进程(线程)继续往下执行。当内核把接收到数据报并把数据报拷贝到了用户空间后,此时再通知用户进程(线程)来处理用户空间的数据报。也就是说,这一些列IO操作都交给了内核去处理了,用户进程无须同步阻塞,因此是异步非阻塞的。

扩展:异步IO模型跟信号驱动IO模型的区别在于当内核准备好数据报后,对于信号驱动IO模型,此时内核会通知用户进程说数据报准备好啦,你需要发起系统调用来将数据报从内核拷贝到用户空间,此过程是同步阻塞的;而对于异步IO模型,当数据报准备好时,内核不会再通知用户进程,而是自己默默将数据报从内核拷贝到用户空间后然后再通知用户进程说,数据已经拷贝到用户空间啦,你直接进行业务逻辑处理就行。


3 各种IO模型区别

大白话详解5种网络IO模型

 

通过5种IO模型的比对,可以发现,前4 种 IO模型都是同步阻塞IO模型,因为其第二阶段数据报从内核拷贝到用户空间都是同步阻塞的,只是第一阶段等待数据报的处理不同; 最后一种IO模型(异步IO模型)才是真正的异步非阻塞IO模型,内核将一切事情都干完(内核: 我真的好累)。


4 总结

好了,五种IO模型基本就已经总结完了,基本是自己基于《UNIX网络编程_卷1_套接字》的读书总结


原文链接:https://mp.weixin.qq.com/s?__biz=MzAwMDczMjMwOQ==&mid=2247484159&idx=1&sn=62a1626a748547ad837f9c6ccc10b11a


如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!



推荐阅读
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 基于事件驱动的并发编程及其消息通信机制的同步与异步、阻塞与非阻塞、IO模型的分类
    本文介绍了基于事件驱动的并发编程中的消息通信机制,包括同步和异步的概念及其区别,阻塞和非阻塞的状态,以及IO模型的分类。同步阻塞IO、同步非阻塞IO、异步阻塞IO和异步非阻塞IO等不同的IO模型被详细解释。这些概念和模型对于理解并发编程中的消息通信和IO操作具有重要意义。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了操作系统的定义和功能,包括操作系统的本质、用户界面以及系统调用的分类。同时还介绍了进程和线程的区别,包括进程和线程的定义和作用。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了软件测试知识点之数据库压力测试方法小结相关的知识,希望对你有一定的参考价值。 ... [详细]
  • HashMap的相关问题及其底层数据结构和操作流程
    本文介绍了关于HashMap的相关问题,包括其底层数据结构、JDK1.7和JDK1.8的差异、红黑树的使用、扩容和树化的条件、退化为链表的情况、索引的计算方法、hashcode和hash()方法的作用、数组容量的选择、Put方法的流程以及并发问题下的操作。文章还提到了扩容死链和数据错乱的问题,并探讨了key的设计要求。对于对Java面试中的HashMap问题感兴趣的读者,本文将为您提供一些有用的技术和经验。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
author-avatar
云姵肇
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有