前言

同步异步:

同步和异步关注的是消息通信机制 (synchronous communication/ asynchronous communication)
同步:在发出一个调用时,在没有得到结果之前,该调用就不返回。一旦调用返回,就得到返回值了。调用者主动等待这个调用的结果。
异步:调用在发出之后就直接返回了,没有立刻得到返回结果。在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。

I/O

通常来说,IO操作包括:对硬盘的读写、对socket的读写以及外设的读写,并且需要进行用户空间内核空间的区分(用户空间就是普通的用户进程,内核空间就是内核进程,只有内核空间才可以直接范围磁盘等物理 I/O设备,操作系统层面的了)
用户空间产生一个读请求,请求再转交由内核空间执行
1. 内核检查读取的数据是否就绪
2. 如果就绪,内核将数据从内核空间复制到用户空间(内存上拷贝)

阻塞I/O与非阻塞I/O

阻塞I/O:内核在检查数据未就绪时,会一直等待,直到数据就绪。

非阻塞I/O:如果数据没有就绪,则会返回一个标志信息告知用户线程当前要读的数据没有就绪
它们的区别在于I/O的第一阶段,阻塞是选择等待,非阻塞是返回一个标志信息

那么非阻塞I/O的优势在哪里呢?使用阻塞I/O处理网络连接时,有10000个连接就要开10000个线程,无论有没有数据到来,处理某一连接的线程必须“忠实地阻塞”。而非阻塞I/O就不需要这样,它可以维护一个1000个线程的线程池,当有数据就绪时,启动一个线程去接受数据,当没有数据时,线程不需要等待,直接就可以回到池中,等待被调度到去接受其它连接。因此非阻塞I/O非常适合连接多但传输的数据内容不大的情况,如果连接少数据多,阻塞I/O更容易编程

同步I/O和异步I/O

事实上,同步IO和异步IO模型是针对用户线程和内核的交互来说的,即数据是否就绪的消息传递机制。

同步IO:当用户发出IO请求操作之后,如果数据没有就绪,需要通过用户线程或者内核不断地去轮询数据是否就绪,当数据就绪时,再将数据从内核拷贝到用户线程。
异步IO:只有IO请求操作的发出是由用户线程来进行的,内核自动完成检查数据是否就绪和将数据拷贝到用户空间的过程,然后发送通知告知用户线程IO操作已经完成。

上述概念参考博文:https://www.cnblogs.com/aeolian/p/10773786.html下面从Java代码中感受BIO与NIO

BIO(同步阻塞式IO)

Java中ServerSocket和Socket就是阻塞式的。代码如下

服务器端:

public class Server {public static void main(String[] args) {try {//建立服务器    BIO这个Socket是专门用于侦听连接的。并不是用来与客户端通信的ServerSocket server = new ServerSocket(54188);                               byte[] bytes = new byte[1024];while(true) {System.out.println("等待连接");long start = System.currentTimeMillis();/*** 侦听客户端连接  注意:此处会阻塞!!!(放弃CPU资源)如果侦听不到连接程序就阻塞在这不会往下进行* 这个Socket才是专门用于与客户端通信的*/Socket client = server.accept();                                                           long end = System.currentTimeMillis();System.out.println("连接成功,用时:" + (end - start) + "  毫秒");//注意:此处会阻塞!!!   read方法返回的是读取的字节数int factLen = client.getInputStream().read(bytes);                      System.out.println("读取成功:" + new String(bytes, 0, factLen));                  System.out.println("读取数据用时 : " + (System.currentTimeMillis() - end)+ "  毫秒");}} catch (IOException e) {e.printStackTrace();}}}

上述为简单的测试服务器端代码。需要注意的是accept()方法和read()方法都是会阻塞线程的,服务器如果侦听不到连接,或者读取不到消息程序就无法往下进行。

public class Client {public static void main(String[] args) {try {Socket socket = new Socket();SocketAddress address = new InetSocketAddress("127.0.0.1", 54188);socket.connect(address);        //连接服务器//休息一会再发数据       Thread.sleep(3000); socket.getOutputStream().write("===你好===".getBytes());      } catch (IOException e) {e.printStackTrace();} catch (InterruptedException e) {e.printStackTrace();}}}

客户端做的事很简单就是连接服务器然后发送一条消息,先启动服务器端再启动客户端看下结果:

启动两次客户端确实可以看出accept和read的阻塞性。如果服务器端那样写,是不能支持并发地访问服务器的,即如果将客户端代码中的发数据注释起来。开启两次客户端则会出现下面的情况

第二次的时候并没有连接上。因为第一次时候客户端连接上之后一直没有发送消息,导致服务器端程序一直阻塞在read()方法上,根本就没有办法去侦听下一个连接,程序运行不到那里。解决这个问题的办法就是:多线程处理。服务器端利用单独的一个线程来专门侦听连接(只负责侦听连接)。如果发现每有一个客户端连接上来就再开一个专门用作与该客户端的通信的线程。这样就能保证每个客户端都能连接得上,也能确保与每个客户端都能实时进行交互。

但是又有一个问题。假如10000个客户端都连接上服务器。可能真正做数据交互的就那么100个。 其他就只想单纯的连接一下。即使是这样服务器端也开启了大量线程(且是无意义的线程)造成资源的巨大浪费。

如果就上述服务器端代码而言client.getInputStream().read(bytes);        这行代码不阻塞。那么不需要多线程就可以让客户端们并发的访问服务器。单单这还不够。因为read()方法不阻塞了。但是accept()方法还是阻塞的呀。确实是客户端们可以连接上了,但是每次连接上read()方法不阻塞。服务器端又去侦听连接了(会阻塞)。再侦听连接期间,一直在accept()上阻塞着,就执行不到read()方法上,那就不能实时读取到客户端发来的消息呀。所以如果要达到并发的效果,就需要对accpet()和read()方法都解阻塞。

假设现在accept()和read()都解阻塞了,要想与每个连接上的客户端都能进行交互,那就需要保存每个客户端的Socket。将其放入到集合中,不停地遍历集合即可,如果有收到消息就处理,没有就看下一个客户端是否发过来消息。accpet()也解阻塞,如果有客户端请求连接就将其加入Socket池,如果没有就进行下面的执行。大概过程如下:

public class WServer {static List<Socket> clientList = new ArrayList<Socket>();public static void main(String[] args) {try {ServerSocket server = new ServerSocket(54188);                         /*** 假如说  有一个方法可以将server设置为非阻塞假如是这样  server.fzs(true)。那么accept()和read()就可以解阻塞。* 即程序不会阻塞在accept()和read()上。while循环就可以一直快速运行。* 可以将每次连接到的客户端Socket保存起来,要不然循环一次就会丢掉上一次的连接。*/byte[] bytes = new byte[1024];while(true) {/**每次轮询“客户端连接池”如果有人发消息,我就处理。* for(Socket clientItem : clientList) {*     int factLen = client.getInputStream().read(bytes);                       *      System.out.println("读取成功:" + new String(bytes, 0, factLen));                   * }*/Socket client = server.accept();                                                          /**我可以将连接上的客户端保存起来,如果没有连接,那也不阻塞,程序往下执行* if(client != null) {*     clientList.add(client);* }*/}} catch (IOException e) {e.printStackTrace();}}}

而Java没有在Socket基础上增加这种功能,而是提供了一个专门的类来解决这个问题。

NIO

BIO要处理并发访问,就需要多线程。NIO的设计是避免线程资源的浪费。单线程处理并发。

在Java中ServerSocketChannel和SocketChannel是可以设置为非阻塞的。

public class ServerSocketChannelTest {static List<SocketChannel> clientList = new ArrayList<SocketChannel>();//ByteBuffer一个字节缓冲区,capacity 指定缓冲区大小static ByteBuffer byteBuffer = ByteBuffer.allocate(1024); public static void main(String[] args) {try {//打开服务器连接通道。ServerSocketChannel server = ServerSocketChannel.open();server.configureBlocking(false);   //服务器通道设置为非阻塞SocketAddress address = new InetSocketAddress("127.0.0.1", 54188);//设置服务器地址server.bind(address);while(true) {//此处可以轮询SocketChannel检测是否有客户端发送来消息SocketChannel client = server.accept();if(client != null) {System.out.println("连接成功");clientList.add(client);//将此通道设置为非阻塞通道client.configureBlocking(false);}System.out.println("=================");}} catch (IOException e) {e.printStackTrace();}}}

执行结果:

可以体现出accept()已经被解阻塞了。不过上述没有进行“轮询操作”。主要是这里牵扯性能问题:

假如现在有10000个客户端连接上了,没问题。但是活跃的只有1000个。那么岂不是每次都要在程序中进行无意义的轮询9000次。这显然是不好的。这样性能就不好。而且如果将轮询写在上述程序中,那是直接在Jvm上进行轮询。而且IO设备是只能由操作系统直接访问。如果直接交给内核处理那么效率就会提升很多。所以为了提高这个“轮询的性能”。Java又提出解决办法。

selector和epoll   多路复用函数

其实这两个函数本质上是C语言实现的。Java调用的就是底层的C函数。

由于操作系统的不同,所以提供了两个函数,selector是基于windows系统的。epoll是基于Liunx系统的(Liunx也可以使用selector)。

针对selector:它并不是非常完美的解决了上述问题,它也需要轮询10000次,只不过将轮询处理交给操作系统去做。这样性能也能提升。它内部维持了一个数据结构(bitmap),用来存储已经接受的socket对象。然后将此数据复制一份,交给内核去轮训每个socket对象是否有期待的事件发生,如果有就会进行置位(用户态到内核态的复制)。然后返回给用户态,由用户态完成事件的处理。

对于epoll:epoll(Linux下):epoll针对selector进行了优化,采用红黑树来存储accept的socket对象,同时还维持着一个list,存放有发生的事件的socket以及事件,然后将此任务队列返回。用户态无需遍历所有的socket对象。

从BIO到NIO的网络通信相关推荐

  1. 网络通信1 AIO 和 BIO和 NIO

    一 网络编程模型 1.1.1 常见网络编程I/O模型 常见的网络编程I/O模型有:AIO.BIO.NIO BIO: BIO网络编程模型:Blocking IO即 阻塞IO.服务器实现模式为一个连接一个 ...

  2. 面试被问BIO、NIO、AIO的区别,怎么破?

    来源:juejin.cn/post/6844903985158045703 很多文章在谈论到BIO.NIO.AIO的时候仅仅是抛出一堆定义,以及一些生动的例子.看似很好理解.但是并没有将最基础的本质原 ...

  3. 阻塞、非阻塞、多路复用、同步、异步、BIO、NIO、AIO 一锅端

    承接上文的操作系统,关于IO会涉及到阻塞.非阻塞.多路复用.同步.异步.BIO.NIO.AIO等几个知识点.知识点虽然不难但平常经常容易搞混,特此Mark下,与君共勉. 1 阻塞跟非阻塞 1.1 阻塞 ...

  4. 面试必会系列 - 5.1 网络BIO、NIO、epoll,同步/异步模型、阻塞/非阻塞模型,你能分清吗?

    本文已收录至 Github(MD-Notes),若博客中图片模糊或打不开,可以来我的 Github 仓库,包含了完整图文:https://github.com/HanquanHq/MD-Notes,涵 ...

  5. 坦克大战 - 设计模式、BIO、NIO、AIO、Netty

    设计模式 1.策略模式 有时候你想发射单排子弹,有时候你想发射双排子弹. 当你想有不同的子弹发射方式时,应该怎么做才能在尽可能少的修改代码的前提下,快速完成这些子弹发射策略的切换呢? 办法就是,你写一 ...

  6. bio nio aio区别_8分钟深入浅出搞懂BIO、NIO、AIO

    在高性能的IO体系设计中,BIO.NIO.AIO的概念,常常会让我们感到困惑不解.在Java面试中,我们也经常会被问到这个问题.譬如: BIO.NIO.AIO 的概念 同步/异步.阻塞/非阻塞的区别 ...

  7. java中io.nio.aio_Java中网络IO的实现方式-BIO、NIO、AIO

    在网络编程中,接触到最多的就是利用Socket进行网络通信开发.在Java中主要是以下三种实现方式BIO.NIO.AIO. 关于这三个概念的辨析以前一直都是好像懂,但是表达的不是很清楚,下面做个总结完 ...

  8. 从BIO、NIO到Netty

    本文来说下从 BIO.NIO 到 Netty的一路发展过程 文章目录 BIO 传统的阻塞式通信流程 代码示例 NIO NIO概述 NIO核心组件解读 NIO为啥更好 使用NIO编写代码太复杂 Nett ...

  9. BIO、NIO、AIO的区别

    很多文章在谈论到BIO.NIO.AIO的时候仅仅是抛出一堆定义,以及一些生动的例子.看似很好理解.但是并没有将最基础的本质原理显现出来,如果没有从IO的原理出发的话是很难理解这三者之间的区别的.本文你 ...

  10. 你真的理解BIO、NIO、AIO的区别吗?

    来源:juejin.cn/post/6844903985158045703 很多文章在谈论到BIO.NIO.AIO的时候仅仅是抛出一堆定义,以及一些生动的例子.看似很好理解.但是并没有将最基础的本质原 ...

最新文章

  1. JSIS3D:具有多任务点向网络和多值条件随机场的3D点云联合语义-实例分割
  2. 运用Edraw为WPF应用程序嵌入Office文档的方法总结
  3. game connect4 java_为我的connect 4数学游戏创建一个积分系统
  4. word2vec应用场景_介绍Word2Vec和Glove这两种最流行的词嵌入方法背后的直觉
  5. 英文科技写作 · 经验分享 · 讨论合集
  6. (转)一步一步Asp.Net MVC系列_权限管理之权限控制
  7. EF CodeFirst类生成器
  8. get请求500_050 Servlet的请求req与响应resp
  9. Echarts2竖直datazoom滑动后显示数据不全的解决方法
  10. 河南省计算机基础考试题库,计算机基础考试题库
  11. 单片机编程技巧—状态机编程
  12. Android获取外网和内网ip地址
  13. 基础:正则表达式方便理解
  14. 90-BitCoin入门
  15. 轻松几步获得上万点击率(三)
  16. AD域外计算机共享域内打印机出现“无法访问,没有权限访问网络资源,用户不得从此工作站登录网络”
  17. IPv6下的DHCP(DHCPv6)
  18. CentOS安装GlusterFS
  19. python *号的含义
  20. 迅为龙芯2K1000开发板虚拟机ubuntu安装VMtools工具

热门文章

  1. python给csv文件添加表头
  2. 为什么要去学习函数式编程
  3. 磁珠法RNA pull down试剂盒、蛋白质-核酸相互作用
  4. 统计 fastq 文件 q20 , GC 含量的软件
  5. 卡方检验与方差分析的区别
  6. 误差平方和用python_用Python学分析 - 单因素方差分析
  7. 多分支表达-倍数问题
  8. 中标麒麟Neokylin7桌面版安装指南——基于VirtualBox虚拟机
  9. oracle创建视图包含clob字段,报错:数据类型不一致:应为-,但却获得CLOB
  10. Garbled Circuits介绍 - 4 混淆电路的优化