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

男娘i 2023-10-01 16:54 37阅读 0赞

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 使用示例:

  1. public class BufferDemo {
  2. public static void main(String[] args) {
  3. // 1.创建 Buffer
  4. IntBuffer intBuffer = IntBuffer.allocate(5);
  5. for (int i = 0; i < intBuffer.capacity(); i++) {
  6. intBuffer.put(i);
  7. }
  8. // 2.Buffer 转换。写 --> 读
  9. intBuffer.flip();
  10. while (intBuffer.hasRemaining()) {
  11. System.out.println(intBuffer.get());
  12. }
  13. }
  14. }

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

3. Channel

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

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

Channel 是一个接口:

  1. public interface Channel extends Closeable {
  2. // ...
  3. }

常用的 Channel 类有:

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

示例一:本地文件写数据

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

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

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

示例二:本地文件读数据

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

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

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

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

  1. public class FileChannelRwDemo {
  2. public static void main(String[] args) throws Exception{
  3. FileInputStream in = new FileInputStream("E:\\temp.txt");
  4. FileChannel inFileChannel = in.getChannel();
  5. FileOutputStream out = new FileOutputStream("temp.txt");
  6. FileChannel outFileChannel = out.getChannel();
  7. ByteBuffer byteBuffer = ByteBuffer.allocate(512);
  8. while (true) {
  9. // 此行代码是重点
  10. byteBuffer.clear();
  11. int read = inFileChannel.read(byteBuffer);
  12. if (-1 == read) {
  13. break;
  14. }
  15. byteBuffer.flip();
  16. outFileChannel.write(byteBuffer);
  17. }
  18. in.close();
  19. out.close();
  20. }
  21. }

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,进行服务端与客户端数据通讯

服务端:

  1. public class NioServer {
  2. public static void main(String[] args) throws Exception {
  3. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  4. // 绑定端口
  5. serverSocketChannel.socket().bind(new InetSocketAddress(6666));
  6. // 设置非阻塞
  7. serverSocketChannel.configureBlocking(false);
  8. Selector selector = Selector.open();
  9. // 将ServerSocketChannel注册到Selector
  10. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  11. while (true) {
  12. if (selector.select(1000) == 0) {
  13. System.out.println("服务器等待了1秒,无连接");
  14. continue;
  15. }
  16. Set<SelectionKey> selectionKeys = selector.selectedKeys();
  17. Iterator<SelectionKey> iterator = selectionKeys.iterator();
  18. while (iterator.hasNext()) {
  19. SelectionKey selectionKey = iterator.next();
  20. // 如果有客户端连接
  21. if (selectionKey.isAcceptable()) {
  22. SocketChannel socketChannel = serverSocketChannel.accept();
  23. System.out.println("客户端连接成功,生成了一个 socketChannel :" + socketChannel.hashCode());
  24. socketChannel.configureBlocking(false);
  25. socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
  26. }
  27. if (selectionKey.isReadable()) {
  28. SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
  29. // 获取与Channel关联的Buffer
  30. ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
  31. socketChannel.read(byteBuffer);
  32. System.out.println("服务端收到了:" + new String(byteBuffer.array()));
  33. }
  34. iterator.remove();
  35. }
  36. }
  37. }
  38. }

客户端:

  1. public class NioClient {
  2. public static void main(String[] args) throws Exception {
  3. SocketChannel socketChannel = SocketChannel.open();
  4. socketChannel.configureBlocking(false);
  5. InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
  6. // (正在)连接服务端
  7. if (!socketChannel.connect(inetSocketAddress)) {
  8. while (!socketChannel.finishConnect()) {
  9. System.out.println("因为连接需要时间,客户端不用阻塞,可以做其它工作...");
  10. }
  11. }
  12. // 连接成功
  13. String str = "Hello Java";
  14. ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
  15. socketChannel.write(byteBuffer);
  16. System.in.read();
  17. }
  18. }

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

发表评论

表情:
评论列表 (有 0 条评论,37人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaNIONetty框架

    前言   随着移动互联网的爆发性增长,小明公司的电子商务系统访问量越来越大,由于现有系统是个单体的巨型应用,已经无法满足海量的并发请求,拆分势在必行。 ![6bef4