Netty入门之基础篇

869 阅读4分钟

本次文章主要给大家带来NIO的相关知识点,一些基础的理论知识,巩固大家的对NIO的理解。好了废话不多说直接步入正题。

一:NIO基础

什么是NIO?

顾名思义NIO:non-blocking io 也可称作new io 非阻塞 IO

NIO的三大组件

1. Channel&Buffer

Channel:

  • 基本概念:
    Channel有点类似有stream,它是读写数据的双向通道,可以将数据读入buffer,也可以将buffer的数据写入Channel,而之前的stream要么是输入要么就是输出,由此可见Channel比stream更为底层

创建的Channel:

  • FileChannel(文件的数据传输通道)
  • DataGramChannel(UDP网络编程时的数据传输通道)
  • SockeChannel(TCP编程时数据传输通道 客户端服务器都可使用)
  • ServerSocketChannel(TCP编程时数据传输通道 专用于服务器)
  • ......

Buffer

  • 基本概念:
    Buffer则是用来缓冲读写数据的。

常见的Buffer

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer(直接内存)
    • HeapByteBuffer(堆内存)
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

2. Selector

  • 多线程版的设计:

⚠️ 多线程版缺点

内存占用较高 线程上下文切换的成本高 只适合连接数少的场景

  • 线程池版的设计

⚠️ 线程池版缺点

阻塞模式下,线程仅能处理一个socket连接 仅适合短链接场景

  • selector版的设计
    selector基本概念 selector的作用就是配合一个线程来管理多个channel,获取这些channel上发生的事件,这些channel工作在非阻塞模式下,不会让线程吊死在一个channel上。适合连接数特别多,但流量低的场景(low traffic
    大致如图所示:

调用selector的select()会阻塞直到channel发生了读写就绪事件,这些事件发生,select方法就会返回这些事件交给thread来处理。

Channel和Buffer的小demo

Channel获取方式(FileChannel的获取)

  1. 通过输入输出流,可以间接的获取,
  2. 通过randomAccessFile ByteBuffer获取方式 通过静态方法ByteBuffer.allocate(16)
  • TestByteBuffer
@Slf4j
public class TestByteBuffer {

    public static void main(String[] args) {
        // FileChannel
        // 1. 输入输出流, 
        
        // 2. RandomAccessFile
        // RandomAccessFile file = new RandomAccessFile("helloword/data.txt", "rw")
        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {
            // 准备缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);
            while(true) {
                // 从 channel 读取数据,向 buffer 写入
                int len = channel.read(buffer);
                log.debug("读取到的字节数 {}", len);
                if(len == -1) { // 没有内容了
                    break;
                }
                // 打印 buffer 的内容
                buffer.flip(); // 切换至读模式
                while(buffer.hasRemaining()) { // 是否还有剩余未读数据
                    byte b = buffer.get();
                    log.debug("实际字节 {}", (char) b);
                }
                buffer.clear(); // 切换为写模式
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

data.txt的内容是:

1234567890abcd

ByteBuffer 的使用

  1. 向Buffer写入数据,例如调用channel.read(buffer);
  2. 调用读模式flip()
  3. 从buffer读取数据,调用buffer.get();
  4. 调用clear()compact()切换至写模式,后面介绍两种方法的不同之处。

ByteBuffer的结构

ByteBuffer有以下的重要属性:

  • capacity 容量
  • position 位置(位移)
  • limit 限制(等于容量)

ByteBuffer初始化的时候:

写模式下,position是写入位置,limit等于容量,下图是写入了4个字节后的状态。

当发生filp()动作后,position切换为读取位置,limit切换为读取限制。

读取了4个字节后

clear动作发生后,ByteBuffer进行了初始化

compact()方法,是把未读完的部分向前压缩,然后切换到写模式下,如图所示: (这里将clear和compact进行了讲解,大致区别也是如此)

ByteBuffer常见的方法

  • 分配空间
    使用allocate方法为ByteBuffer分配空间,其他的Buffer也有该方法。

例如:

ByteBuffer buf = ByteBuffer.allocate(10);
  • 向ByteBuffer写入数据
    方法有两种:
    • 调用channelread方法;
    • 调用buffer自己的put方法;

例如:

int readBytes = channel.read(buffer);

buf.put((byte)127);
  • 从buffer读取数据
    方法同样有两种:
      1. 调用channelwrite方法;
      1. 调用buffer的get方法;

例如:

int writeBytes = channel.write(buf);

byte b = buf.get()

💡 注意:

buffer的get()方法会让position读指针向后移动,如果想要重复读取数据:

  • 可以调用rewind()方法将position重新置为0;
  • 调用get(int i)获取索引 i 的内容,它不回移动读指针

ByteBuffer的mark和reset

mark是在读取的时候,做一个 标记,即使position发生改变,只要调用 reset方法就能回到mark的位置

💡 注意:
rewindflip都会清楚掉mark位置,前者是将position重置为0,后者是切换读模式,切记切记

字符串和ByteBuffer的互转

 /**
         * 1.字符串转化为ByteBuffer
         * 此方法要想将ByteBuffer转换为字符串则需要切换为读模式-使用flip()方法  该方法还是写模式
         */
        ByteBuffer buffer1 = ByteBuffer.allocate(16);
        buffer1.put("hello".getBytes());
        debugAll(buffer1);

        /**
         * 2.字符串转化为ByteBuffer
         */
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
        debugAll(buffer2);

        /**
         * 3.字符串转化为ByteBuffer
         */
        ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
        debugAll(buffer3);

        /**
         * ByteBuffer 转换为字符串
         */
        String str3 = StandardCharsets.UTF_8.decode(buffer3).toString();
        System.out.println(str3);

关于Netty入门暂时结束。如果有帮助,欢迎点赞关注。

微信搜索【码上遇见你】获取更多精彩内容