Netty

  • 三种I/O模式

image-20240530212145667

Reactor 三种版本

  • 第一种版本

image-20240602225627296

  • 第二种版本

image-20240602225627299

  • 第三种版本

image-20240602225717739

  • 如果使用Netty 使用三种 Reactor 模式

image-20240602225753923

Netty 对 Reactor 的支持

  • 主从Reactor 的 源码追踪

    • mainReactor 只绑定一次
    • Netty给 Netty 分配 ChannelEventGroup的时候使用了哪些算法, 具体是怎么实现的
    • 如何实现跨平台
  • 再对 主从模式的group 方法调用的追踪, 它是先将主 Reactor 交给了 他们的super 类 的group变量,

    然后 对于从Reactor, 他会再 ServerBootstrapAcceptor 这个东西注册注册监听的acceptor 的 东西。?

  • 然后 childGroup 是怎么选出对应的Channel的呢?

    • 这里是使用register

    • 这里默认选择的实现类 MultithreadEventLoopGroup

    • 调用next 方法

      image-20240602235022840

​ 可以看到这里是有两个实现类(策略模式)的 , 第一种是通用, 第二种是 对于大小是2的幂次的。

image-20240602235053034

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;

PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
// 这个处理模式类似于Java HashMap 中的处理
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}

private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;

GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}

@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}

粘包和半包

TCP 粘包和半包

  • 粘包

    粘包现象发生在接收方在一次接收调用中收到多个数据包的数据。例如,发送方连续发送了两个数据包,但接收方在一次接收操作中将这两个数据包的数据一次性接收完毕,导致接收缓冲区中包含两个数据包的内容

    出现原因:

    1. 发送方每次写入的数据 《 套接字缓存区大小
    2. 接收方读取套接字缓冲区数据不够及时
  • 半包

    半包现象发生在接收方在一次接收调用中只接收到一个数据包的一部分数据。这可能是因为数据包在传输过程中被拆分,或者接收方的接收缓冲区大小限制导致

​ 出现原因:

        1. 发送方写入数据》套接字缓冲区大小
        1. 发送的数据大于协议MTU(最大传输单元), 必须进行拆包

提醒: UDP 不存在粘包和半包的情况, 因为它的消息之间存在界限, 需要一个一个的进行接收。

解决方法

找出消息的边界

  • 使用TCP短连接
  • 封装成帧

image-20240603222819947

Netty 粘包和半包

下面三个 解码都是继承于ByteToMessageDecoder 这个抽象类

image-20240603223227844

:question: Netty通过数据积累器收集的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
first = cumulation == null;
// 第一次获取数据的时候, 先判断有无, 然后放到
if (first) {
cumulation = data;
} else {
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
callDecode(ctx, cumulation, out);
}
}

:question:数据积累器是怎么实现的

  1. 一种是内存复制的方式 (这是默认的使用方式)
  2. 一种是组合复制的方式

:question:为什么选择内存复制作为默认的方式,而不是 组合复制

  • 组合复制

MERGE_CUMULATOR是通过copy实现,实际操作的是ByteBuf;COMPOSITE_CUMULATOR操作的CompositeByteBuf可以看做是对ByteBuf的封装,其维护了一个ByteBuf的List列表,每次cumulate操作其实把当前的ByteBuf放到List中。我认为这两种cumulate的性能侧重点不同,merge方式提前copy,那么读取时会更快,反之,使用composite的方式在读取时需要遍历List,读取数据时更慢

:question:LengthFieldBasedFrameDecoder 和 它的解码器的 操作

  • 四个参数分别代表什么
    • lengthFieldOffset
    • lengthFieldLength
    • lengthAdjustment
    • initalBytesToStrips

:question: refCnt 属性的作用是什么?

  • 记录 变量的引用次数

常用的二次编解码

对于前端的解码器过程中, 他会把发送的帧数据解码为 字节, 但是我们需要和对象进行关联, 所以需要二级编码器来处理。相应的也就有了从对象到字节的操作, 也就是编码

  • 这个类解码器是MessageToMessageDecoder

:question:为什么不把两步操作合二为一 : 因为耦合度高, 不利用代码扩展。 没有分层

ProtoBuffer

image-20240603233519720

  • 使用的方式

    先按照语法写好protoBuffer 的 规则

    然后 根据你要生成的语言执行下面的指令中的一个, 得到 类文件就可以使用了

image-20240603233613795

image-20240603233759948

worldClock的研读

Keepalive 和 idle监测

Keepalive

image-20240604232524522

image-20240604232753029

  • 这里和http协议的keepalive 没有关系

image-20240604233133318

idle 监测

image-20240604233330289

image-20240604233631780

Netty 开启 Keepalive 和 idle的方法

Keepalive

  • server 端开启keepalive
1
2
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true)
bootstrap.childOption(NioChannelOption.of(StandardSocketOptions.SO_KEEPALIVE), true)

idle

image-20240604234216780

源码剖析

image-20240605225936938

  • keepalive 是怎么生效的, 再设置ServerBootstrap的时候, 通过childOption进行设置。

  • 有两种设置 方式

    • NioChannelOption- NioSocketChannel

    • ChannelOption- defaultSocketChannel

      这个是由一段if-else 判断构成的

  • idle读的原理

ReaderIdleTimeoutTask的run方法

​ 如果 没有空闲, 重新schedule 然后到时间之后继续操作

​ 如果有空闲, 那么判断是否进来超过1次了 然后进行后续的handler

  • idle写的原理

    同idle读, 但是判断 是否空闲的方式, 不是使用时间间隔, 而是 是否有写入事件的改变

    调用了hasOutputChanged

image-20240605231954294

Netty 的锁

Netty如何玩转内存使用

  • 能用基本类型, 就别用包装类型

  • AtomicLong =》 volatile long + static AtomicLongFieldUpdater

  • zero copy

  • 堆外内存

  • 内存池

Netty源码操作

Netty代码编译 与 总览

  • buffer 就是 处理字节流那些的

  • codec 就是用来编解码

  • stomp 就是用来websocket的

Netty启动服务

主线

image-20240612231352066

启动服务

image-20240612232819248