Netty入门

2024/04/03 网络编程 共 3214 字,约 10 分钟

Netty

JDK和Netty字节缓冲的比较

JDK ByteBuffer

  • 长度固定:一旦分配完成,容量不能动态扩展和收缩
  • 只有一个position指针记录读写位置
  • byteBuffer的内容,是按position指针当前位置顺序写
  • 读取byteBuffer的内容,是读写positioncapacity之间的数据
    • 所以读取时必须调用flip()position为0,才能读到正确数据

Netty ByteBuf

  • ByteBuf则是通过两个位置指针来协助缓冲区的读写操作,读操作使用readerIndex、写操作使用writerIndex
  • 读操作的时候:readIndex++,读取的内容为,readIndex - writeIndex,但是不会超过writeIndex。 之后0~readIndex的数据视为discard无用,调用discardReadBytes方法可以释放掉。
  • 写操作的时候:writeIndex++,write ~ capacity之间的数据都是可以写的

Channel

  • 客户端与服务端之间连接的抽象
    • 显示当前通道状态,比如开启状态,连接状态等。
    • 提供通道配置参数,例如设定接收缓冲区的大小。
    • 支持多种IO操作,如读操作、写操作、连接和绑定等。同时,它也可以处理与通道相关联的所有IO事件和请求。

在Netty中,AbstractChannel 是整个家族的基类,它的子类包括 AbstractNioChannelAbstractOioChannelAbstractEpollChannel 等,每一种都代表了不同的 I/O 模型和协议类型。常用的 Channel 实现类有:

  • NioServerSocketChannel 异步 TCP 服务端。
  • NioSocketChannel 异步 TCP 客户端。
  • OioServerSocketChannel 同步 TCP 服务端。
  • OioSocketChannel 同步 TCP 客户端。
  • NioDatagramChannel 异步 UDP 连接。
  • OioDatagramChannel 同步 UDP 连接。

attr

Netty中的 Channel.attr() 方法让我们可以在 Channel 上绑定和使用自定义属性。这些属性会在整个 Channel 的生命周期中保持,也就是说,可以在任何时候,任何地方,通过同样的 Channel.attr()方法访问获取到这些属性。

实际上,Attr 是一个接口,Channel 通过类似于 Map 的机制来维护它们。使用 AttributeKey 非常简单,首先我们需要创建一个 AttributeKey 对象,例如:

private static final AttributeKey<Integer> LOGIN_COUNT = AttributeKey.valueOf("loginCount");

上面的代码创建了一个 AttributeKey 对象,它的键名是 “loginCount”,值的类型是 Integer。我们可以在 Channel 上使用 AttributeKey 存储或获取 Integer 类型的属性,例如:

Channel channel = ...; // 获取一个 Channel 对象
channel.attr(LOGIN_COUNT).set(1); // 存储一个 Integer 类型的属性
Integer loginCount = channel.attr(LOGIN_COUNT).get(); // 获取 Integer 类型的属性

获取channel的状态

boolean isOpen(); //如果通道打开,则返回true
boolean isRegistered();//如果通道注册到EventLoop,则返回true
boolean isActive();//如果通道处于活动状态并且已连接,则返回true
boolean isWritable();//当且仅当I/O线程将立即执行请求的写入操作时,返回true。

writeAndFlush流程解析

  • write():将数据写到ChannelOutboundBuffer的缓存中
  • flush():真正将数据写到socket缓冲区

比较两个类的writeAndFlush()

  • ChannelHandlerContext :是从 pipeline 链中的当前节点开始往前找到第一个 outBound 类型的 handler 把对象往前进行传播,如果这个对象确认不需要经过其他 outBound 类型的 handler 处理,就使用这个方法
  • Channel 是从 pipeline 链中的最后一个 outBound 类型的 handler 开始,把对象往前进行传播,如果你确认当前创建的对象需要经过后面的 outBound 类型的 handler,那么就调用此方法

ChannelGroup

  • Netty提供的维护多个Channel的类,提供一些对于Channel的批量操作
  • 支持添加、移除、遍历、等操作
  • 支持向所有维护的Channel发送信息,信息由于网络原因会乱序

ChannelPipeline

每个Channel都定义了一个ChannelPipeline,底层使用双向链表维护了多个ChannelHandlerContext,一个ChannelHandlerContext维护了一个ChannelHandler,其中ChannelHandler用于处理具体逻辑。

Netty定义的HeadHandler和TailHandler永远位于链表的两端,开发者自定义的ChannelHandler根据插入顺序位于中间位置。

Netty中所有的操作(如:读、写、连接、异常处理等,可以参考LoggingHandler类中对事件的归纳)都被定义为事件,对每一个事件的处理,根据事件类型要么是从链表的头到尾,要么是链表尾到头的流程。

并且Netty对事件模型的处理机制进行了优化,一般一个事件不会经过所有的ChannelHandler,会根据事件的类型跳过一些对该事件不会产生影响的ChannelHandler,但是流程顺序不会改变。如从输出信息时,不会经过ChannelInBoundHandler。

TODO Netty的跳过机制: io.netty.channel.ChannelHandlerMask#isSkippable

ChannelHandlerContext

EventLoop

继承实现关系

netty-start-uml-EventLoop

特点

  • 单线程执行:EventLoop本质是一个只会由一个线程执行的线程池
  • 多路复用:即一条线程可以监听多个 Channel,EventLoop 使用底层操作系统提供的 I/O 模型(例如 Java NIO)实现多路复用,从而实现并发处理多个连接的能力。
  • 事件驱动:EventLoop 通过监听与 Channel 相关的 I/O 事件(例如读、写、连接等),并将其转化为事件对象(例如 ChannelReadEventChannelWriteEvent 等)进行处理。这种事件驱动模型可以让 Netty 高效地响应 I/O 事件,避免了轮询等不必要的操作。
  • 非阻塞操作:EventLoop 中所有的操作都是非阻塞的,包括 I/O 操作和异步任务的执行。这样可以确保整个系统始终处于高效运行状态,并提高了系统的可伸缩性。

EventLoopGroup

本质是个线程池,负责管理一个或多个EventLoop,每个EventLoop负责处理其分配的所有Channel的生命周期中发生的事件

服务端使用时会创建Boss Group和Worker Group,Boss负责监听客户端连接,Worker负责已连接客户端的读写操作

Boss Group监听到新连接请求时,会将其注册到某个EventLoop的Selector上,并将其关联到对于的Channel对象。之后Worker Group将负责处理该已建立连接的全部读写操作

EventLoopGroup支持优雅关闭,调用shutdownGracefully(),将不在接收新任务,旧任务全部完成后安全关闭

ChannelReadEvent等

文档信息

Search

    Table of Contents