BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(上)

文章目录

    • 文章引入
    • IO模型及概念梳理
    • BIO
      • 简单介绍
      • 代码样例
      • 压测结果
    • NIO(单线程模型)
      • 简单介绍
      • 与BIO的比较
      • 代码样例
      • 压测结果
    • 多路复用器
      • 问题引入

文章引入

如果你对BIO、NIO、多路复用器有些许疑惑, 那么这篇文章就是肯定需要看的, 本文将主要从概念, 代码实现、发展历程的角度去突出并解决各个IO模型问题, 并用实际代码样例进行演示, 使你更加直观.

本文在介绍各个IO模型后, 都将给出样例代码接收处理5000个连接, 打印出各自处理需要的时间, 各自效率一览便知.

IO模型及概念梳理

Java中的IO模型主要有三种BIO和NIO.AIO.其实主要是同步阻塞,同步不阻塞,异步不阻塞的区别。
这里的同步,异步,阻塞,非阻塞解释如下:

  1. 同步:当触发读写请求后,你的程序自己去读写数据, 即自己写代码去做读写这个动作,那么就是同步的.
    简单来说就是应用发送完指令要参与这个过程,直到返回数据.
  2. 异步:当触发读写请求后(这里不是去缓冲区读写,仅仅告诉内核我想去读取数据),直接返回,内核帮助你完成整个操作后,帮你将数据从内核复制到应用空间,再通知你.
    简单来说就是应用发送完指令不再参与整个过程,直接返回等待通知.
  3. 阻塞:试图对缓冲区进行读写时,当前不可读或者不可写(内核数据没有准备好的情况下),程序进行等待,直到可以操作。
  4. 非阻塞:试图对缓冲区进行读写时,当前不可读或者不可写,读取函数马上返回

看到这里你可能还是对这些概念似懂非懂, 没关系, 当你看到实际演示代码时就自然懂了

当然, 需要提一下, 本篇文章主要讨论同步阻塞和同步非阻塞, 异步非阻塞还没有实际运用起来, 比如Netty,Tomcat现在还依然是NIO. 所以AIO暂不在本文讨论范围内.

BIO

简单介绍

BIO, 即BLOCKING IO, 同步阻塞IO, 那它可能阻塞在哪里呢? 主要是以下两个步骤

  1. accept()
    当服务端准备好入等待客户端连接时, 也就是执行accept方法时, 会发生阻塞, 一直等到有客户端连接进来
  2. read()
    当创建好一个连接后, 这个socket对象去读取数据的时候, 会发生阻塞, 一直等到对方发送过来数据, 并且能够读取返回

代码样例

服务端代码 :

public class ServerSocketTest {public static void main(String[] args) {ServerSocket server = null;try {server = new ServerSocket();server.bind(new InetSocketAddress(9090));System.out.println("server up use 9090");long startTime = System.currentTimeMillis();int count = 0;while(true){try {//真正开启监听Socket client = server.accept();System.out.println("client port: "+client.getPort());new Thread(() -> {while(true){try {InputStream in = client.getInputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(in));char[] data = new char[1024];int num = reader.read(data);if(num>0){System.out.println("client read some data :"+new String(data));}else if(num==0){System.out.println("client read data nothing......");}else{System.out.println("client read data error......");client.close();}} catch (IOException e) {e.printStackTrace();}}}).start();if(++count >= 5000){System.out.println("处理5000个连接用时:"+(System.currentTimeMillis()-startTime)/1000+"s");break;}} catch (IOException e) {e.printStackTrace();break;}}} catch (IOException e) {e.printStackTrace();}finally {try {server.close();} catch (IOException e) {e.printStackTrace();}}System.exit(0);}
}

简单介绍:
上述代码每次拿到一个新的连接后, 会新new一个线程去进行真正的数据读取发送, 主线程就干一件事情, 监听建立新连接.

为什么要这样做呢?

因为如果都在一个线程里, 那么一个服务端只能处理一个客户端请求, 因为读请求也是阻塞的, 会影响到主线程监听建立新连接, 根本没法玩, 玩不动.

客户端代码:

public class C10K {public static void main(String[] args) throws InterruptedException {InetSocketAddress serverAddr = new InetSocketAddress("192.168.68.2", 9090);//线性发起10000个连接byte[] bytes = "hello".getBytes();ByteBuffer buffer = ByteBuffer.allocate(1024);for (int i = 10000; i < 15000; i++) {try {//建立连接SocketChannel client = SocketChannel.open();client.bind(new InetSocketAddress("192.168.68.248",i));client.connect(serverAddr);//发送数据,这里每次发送固定的,且只是单一的发送固定数据, 就共用一个buffer//正常复杂读写为每个连接单独维护buffer更安全可靠buffer.put(bytes);buffer.flip();client.write(buffer);buffer.clear();} catch (IOException e) {e.printStackTrace();}}System.out.println("连接都处理完毕,未避免程序停止关闭连接,这里直接等待......");Thread.sleep(50000);}
}

压测结果

直接用上述的客户端代码, 不断想服务端发起连接请求, 测试结果如下:
在这里插入图片描述

NIO(单线程模型)

简单介绍

NIO, 这个N有两层含义
对于JDK而言: 是New IO, jdk nio包的类既支持阻塞模型,也支持非阻塞模型
对于操作系统: 是 NO BLOCKING

我们这里讲的主要是操作系统层面的,即非阻塞IO, BIO会在accept()和read()方法阻塞, 而NIO则均不会阻塞, 会直接返回,

比如accept()方法, 如果有新连接, 就返回连接对象, 如果没有, 则立即返回null.这一点可以在后序的样例代码中体会到.

与BIO的比较

传统BIO的劣势: 每新来一个连接就要new一个线程, 非常非常耗费资源,支持不了数量庞大的连接.

NIO的优势 : 可以通过一个或几个线程来解决N个IO连接的处理.比如以下的样例, 处理5000个连接只用了一个线程.

代码样例

服务端代码:

public class SocketNIO {public static void main(String[] args) throws Exception {ArrayList<SocketChannel> clients = new ArrayList<>();//通道, 可读可写, 是JDK new io 中的工具类ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.bind(new InetSocketAddress(9090));//设置为非阻塞, 即no blocking, 当设置了这一步进行accept时便不会阻塞,会直接返回//如果设置为true, 就相当于BIOserverSocketChannel.configureBlocking(false);//ByteBuffer同时支持读写,有指针控制//写切换成读需要调用flip方法,就是将指针移动至未读的位置//读切换成写需要调用compact方法, 把读取过的字节往前覆盖掉,指针移动至还未写入数据的位置//这里可以采用堆内或者堆外的方式分配内存,  Direct就是堆外的方式ByteBuffer buffer = ByteBuffer.allocate(1024);//ByteBuffer allocate = ByteBuffer.allocateDirect(4096);long startTime = System.currentTimeMillis();while (true) {//这里如果没有接收到客户端, jdk api便会返回null//如果有, 便会返回相应对象SocketChannel client = serverSocketChannel.accept();if(client==null){continue;}//这里设置的是read不进行阻塞client.configureBlocking(false);int port = client.socket().getPort();System.out.println("add client port:"+port);clients.add(client);for (int i = 0; i < clients.size(); i++) {SocketChannel curClient = clients.get(i);//这里不阻塞,直接返回//没有数据的话直接返回-1int read = curClient.read(buffer);if(read<=0){continue;}//对于buffer,刚刚是写,现在进行读操作,调用flipbuffer.flip();byte[] bytes = new byte[buffer.limit()];buffer.get(bytes);String str = new String(bytes);System.out.println(curClient.socket().getRemoteSocketAddress()+" -->" +str);//一次循环之后又需要进行写入,这里直接clear清0,就无需调用compact方法buffer.clear();}if(clients.size() >=  5000){System.out.println("处理5000个连接用时:"+(System.currentTimeMillis()-startTime)/1000+"s");break;}}serverSocketChannel.close();}
}

测试使用的客户端代码还是和上面的一样, 这里不再放了.

压测结果

还是用和BIO相同的客户端代码, 压测结果如下:

可能会比较惊讶, 竟然比BIO用时还多? 那这个NIO究竟慢在哪里呢?
继续往下看就知道了, 这也是为什么会有多路复用器.

在这里插入图片描述

多路复用器

问题引入

  1. 从上面两个比较结果看, 同样处理5000个请求, 直接使用NIO竟然比BIO用时还久, 这个一方面是因为上述NIO代码只用了一个线程, 如果分摊到几个线程, 比如5个, 每个线程负责1000个连接, 那速度会快一些

  2. 第二个最主要的点是传统NIO每次都是主动调用,主动去查看有没有新连接, 对于每一个连接有没有新数据,
    每次循环的时间复杂度是0(n) n代表连接数, 也就是假如有10000个链接, 每次都要挨个循环一遍, 看看其缓冲区有没有准备好的数据, 并且这个操作(read)需要系统调用,调用内核级别的方法,涉及到用户态内核态切换,相当于10000次的系统调用, 成本很高
    但是可能10000个连接里面可能某一个时刻,只有100个连接是有数据的, 这样循环就会造成浪费

这里引入了多路复用器, 但是多路复用器原理是什么, 怎么使用, 真实效率到底怎么样?

以及各自效率对比,总结, 可点击链接观看下篇文章BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(下)

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/114943.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript设计模式(二)——简单工厂模式、抽象工厂模式、建造者模式

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

使用element-ui中的el-table回显已选中数据时toggleRowSelection报错

最近在写一个后台&#xff0c;需要在表格中多选&#xff0c;然后点击编辑按钮的时候&#xff0c;需要回显已经选中的表单项 <el-table v-loading"loading" :data"discountList" :row-key"(row) > row.id" refmultipleTable selection-cha…

[ES]二基础 |

一、索引库操作 1、mapping属性 mapping是对索引库中文档的约束&#xff0c;常见的mapping属性包括&#xff1a; 1)type&#xff1a;字段数据类型&#xff0c;常见的简单类型有&#xff1a; ①字符串&#xff1a;text(可分词的文本)、keyword&#xff08;精确值&#xff0c…

基于JavaWeb和mysql实现校园订餐前后台管理系统(源码+数据库)

一、项目简介 本项目是一套基于JavaWeb和mysql实现网上书城前后端管理系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、项目文档、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都…

【Terraform学习】Terraform模块基础操作(Terraform模块)

本站以分享各种运维经验和运维所需要的技能为主 《python》&#xff1a;python零基础入门学习 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8》暂未更新 《docker学习》暂未更新 《ceph学习》ceph日常问题解…

SpringBoot中自定义starter

SpringBoot自动装配原理&#xff1a; EnableAutoConfiguration注解开启自动装配功能&#xff0c;该注解通常放在应用的主类上。spring.factories文件位于META-INF目录下的配置文件中定义各个自动装配类的全限定名 当SpringBoot启动时&#xff0c;会加载classpath下所有的spri…

说说Flink运行模式

分析&回答 1.开发者模式 在idea中运行Flink程序的方式就是开发模式。 2.local-cluster模式 Flink中的Local-cluster(本地集群)模式,单节点运行&#xff0c;主要用于测试, 学习。 3.Standalone模式 独立集群模式&#xff0c;由Flink自身提供计算资源。 4.Yarn模式 把Fl…

android frida 逆向 自吐加密算法

前言&#xff1a; ♛ frida hook android Android 逆向神器 前几天在学习 Android 逆向的时候发现了一个神器&#xff1a;通过 frida hook 我们可以 “劫持” 一些函数 为我们所用&#xff0c; 今天就和大家上手一个 加密函数的劫持 让打印出&#xff1a; 加密秘钥 …

Window11-Ubuntu双系统安装

一、制作Ubuntu系统盘 1.下载Ubuntu镜像源 阿里云开源镜像站&#xff1a;https://mirrors.aliyun.com/ubuntu-releases/ 清华大学开源软件镜像网站&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/ 选择想要的版本下载&#xff0c;我用的是20.04版本。 2…

【广州华锐互动】AR昆虫认知学习系统实现对昆虫形态的捕捉和还原

随着科技的不断发展&#xff0c;人们对自然界的认识也在不断加深。在这个过程中&#xff0c;AR&#xff08;增强现实&#xff09;技术的出现为人们带来了全新的体验方式。为此&#xff0c;广州华锐互动开发了AR昆虫认知学习系统&#xff0c;本文将为大家详细介绍这款系统的特点…

无涯教程-Android - Style Demo Example函数

下面的示例演示如何将样式用于单个元素。让我们开始按照以下步骤创建一个简单的Android应用程序- 步骤说明 1 您将使用Android Studio IDE创建一个Android应用程序,并在 com.example.saira_000.myapplication 包下将其命名为 myapplication ,如中所述您好世界Example一章。 2 …

【方案】基于视频与AI智能分析技术的城市轨道交通视频监控建设方案

一、背景分析 地铁作为重要的公共场所交通枢纽&#xff0c;流动性非常高、人员大量聚集&#xff0c;轨道交通需要利用视频监控系统来实现全程、全方位的安全防范&#xff0c;这也是保证地铁行车组织和安全的重要手段。调度员和车站值班员通过系统监管列车运行、客流情况、变电…

vue小测试之拖拽、自定义事件

在开始之前我去复习了一下&#xff0c;clientX、clientY、pageX、pageY的区别&#xff0c;对于不熟悉offsetLeft和offsetTop的也可以在这里去复习一下。 vue拖拽指令之offsetX、clientX、pageX、screenX_wade3po的博客-CSDN博客_vue offset 客户区坐标位置&#xff08;clientX&…

讲讲几道关于 TCP/UDP 通信的面试题

TCP &#xff08;1&#xff09;TCP 的 accept 发生在三次握手的哪个阶段&#xff1f; 如下图connect和accept的关系&#xff1a; accept过程发生在三次握手之后&#xff0c;三次握手完成后&#xff0c;客户端和服务器就建立了tcp连接并可以进行数据交互了。这时可以调用accep…

像linux 一样清理Windows C盘

像 linux 有命令 du -sh 查看文件夹大小 但是windows 可就没有这个命令了&#xff0c;就算有命令&#xff0c;也不能扫描子目录里面的文件 但是windows 可以借助 软件来清理&#xff0c;和linux 一样 文件上面是目录&#xff0c;下面是文件所占用空间大小的图&#xff0c;咋…

通过参数化可变形曲线直接从 X 射线投影数据计算分割研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

虚拟机Ubuntu20.04 网络连接器图标开机不显示怎么办

执行以下指令&#xff1a; sudo service network-manager stop sudo rm /var/lib/NetworkManager/NetworkManager.state sudo service network-manager start

MySQL 特殊语法时间格式以及Greadb连接

一、时间语法 DATE_FORMAT和to_char() select to_char(now(),%Y-%m-%d %H:%i:%s) from dual; select DATE_FORMAT(now(),%Y-%m-%d %H:%i:%s) from dual; 2.to_date() 和STR_TO_DATE(#{date},%Y-%m-%d ) select to_date(now(),yyyy-mm-dd hh24:mi:ss) from dual;

LeetCode——栈的压入、弹出序列

这里我用下面的例子子来讲解一下模拟栈的实现。 例子1&#xff1a;pushed [1,2,3,4,5] popped [4,5,3,2,1] 思路&#xff1a;第一步&#xff1a;我们先创建一个栈&#xff0c;然后将pushed的数据压进去 第二步&#xff1a;判断&#xff01; 当压入栈的数据和popped第一个数据…

本地启动若依微服务版本

前置工作&#xff1a; 1.导入sql文件 2.安装完nacos 3.安装完redis 启动步骤&#xff1a; 1.开启nacos&#xff0c;在bin目录下 startup.cmd -m standalone 注意&#xff1a;在这之前要配置nacos持久化&#xff0c;修改conf/application.properties文件&#xff0c;增加支持…