ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【Netty】(二)NIO 中的三大组件的介绍与使用

2022-01-30 17:06:52  阅读:185  来源: 互联网

标签:Netty ByteBuffer Buffer String byteBuffer out socketChannel 三大 NIO


1. NIO 基本介绍

Java NIO 是同步非阻塞的。NIO 相关的类放在 java.nio 包及子包下面,并且对原生的 IO 进行了很多类的改写。

NIO 是面向缓冲区或者是面向块编程的:数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中移动,这就增加了处理过程中的灵活性。

它有三大核心组件:

  • Channel:通道
  • Buffer:缓冲区
  • Selector:选择器

其核心组件结构如下图:
在这里插入图片描述
由上面的图可以看出:

  1. 一个 Channel 对应一个 Buffer
  2. 一个 Selector 对应一个 Thread,但对应多个 Channel
  3. Selector 会根据不同事件在各个通道上切换
  4. 数据的读取/写入是通过 Buffer,这个跟 BIO 不同。BIO 是直接与通道打交道的
  5. NIO 中的 Buffer 是双向的(既可以读又可以写),但需要 flip() 方法切换;BIO 中不是双向流,要么是一个单独的输入流,要么就是一个输出流
  6. Channel 也是双向的

Client 不直接与 Channel 交互,而是通过中间媒介 Buffer 进行交互。

2. Buffer

Buffer 本质上是一个可以读、写数据的内存块,可以理解为一个容器对象。

Java 中的基本数据类型除了 boolean 类型外,其余的都有与之对应的 Buffer 类型。

下面以 IntBuffer 为例:

Buffer 使用示例:

public class BufferDemo {

    public static void main(String[] args) {
        // 1.创建 Buffer
        IntBuffer intBuffer = IntBuffer.allocate(5);
        for (int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put(i);
        }
        // 2.Buffer 转换。写 --> 读
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

这里创建了一个 IntBuffer,大小为 5。然后,往其里面 put 了 5 个整数(Buffer 写)。由于 Buffer 既可以写又可以读。所以,在进行读取之前,进行 Buffer 切换 intBuffer.flip()

3. Channel

NIO 的通道类似于流,但有如下区别:

  1. 通道可以同时进行读、写,而流只能进行读或者写
  2. 通道可以实现异步读、写数据
  3. 通道可以从缓冲区读数据,也可以写数据到缓冲区

Channel 是一个接口:

public interface Channel extends Closeable {
	// ...
}

常用的 Channel 类有:

  • FileChannle:用于文件的数据的读、写
  • DatagramChannel:用于 UDP 的数据的读、写
  • ServerSocketChannel:用于 TCP 的数据的读、写
  • SocketChannel:用于 TCP 的数据的读、写

示例一:本地文件写数据

使用 ByteBuffer 和 FileChannel 将 “hello,JAVA” 写入到某个磁盘文件

public static void main(String[] args) throws Exception {
    String str = "hello, JAVA";
    FileOutputStream out = new FileOutputStream("E:\\temp.txt");
    FileChannel fileChannel = out.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    byteBuffer.put(str.getBytes());
    byteBuffer.flip();
    fileChannel.write(byteBuffer);
    out.close();
}

运行上述代码后,便会在 E 盘中生成一个 temp.txt 文件,并且,其里面有内容。

示例二:本地文件读数据

使用 ByteBuffer 和 FileChannel 将 temp.txt 文件中的内容读取出来

public static void main(String[] args) throws Exception {
    File file = new File("E:\\temp.txt");
    FileInputStream in = new FileInputStream(file);
    FileChannel fileChannel = in.getChannel();
    ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
    fileChannel.read(byteBuffer);
    // 将字节转化为字符串
    String result = new String(byteBuffer.array());
    System.out.println(result);
    in.close();
}

示例三:本地文件读、写数据

通过 FileChannel 和一个 Buffer 完成某个文件的拷贝

public class FileChannelRwDemo {

    public static void main(String[] args) throws Exception{
        FileInputStream in = new FileInputStream("E:\\temp.txt");
        FileChannel inFileChannel = in.getChannel();
        FileOutputStream out = new FileOutputStream("temp.txt");
        FileChannel outFileChannel = out.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        while (true) {
        	// 此行代码是重点
            byteBuffer.clear();
            
            int read = inFileChannel.read(byteBuffer);
            if (-1 == read) {
                break;
            }
            byteBuffer.flip();
            outFileChannel.write(byteBuffer);
        }
        in.close();
        out.close();
    }
}

4. Selector

Selector 能够检测多个注册的通道上是否有事件发生。如果有事件发生,便获取事件,然后针对每个事件进行相应的处理。

只有在连接真正地有读、写事件发生时,才会进行读、写。这就大大地减少了系统的开销,并且,不必为每个连接都创建一个线程,不用去维护多个线程。

Selector 工作流程:

  1. 当客户端连接时,会通过 ServerSocketChannel 得到 SocketChannel
  2. SocketChannel 注册到 Selector 上(SelectableChannel#register())
  3. 注册后返回一个 SelectionKey,会和该 Selector 关联
  4. Selector 通过 select() 方法进行监听。该方法返回有事件发生的通道数
  5. 进一步可得到 SelectionKey
  6. SelectionKey 通过 channel() 方法反向获取 SocketChannel,然后,可以进行操作

示例:通过 NIO,进行服务端与客户端数据通讯

服务端:

public class NioServer {

    public static void main(String[] args) throws Exception {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置非阻塞
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        // 将ServerSocketChannel注册到Selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            if (selector.select(1000) == 0) {
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 如果有客户端连接
                if (selectionKey.isAcceptable()) {
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端连接成功,生成了一个 socketChannel :" + socketChannel.hashCode());
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    // 获取与Channel关联的Buffer
                    ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("服务端收到了:" + new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        }
    }
}

客户端:

public class NioClient {

    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        // (正在)连接服务端
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("因为连接需要时间,客户端不用阻塞,可以做其它工作...");
            }
        }
        // 连接成功
        String str = "Hello Java";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}

先运行服务端,再运行客户端,服务端打印出如下信息:
在这里插入图片描述

标签:Netty,ByteBuffer,Buffer,String,byteBuffer,out,socketChannel,三大,NIO
来源: https://blog.csdn.net/sco5282/article/details/122746140

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有