1. 课程背景
分布式系统的根基在于网络编程,而 Netty 是 Java 领域网络编程的王者。
2. 课程内容
-
第一部分 NIO 编程,三大组件
-
第二部分 netty 入门学习,EventLoop、Channel、Future、Pipeline、Handler、byteBuf
-
第三部分 Netty 进阶学习,粘包半包的解决方法、协议的设计、序列化知识
-
第四部分 Netty 常见参数的学习及优化
-
第五部分 源码
non Blocking IO 非阻塞 IO
1. 三大组件
1.1 Channel & Buffer
channel 有一点类似于 stream,它就是读写数据的双向通道,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是写入,要么是输出。
常见的 Channel 有:
-
FileChannel
-
DatagramChannel
-
socketChannel
-
ServerSocketChannel
buffer 则用来缓冲读写数据,常见的 buffer 有:
-
ByteBuffer
-
MappedByteBuffer
-
DirectByteBuffer
-
HeapByteBuffer
-
short/Int/Long/Float/Double/Char Buffer
1.2 Selector
使用多线程技术
为每个连接分别开辟一个线程,分别去处理对应的 socket 连接
:exclamation: 多线程缺点
-
内存占用高
-
线程上下文切换成本高
-
只适合连接数较少的场景
使用线程池技术
使用线程池,让线程池中的线程去处理连接
这种方式存在以下几个问题:
-
阻塞模式下,线程仅能处理一个连接
-
仅适合短连接场景
使用 Selector
selector 的作用就是配合一个线程来管理多个 channel(fileChannel 因为是阻塞式的,所以无法使用 selector),获取这些 channel 上发生的事件,这些 channel 工作在非阻塞模式下,当一个 channel 中没有执行任务时,可以去执行其他 channel 中的任务。适合连接数多,但流量较少的场景。
若事件未就绪,调用 selector 的 select 方法会阻塞线程,直到 channel 发生了就绪事件。这些事件就绪后,select 方法就会返回这些事件交给 thread 来处理。
2.ByteBuffer
使用案例
有一普通文本文件 data.txt 内容为
1234567890abc
使用 FileChannel 来读取文件内容
@Slf4jpublicclassTestByteBuffer{publicstaticvoidmain(String[] args) {// FileChannel// 1.输入输出流 2.RandomAccessFiletry {FileChannel fileChannel = new FileInputStream("data.txt").getChannel;// 准备缓冲区ByteBuffer buf = ByteBuffer.allocate(10);// 从 Channel 中读取数据,向 Buffer 写入int len;while ((len = fileChannel.read(buf)) != -1) {log.info("读取到的字节:{}", len);buf.flip; // 切换至读模式log.debug("输出内容为:{}", new String(buf.array, 0, len));// while (buf.hasRemaining) { // 是否还剩余数据// byte b = buf.get;// log.debug("输出内容为:{}", (char) b);// }// 切换为写模式buf.clear;}} catch (IOException e) {e.printStackTrace;}}}
2.1 ByteBuffer 使用步骤
-
向 buffer 写入数据,e.g. 调用
channel.read(buf)
-
调用
flip
切换至读模式 -
向 buffer 读取数据,e.g. 调用
buf.get
-
调用
clear
或compact
切换至写模式 -
重复 1~4 步骤
2.2 ByteBuffer 结构
核心属性
字节缓冲区的父类 Buffer 中有几个核心属性,如下:
// Invariants: mark <= position <= limit <= capacityprivate int mark = -1;private int position = 0;private int limit;private int capacity;
-
capacity
:缓冲区的容量。通过构造函数赋予,一旦设置,无法更改。 -
limit
:缓冲区的界限。位于 limit 后的数据不可读写。缓冲的限制不能为负,并且不能大于其容量。 -
position
:下一个读写位置的索引(类似 PC)。缓冲区的位置不能为负,并且不能大于 limit。 -
mark
:记录当前 position 的值。position 被改变后,可以通过调用reset
方法恢复到 mark 的位置。
核心方法:
put
方法
-
put 方法可以将一个数据放入缓冲区
-
进行该操作后,position 的值会 1,指向下一个可以放入的位置。capacity = limit。
flip
方法
-
flip 方法会切换对缓冲区的操作模式,由写 -> 读 / 读 -> 写
-
进行该操作后
-
如果是 写模式 -> 读模式,position = 0,limit 指向最后一个元素的下一个位置,capacity 不变
-
如果是读 -> 写,则恢复为 put 方法中的值
get
方法
-
get
方法会读取缓冲区中的一个值 -
进行该操作后,position 会 1,如果超过了 limit 则会抛出异常
-
注意:
get(i)
方法不会改变 position 的值
rewind
方法
-
该方法只能在读写模式下使用
-
rewind
方法后,会恢复 position、limit 和 capacity 的值,变为进行 get 前的值
clean
方法
-
clean 方法会将缓冲区中的各个属性恢复为最初的状态,position = 0,capacity = limit
-
此时,缓冲区的数据依然存在,处于“被遗忘”状态,下次进行写操作时会覆盖这些数据
mark
和reset
方法
-
mark 方法会将 position 的值保存到 mark 属性中
-
reset 方法会将 position 的值改为 mark 中保存的值
compact
方法
此方法为 ByteBuffer 的方法,而不是 Buffer 的方法
-
compact 会把未读完的数据向前压缩,然后切换到写模式
-
数据前移后,原位置的值并未清零,写时会覆盖之前的值
2.2 ByteBuffer 结构
ByteBuffer 有以下重要属性
-
capacity
-
position
-
limit
刚开始
写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态。
flip 动作发生后,position 切换为读取位置,limit 切换为读取限制。
读取 4 个 byte 后,状态:
clear 动作发生后,状态变为一开始。
compact 方法,是把未读完的部分向前压缩,然后切换至写模式。