摘要: 关于BIO和NIO的理解
最近大概看了ZooKeeper和Mina的源码发现都是用Java NIO实现的
一、简介
BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端 有连接请求 时服务器端就 需要启动一个线程 进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到 多路复用器 上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
1.BIO基本介绍
BIO是传统的Java IO编程,其基本的类和接口在java.io包中
BIO(blocking I/O):同步阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销
BIO方式使用于连接数目比较小且固定的架构,这种服务方式对服务器资源要求比价高,并且局限于应用中,JDK1.4以前的唯一选择,程序简单易理解
BIO基本模型:
2.NIO基本介绍
NIO全称 java non-blocking IO。从JDK 1.4开始,java提供了一些列改进的输入/输出(I/O)的新特性,被称为NIO,是同步非阻塞的
NIO相关类都被放在java.nio包及其子包下
NIO三大核心部分:Channel(通道),Buffer(缓冲区),Selector(选择器)
NIO是面向缓冲区的,或者面向块编程的。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区内前后移动,这就增加了处理过程中的灵活性,使用它可以提供非阻塞的高伸缩性网络
Java NIO的非阻塞模式,使一个线程从某通道发送或者读取数据,但是它仅能得到目前可用的数据,如果目前没有可用的数据时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可读取之前,该线程可以继续做其他事情。非阻塞就是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情
通俗来讲:NIO是可以做到用一个线程处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或100个线程来处理。不像BIO一样需要分配10000个线程来处理
NIO基本模型:
3.BIO和NIO的区别
BIO以流的方式处理数据,NIO以块的方式处理数据,块IO的效率比流IO高很多。(比如说流IO他是一个流,你必须时刻去接着他,不然一些流就会丢失造成数据丢失,所以处理这个请求的线程就阻塞了他无法去处理别的请求,他必须时刻盯着这个请求防止数据丢失。而块IO就不一样了,线程可以等他的数据全部写入到缓冲区中形成一个数据块然后再去处理他,在这期间该线程可以去处理其他请求)
BIO是阻塞的,NIO是非阻塞的
BIO基于字节流和字符流进行操作的,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作的,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道事件,因此使用单个线程就可以监听多个客户端通道
二、各自应用场景
到这里你也许已经发现,一旦有请求到来(不管是几个同时到还是只有一个到),都会调用对应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. *
- 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. *
- 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. }