摘要: 关于BIO和NIO的理解

最近大概看了ZooKeeper和Mina的源码发现都是用Java NIO实现的,所以有必要搞清楚什么是NIO。下面是我结合网络资料自己总结的,为了节约时间图示随便画的,能达意就行。

简介:

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。 
NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 
AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。

BIO 
同步阻塞式IO,相信每一个学习过操作系统网络编程或者任何语言的网络编程的人都很熟悉,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。 
如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样: 

虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。

NIO 
同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器。 
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,这是怎么做到的呢? 
就是多路复用器,可以监听来自多个客户端的IO事件: 
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。 
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。 
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。 

总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。

各自应用场景

到这里你也许已经发现,一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应IO处理函数处理,所以:

(1)NIO适合处理连接数目特别多,但是连接比较短(轻操作)的场景,Jetty,Mina,ZooKeeper等都是基于java nio实现。

(2)BIO方式适用于连接数目比较小且固定的场景,这种方式对服务器资源要求比较高,并发局限于应用中。

附录:下面附上一个别人写的java NIO的例子。 
服务端:

  1. 1. package cn.nio;
  2. 2.
  3. 3. import java.io.IOException;
  4. 4. import java.net.InetSocketAddress;
  5. 5. import java.nio.ByteBuffer;
  6. 6. import java.nio.channels.SelectionKey;
  7. 7. import java.nio.channels.Selector;
  8. 8. import java.nio.channels.ServerSocketChannel;
  9. 9. import java.nio.channels.SocketChannel;
  10. 10. import java.util.Iterator;
  11. 11.
  12. 12. /**
  13. 13. * NIO服务端
  14. 14. *
  15. 15. */
  16. 16. public class NIOServer {
  17. 17.    //通道管理器
  18. 18.    private Selector selector;
  19. 19.
  20. 20.    /**
  21. 21.     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
  22. 22.     * @param port  绑定的端口号
  23. 23.     * @throws IOException
  24. 24.     */
  25. 25.    public void initServer(int port) throws IOException {
  26. 26.        // 获得一个ServerSocket通道
  27. 27.        ServerSocketChannel serverChannel = ServerSocketChannel.open();
  28. 28.        // 设置通道为非阻塞
  29. 29.        serverChannel.configureBlocking(false);
  30. 30.        // 将该通道对应的ServerSocket绑定到port端口
  31. 31.        serverChannel.socket().bind(new InetSocketAddress(port));
  32. 32.        // 获得一个通道管理器
  33. 33.        this.selector = Selector.open();
  34. 34.        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
  35. 35.        //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
  36. 36.        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
  37. 37.    }
  38. 38.
  39. 39.    /**
  40. 40.     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
  41. 41.     * @throws IOException
  42. 42.     */
  43. 43.    @SuppressWarnings("unchecked")
  44. 44.    public void listen() throws IOException {
  45. 45.        System.out.println("服务端启动成功!");
  46. 46.        // 轮询访问selector
  47. 47.        while (true) {
  48. 48.            //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
  49. 49.            selector.select();
  50. 50.            // 获得selector中选中的项的迭代器,选中的项为注册的事件
  51. 51.            Iterator ite = this.selector.selectedKeys().iterator();
  52. 52.            while (ite.hasNext()) {
  53. 53.                SelectionKey key = (SelectionKey) ite.next();
  54. 54.                // 删除已选的key,以防重复处理
  55. 55.                ite.remove();
  56. 56.                // 客户端请求连接事件
  57. 57.                if (key.isAcceptable()) {
  58. 58.                    ServerSocketChannel server = (ServerSocketChannel) key
  59. 59.                            .channel();
  60. 60.                    // 获得和客户端连接的通道
  61. 61.                    SocketChannel channel = server.accept();
  62. 62.                    // 设置成非阻塞
  63. 63.                    channel.configureBlocking(false);
  64. 64.
  65. 65.                    //在这里可以给客户端发送信息哦
  66. 66.                    channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));
  67. 67.                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
  68. 68.                    channel.register(this.selector, SelectionKey.OP_READ);
  69. 69.
  70. 70.                    // 获得了可读的事件
  71. 71.                } else if (key.isReadable()) {
  72. 72.                        read(key);
  73. 73.                }
  74. 74.
  75. 75.            }
  76. 76.
  77. 77.        }
  78. 78.    }
  79. 79.    /**
  80. 80.     * 处理读取客户端发来的信息 的事件
  81. 81.     * @param key
  82. 82.     * @throws IOException
  83. 83.     */
  84. 84.    public void read(SelectionKey key) throws IOException{
  85. 85.        // 服务器可读取消息:得到事件发生的Socket通道
  86. 86.        SocketChannel channel = (SocketChannel) key.channel();
  87. 87.        // 创建读取的缓冲区
  88. 88.        ByteBuffer buffer = ByteBuffer.allocate(10);
  89. 89.        channel.read(buffer);
  90. 90.        byte[] data = buffer.array();
  91. 91.        String msg = new String(data).trim();
  92. 92.        System.out.println("服务端收到信息:"+msg);
  93. 93.        ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
  94. 94.        channel.write(outBuffer);// 将消息回送给客户端
  95. 95.    }
  96. 96.
  97. 97.    /**
  98. 98.     * 启动服务端测试
  99. 99.     * @throws IOException
  100. 100.     */
  101. 101.    public static void main(String[] args) throws IOException {
  102. 102.        NIOServer server = new NIOServer();
  103. 103.        server.initServer(8000);
  104. 104.        server.listen();
  105. 105.    }
  106. 106.
  107. 107. }

客户端:

  1. 1. package cn.nio;
  2. 2.
  3. 3. import java.io.IOException;
  4. 4. import java.net.InetSocketAddress;
  5. 5. import java.nio.ByteBuffer;
  6. 6. import java.nio.channels.SelectionKey;
  7. 7. import java.nio.channels.Selector;
  8. 8. import java.nio.channels.SocketChannel;
  9. 9. import java.util.Iterator;
  10. 10.
  11. 11. /**
  12. 12. * NIO客户端
  13. 13. *
  14. 14. */
  15. 15. public class NIOClient {
  16. 16.    //通道管理器
  17. 17.    private Selector selector;
  18. 18.
  19. 19.    /**
  20. 20.     * 获得一个Socket通道,并对该通道做一些初始化的工作
  21. 21.     * @param ip 连接的服务器的ip
  22. 22.     * @param port  连接的服务器的端口号
  23. 23.     * @throws IOException
  24. 24.     */
  25. 25.    public void initClient(String ip,int port) throws IOException {
  26. 26.        // 获得一个Socket通道
  27. 27.        SocketChannel channel = SocketChannel.open();
  28. 28.        // 设置通道为非阻塞
  29. 29.        channel.configureBlocking(false);
  30. 30.        // 获得一个通道管理器
  31. 31.        this.selector = Selector.open();
  32. 32.
  33. 33.        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
  34. 34.        //用channel.finishConnect();才能完成连接
  35. 35.        channel.connect(new InetSocketAddress(ip,port));
  36. 36.        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
  37. 37.        channel.register(selector, SelectionKey.OP_CONNECT);
  38. 38.    }
  39. 39.
  40. 40.    /**
  41. 41.     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
  42. 42.     * @throws IOException
  43. 43.     */
  44. 44.    @SuppressWarnings("unchecked")
  45. 45.    public void listen() throws IOException {
  46. 46.        // 轮询访问selector
  47. 47.        while (true) {
  48. 48.            selector.select();
  49. 49.            // 获得selector中选中的项的迭代器
  50. 50.            Iterator ite = this.selector.selectedKeys().iterator();
  51. 51.            while (ite.hasNext()) {
  52. 52.                SelectionKey key = (SelectionKey) ite.next();
  53. 53.                // 删除已选的key,以防重复处理
  54. 54.                ite.remove();
  55. 55.                // 连接事件发生
  56. 56.                if (key.isConnectable()) {
  57. 57.                    SocketChannel channel = (SocketChannel) key
  58. 58.                            .channel();
  59. 59.                    // 如果正在连接,则完成连接
  60. 60.                    if(channel.isConnectionPending()){
  61. 61.                        channel.finishConnect();
  62. 62.
  63. 63.                    }
  64. 64.                    // 设置成非阻塞
  65. 65.                    channel.configureBlocking(false);
  66. 66.
  67. 67.                    //在这里可以给服务端发送信息哦
  68. 68.                    channel.write(ByteBuffer.wrap(new String("向服务端发送了一条信息").getBytes()));
  69. 69.                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
  70. 70.                    channel.register(this.selector, SelectionKey.OP_READ);
  71. 71.
  72. 72.                    // 获得了可读的事件
  73. 73.                } else if (key.isReadable()) {
  74. 74.                        read(key);
  75. 75.                }
  76. 76.
  77. 77.            }
  78. 78.
  79. 79.        }
  80. 80.    }
  81. 81.    /**
  82. 82.     * 处理读取服务端发来的信息 的事件
  83. 83.     * @param key
  84. 84.     * @throws IOException
  85. 85.     */
  86. 86.    public void read(SelectionKey key) throws IOException{
  87. 87.        //和服务端的read方法一样
  88. 88.    }
  89. 89.
  90. 90.
  91. 91.    /**
  92. 92.     * 启动客户端测试
  93. 93.     * @throws IOException
  94. 94.     */
  95. 95.    public static void main(String[] args) throws IOException {
  96. 96.        NIOClient client = new NIOClient();
  97. 97.        client.initClient("localhost",8000);
  98. 98.        client.listen();
  99. 99.    }
  100. 100.
  101. 101. }

http://blog.csdn.net/jiyiqinlovexx/article/details/42619097 

关于BIO和NIO的理解的更多相关文章

  1. Java提高班(五)深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO ...

  2. BIO与NIO、AIO的区别(这个容易理解)

    转自:http://blog.csdn.net/skiof007/article/details/52873421 BIO与NIO.AIO的区别(这个容易理解) IO的方式通常分为几种,同步阻塞的BI ...

  3. 从实践角度重新理解BIO和NIO

    前言 这段时间自己在看一些Java中BIO和NIO之类的东西,看了很多博客,发现各种关于NIO的概念说的天花乱坠头头是道,可以说是非常的完整,但是整个看下来之后,自己对NIO还是一知半解的状态,所以这 ...

  4. Java核心(一)深入理解BIO、NIO、AIO

    目标: BIO.NIO.AIO 的区别是什么? 同/异步.阻/非阻塞的区别是什么? 文件读写最优雅的实现方式是什么? NIO 如何实现多路复用功能? 一,IO的介绍: (1)IO的全称其实是:Inpu ...

  5. [转帖]JAVA BIO与NIO、AIO的区别(这个容易理解)

    JAVA BIO与NIO.AIO的区别(这个容易理解) https://blog.csdn.net/ty497122758/article/details/78979302 2018-01-05 11 ...

  6. Java中BIO,NIO,AIO的理解

    在高性能的I/O体系设计中,有几个概念常常会使我们感到迷惑不解.具体如下: 1 什么是同步? 2 什么是异步? 3 什么是阻塞? 4 什么是非阻塞? 5 什么是同步阻塞? 6 什么是同步非阻塞? 7  ...

  7. 深入理解BIO、NIO、AIO

    导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO ...

  8. BIO、NIO、AIO --- 个人理解

    1.前言 什么是 BIO.NIO.AIO  ,不难看出,都是共同的字符IO , IO的意思是input output  ,即输入输出 , 那么 B . N .A 分别指不同的io模型 ,而io又分为 ...

  9. Java BIO、NIO、AIO 学习(转)

    转自 http://stevex.blog.51cto.com/4300375/1284437 先来个例子理解一下概念,以银行取款为例: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Ja ...

随机推荐

  1. 一个针对string的较好的散列算发djb2

    var djb2HashCode = function(key) { var hash = 5831; for(var i = 0; i < key.length; i++) { hash = ...

  2. zabbix-agent报错:zabbix_agentd [5922]: cannot open log: cannot create semaphore set: [28] No space left on device

    起因: 新添加完自定义监控项后重启zabbix-agent时失败,查看/var/log/zabbix/zabbix-agentd.log 发现系统提示zabbix共享内存报错 zabbix_agent ...

  3. mysql 优化之 doublewrite buffer 机制

    是什么? doublewrite buffer是mysql 系统表空间的一块存储区域. 有什么用? 在Innodb将数据页写到数据存储文件之前,存储从Innodb缓存池刷过来的数据页.且只有将数写入d ...

  4. 九九乘法表的python复习

    九九开始的复习 这周复习之前的学的知识关于range函数,gormat函数,print的使用总结一下 从一个小例子开始,开始我的回顾吧, 大家都是从那个九九乘法表开始的数学之旅,从一一得一,开始了我们 ...

  5. docker server gave HTTP response to HTTPS client 问题处理办法

    vi /etc/docker/daemon.json [root@localhost ~]# cat /etc/docker/daemon.json {"insecure-registrie ...

  6. happybase(TSocket read 0 bytes)

    关于报错happybase 是使用python连接hbase的一个第三方库,目前基于thrift1 .在使用过程中经常碰到报错 TTransportException(type=4, message= ...

  7. dstat 性能测试工具常用选项

    dstat常用的选项有: -c     显示cpu使用情况 -d     显示磁盘使用情况 -g,     显示页面数据 -i      启用中断数据 -l      平均负载统计(1分钟,5分钟,1 ...

  8. rev命令详解

    基础命令学习目录首页 rev命令将文件中的每行内容以字符为单位反序输出,即第一个字符最后输出,最后一个字符最先输出,依次类推. #cat a.txt wo shi mcw, nihao how do ...

  9. whoami,who,w命令详解

    http://www.voidcn.com/blog/wszzdanm/article/p-6145895.html 命令功能:显示登录用户的信息 命令格式: 常用选项: 举例: w 显示已经登录的用 ...

  10. Vue 入门之数据绑定

    什么是双向绑定? Vue 框架很核心的功能就是双向的数据绑定. 双向是指:HTML 标签数据 绑定到 Vue 对象,另外反方向数据也是绑定的.通俗点说就是,Vue 对象的改变会直接影响到 HTML 的 ...