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

客户端:

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

时间: 2024-08-09 02:07:48

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

Java核心(五)深入理解BIO、NIO、AIO

导读:本文你将获取到:同/异步 + 阻/非阻塞的性能区别:BIO.NIO.AIO 的区别:理解和实现 NIO 操作 Socket 时的多路复用:同时掌握 IO 最底层最核心的操作技巧. BIO.NIO.AIO 的区别是什么? 同/异步.阻/非阻塞的区别是什么? 文件读写最优雅的实现方式是什么? NIO 如何实现多路复用功能? 带着以上这几个问题,让我们一起进入IO的世界吧. 在开始之前,我们先来思考一个问题:我们经常所说的"IO"的全称到底是什么? 可能很多人看到这个问题和我一样一脸懵

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

IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行. 二.NIO NIO本身是基于事件驱动思想来完成的,其主

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

目标: BIO.NIO.AIO 的区别是什么? 同/异步.阻/非阻塞的区别是什么? 文件读写最优雅的实现方式是什么? NIO 如何实现多路复用功能? 一,IO的介绍: (1)IO的全称其实是:Input/Output的缩写. (2)我们通常所说的 BIO 是相对于 NIO 来说的,BIO 也就是 Java 开始之初推出的 IO 操作模块,BIO 是 BlockingIO 的缩写,顾名思义就是阻塞 IO 的意思. 1.1 BIO.NIO.AIO的区别 BIO 就是传统的 java.io 包,它是基

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

JAVA BIO与NIO.AIO的区别(这个容易理解) https://blog.csdn.net/ty497122758/article/details/78979302 2018-01-05 11:26:13 涂有 阅读数 41728 文章标签: javaaiobionio 更多 分类专栏: java IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个Server

Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

转载请注明出处:http://blog.csdn.net/anxpp/article/details/51512200,谢谢! 本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为注释,嵌入到代码中,看代码时就能更容易理解,代码中会用到一个计算结果的工具类,见文章代码部分. 相关的基础知识文章推荐: Linux 网络 I/O 模型简介(图文) Jav

BIO与NIO、AIO的区别

IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行. 二.NIO NIO本身是基于事件驱动思想来完成的,其主

JAVA中IO技术:BIO、NIO、AIO

1.同步异步.阻塞非阻塞概念 同步和异步是针对应用程序和内核的交互而言的. 阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作函数的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入函数会立即返回一个状态值. 由上描述基本可以总结一句简短的话,同步和异步是目的,阻塞和非阻塞是实现方式. 1 同步 指的是用户进程触发IO操作并等待或者轮询的去查看IO操作是否就绪 自己上街买衣服,自己亲自干这件事,别的事干不了.

Java BIO、NIO、AIO 学习

先来个例子理解一下概念,以银行取款为例: 同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写). 异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API). 阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回). 非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你

BIO与NIO、AIO的区别(转)

IO的方式通常分为几种,同步阻塞的BIO.同步非阻塞的NIO.异步非阻塞的AIO. 一.BIO 在JDK1.4出来之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个ServerSocket,然后在客户端启动Socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端会线程会等待请求结束后才继续执行. 二.NIO NIO本身是基于事件驱动思想来完成的,其主