通俗易懂的NIO讲解

一、NIO是什么?

NIO的全称是New I/O,与之相对应的是Java中传统的I/O,这里都指的是Java的API包。

传统的IO包提供的是同步阻塞IO,即当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU),当数据就绪后,内核会将数据复制到用户线程,并把结果返回给用户线程,同时接触用户线程的阻塞,同步体现在用户线程需要等待数据就绪后才能向后执行(后面的执行依赖于前面的结果)。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,线程数量也会受到。

而NIO包提供的IO是同步非阻塞IO,非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。而同步体现在用户线程需要主动去轮询直到发现数据就绪,再主动将数据从内核拷贝到用户线程。服务器实现模式为多个连接一个线程(IO多路复用),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理

传统IO模型

NIO

客户端个数IO线程

1:1

M11IO线程处理多个客户端连接)

IO类型(阻塞)

阻塞IO

非阻塞IO

IO类型(同步)

同步IO

同步IOIO多路复用)

API使用难度

简单

非常复杂

调式难度

简单

复杂

可靠性

非常差

吞吐量


二、NIO包API的用法。

(1)Channel:Java NIO中的所有I/O操作都基于Channel对象,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。

FileChannel:文件通道,从输入流输出流中获取实例,常见的使用场景就是从一个文件拷贝其内容到另一个文件。


    
    
  1. FileInputStream fis = new FileInputStream( “E://zfb.txt”);
  2. FileChannel ifc = fis.getChannel();
  3. FileOutputStream os = new FileOutputStream( “E://zfb2.txt”);
  4. FileChannel ofc = os.getChannel();
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024);
  6. while (ifc.read(buffer) != - 1){
  7. System.out.println( 1);
  8. buffer.flip();
  9. ofc.write(buffer);
  10. buffer.clear();
  11. }

SocketChannel/ServerSocketChannel:套接字通道,通过静态方法获取实例,使用场景在最后会给出demo。

SocketChannel socketChannel = SocketChannel.open();
    
    

(2)Buffer:NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据。与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是capacity (总容量)position (指针当前位置)、limit (读/写边界位置)。

接下来看三个方法flip、rewind、clear


    
    
  1. public final Buffer flip() { //用于写模式到读模式的转换
  2. limit = position; //设置上届为当前位置
  3. position = 0; //当前位置设置为0
  4. mark = - 1; //重置写标记
  5. return this;
  6. }

    
    
  1. public final Buffer rewind() { //设置当前位置为0
  2. position = 0;
  3. mark = - 1;
  4. return this;
  5. }

    
    
  1. public final Buffer clear() { //通过指针移动的方式清空缓冲区
  2. position = 0; //设置当前位置为0
  3. limit = capacity; //上限为容量
  4. mark = - 1;
  5. return this;
  6. }

(3)Selector:Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置关心的事件,然后就可以通过调用select()方法,监听关注事件的发生。通道有4个事件可供我们监听:Accept、Connect、Read、Write。

selector.select()方法是阻塞的方法!!!若注册的通道没有事件到达,则不会向下执行。但是NIO的IO机制是非阻塞的!!!

三、NIO的编程模型——基于Reactor模式的事件驱动。

NIO是同步非阻塞的,但是提供了基于Selector的异步网络IO。这句话的意思是NIO的IO机制是同步非阻塞的,而基于Selector这个组件的编程模型(Reactor)是事件驱动的,是异步的。

下面来看一下一般采用NIO的编程模型:


demo服务端代码:


    
    
  1. public class NIOServer {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private ServerSocketChannel serverSocketChannel;
  5. private ByteBuffer byteBuffer;
  6. private Selector selector;
  7. public NIOServer(){
  8. try {
  9. init();
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. public void init() throws IOException { //初始化
  15. serverSocketChannel = ServerSocketChannel.open();
  16. serverSocketChannel.configureBlocking( false);
  17. serverSocketChannel.bind( new InetSocketAddress(PORT));
  18. selector = Selector.open();
  19. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  20. byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
  21. }
  22. private void listen() throws IOException {
  23. while ( true){
  24. if (selector.select() == 0){
  25. continue;
  26. }
  27. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  28. while (iterator.hasNext()){
  29. SelectionKey key = iterator.next();
  30. if (key.isAcceptable()){
  31. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  32. SocketChannel socketChannel = ssc.accept();
  33. socketChannel.configureBlocking( false);
  34. socketChannel.register(selector, SelectionKey.OP_READ);
  35. replyClient(socketChannel);
  36. }
  37. if (key.isReadable()){
  38. readDataFromClient(key);
  39. }
  40. iterator.remove();
  41. }
  42. }
  43. }
  44. private void readDataFromClient(SelectionKey key) throws IOException {
  45. SocketChannel socketChannel = (SocketChannel) key.channel();
  46. byteBuffer.clear();
  47. int n;
  48. while ((n = socketChannel.read(byteBuffer)) > 0){
  49. byteBuffer.flip();
  50. while (byteBuffer.hasRemaining()){
  51. socketChannel.write(byteBuffer);
  52. }
  53. byteBuffer.clear();
  54. }
  55. if (n < 0){
  56. socketChannel.close();
  57. }
  58. }
  59. private void replyClient(SocketChannel channel) throws IOException {
  60. byteBuffer.clear();
  61. byteBuffer.put( "Hello,I am server!".getBytes());
  62. byteBuffer.flip();
  63. channel.write(byteBuffer);
  64. }
  65. public static void main(String[] args) {
  66. try {
  67. new NIOServer().listen();
  68. } catch (IOException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }

客户端代码:


    
    
  1. public class NIOClient {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private SocketChannel socketChannel;
  5. private ByteBuffer byteBuffer;
  6. public void connect() throws IOException {
  7. socketChannel = SocketChannel.open();
  8. socketChannel.connect( new InetSocketAddress( "127.0.0.1", PORT));
  9. byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
  10. receive();
  11. }
  12. private void receive() throws IOException {
  13. while ( true){
  14. int n;
  15. byteBuffer.clear();
  16. while ((n = socketChannel.read(byteBuffer)) > 0){
  17. byteBuffer.flip();
  18. System.out.println( "从服务器收到消息: " + new String(byteBuffer.array()));
  19. //socketChannel.write(byteBuffer);//发消息
  20. byteBuffer.clear();
  21. }
  22. }
  23. }
  24. public static void main(String[] args) {
  25. try {
  26. new NIOClient().connect();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

一、NIO是什么?

NIO的全称是New I/O,与之相对应的是Java中传统的I/O,这里都指的是Java的API包。

传统的IO包提供的是同步阻塞IO,即当用户线程发出IO请求后,内核会去查看数据是否已经就绪,若未就绪,则用户线程会处于阻塞状态(让出CPU),当数据就绪后,内核会将数据复制到用户线程,并把结果返回给用户线程,同时接触用户线程的阻塞,同步体现在用户线程需要等待数据就绪后才能向后执行(后面的执行依赖于前面的结果)。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,线程数量也会受到。

而NIO包提供的IO是同步非阻塞IO,非阻塞体现在用户线程发起IO请求后,会直接得到返回结果,即便在数据未就绪的情况下,也能马上得到失败信息。而同步体现在用户线程需要主动去轮询直到发现数据就绪,再主动将数据从内核拷贝到用户线程。服务器实现模式为多个连接一个线程(IO多路复用),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理

传统IO模型

NIO

客户端个数IO线程

1:1

M11IO线程处理多个客户端连接)

IO类型(阻塞)

阻塞IO

非阻塞IO

IO类型(同步)

同步IO

同步IOIO多路复用)

API使用难度

简单

非常复杂

调式难度

简单

复杂

可靠性

非常差

吞吐量


二、NIO包API的用法。

(1)Channel:Java NIO中的所有I/O操作都基于Channel对象,一个Channel(通道)代表和某一实体的连接,这个实体可以是文件、网络套接字等。也就是说,通道是Java NIO提供的一座桥梁,用于我们的程序和操作系统底层I/O服务进行交互。

FileChannel:文件通道,从输入流输出流中获取实例,常见的使用场景就是从一个文件拷贝其内容到另一个文件。


  
  
  1. FileInputStream fis = new FileInputStream( “E://zfb.txt”);
  2. FileChannel ifc = fis.getChannel();
  3. FileOutputStream os = new FileOutputStream( “E://zfb2.txt”);
  4. FileChannel ofc = os.getChannel();
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024);
  6. while (ifc.read(buffer) != - 1){
  7. System.out.println( 1);
  8. buffer.flip();
  9. ofc.write(buffer);
  10. buffer.clear();
  11. }

SocketChannel/ServerSocketChannel:套接字通道,通过静态方法获取实例,使用场景在最后会给出demo。

SocketChannel socketChannel = SocketChannel.open();
  
  

(2)Buffer:NIO中所使用的缓冲区不是一个简单的byte数组,而是封装过的Buffer类,通过它提供的API,我们可以灵活的操纵数据。与Java基本类型相对应,NIO提供了多种 Buffer 类型,如ByteBuffer、CharBuffer、IntBuffer等,区别就是读写缓冲区时的单位长度不一样(以对应类型的变量为单位进行读写)。Buffer中有3个很重要的变量,它们是理解Buffer工作机制的关键,分别是capacity (总容量)position (指针当前位置)、limit (读/写边界位置)。

接下来看三个方法flip、rewind、clear


  
  
  1. public final Buffer flip() { //用于写模式到读模式的转换
  2. limit = position; //设置上届为当前位置
  3. position = 0; //当前位置设置为0
  4. mark = - 1; //重置写标记
  5. return this;
  6. }

  
  
  1. public final Buffer rewind() { //设置当前位置为0
  2. position = 0;
  3. mark = - 1;
  4. return this;
  5. }

  
  
  1. public final Buffer clear() { //通过指针移动的方式清空缓冲区
  2. position = 0; //设置当前位置为0
  3. limit = capacity; //上限为容量
  4. mark = - 1;
  5. return this;
  6. }

(3)Selector:Selector(选择器)是一个特殊的组件,用于采集各个通道的状态(或者说事件)。我们先将通道注册到选择器,并设置关心的事件,然后就可以通过调用select()方法,监听关注事件的发生。通道有4个事件可供我们监听:Accept、Connect、Read、Write。

selector.select()方法是阻塞的方法!!!若注册的通道没有事件到达,则不会向下执行。但是NIO的IO机制是非阻塞的!!!

三、NIO的编程模型——基于Reactor模式的事件驱动。

NIO是同步非阻塞的,但是提供了基于Selector的异步网络IO。这句话的意思是NIO的IO机制是同步非阻塞的,而基于Selector这个组件的编程模型(Reactor)是事件驱动的,是异步的。

下面来看一下一般采用NIO的编程模型:


demo服务端代码:


  
  
  1. public class NIOServer {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private ServerSocketChannel serverSocketChannel;
  5. private ByteBuffer byteBuffer;
  6. private Selector selector;
  7. public NIOServer(){
  8. try {
  9. init();
  10. } catch (IOException e) {
  11. e.printStackTrace();
  12. }
  13. }
  14. public void init() throws IOException { //初始化
  15. serverSocketChannel = ServerSocketChannel.open();
  16. serverSocketChannel.configureBlocking( false);
  17. serverSocketChannel.bind( new InetSocketAddress(PORT));
  18. selector = Selector.open();
  19. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  20. byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
  21. }
  22. private void listen() throws IOException {
  23. while ( true){
  24. if (selector.select() == 0){
  25. continue;
  26. }
  27. Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
  28. while (iterator.hasNext()){
  29. SelectionKey key = iterator.next();
  30. if (key.isAcceptable()){
  31. ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
  32. SocketChannel socketChannel = ssc.accept();
  33. socketChannel.configureBlocking( false);
  34. socketChannel.register(selector, SelectionKey.OP_READ);
  35. replyClient(socketChannel);
  36. }
  37. if (key.isReadable()){
  38. readDataFromClient(key);
  39. }
  40. iterator.remove();
  41. }
  42. }
  43. }
  44. private void readDataFromClient(SelectionKey key) throws IOException {
  45. SocketChannel socketChannel = (SocketChannel) key.channel();
  46. byteBuffer.clear();
  47. int n;
  48. while ((n = socketChannel.read(byteBuffer)) > 0){
  49. byteBuffer.flip();
  50. while (byteBuffer.hasRemaining()){
  51. socketChannel.write(byteBuffer);
  52. }
  53. byteBuffer.clear();
  54. }
  55. if (n < 0){
  56. socketChannel.close();
  57. }
  58. }
  59. private void replyClient(SocketChannel channel) throws IOException {
  60. byteBuffer.clear();
  61. byteBuffer.put( "Hello,I am server!".getBytes());
  62. byteBuffer.flip();
  63. channel.write(byteBuffer);
  64. }
  65. public static void main(String[] args) {
  66. try {
  67. new NIOServer().listen();
  68. } catch (IOException e) {
  69. e.printStackTrace();
  70. }
  71. }
  72. }

客户端代码:


  
  
  1. public class NIOClient {
  2. private final static int BUFFER_SIZE = 1024;
  3. private final static int PORT = 52621;
  4. private SocketChannel socketChannel;
  5. private ByteBuffer byteBuffer;
  6. public void connect() throws IOException {
  7. socketChannel = SocketChannel.open();
  8. socketChannel.connect( new InetSocketAddress( "127.0.0.1", PORT));
  9. byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
  10. receive();
  11. }
  12. private void receive() throws IOException {
  13. while ( true){
  14. int n;
  15. byteBuffer.clear();
  16. while ((n = socketChannel.read(byteBuffer)) > 0){
  17. byteBuffer.flip();
  18. System.out.println( "从服务器收到消息: " + new String(byteBuffer.array()));
  19. //socketChannel.write(byteBuffer);//发消息
  20. byteBuffer.clear();
  21. }
  22. }
  23. }
  24. public static void main(String[] args) {
  25. try {
  26. new NIOClient().connect();
  27. } catch (IOException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

猜你喜欢

转载自blog.csdn.net/a639735331/article/details/81738568