💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] 1. Netty 在内部使用了回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理。当一个新的连接已经被建立时,ChannelHandler 的 channelActive()回调方法将会被调用,并将打印出一条信息。 2. 所有的 Netty 服务器都需要以下两部分。 * 至少一个 ChannelHandler—该组件实现了服务器对从客户端接收的数据的处理,即 它的业务逻辑。 * 引导—这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的 端口上。 在本小节的剩下部分,我们将描述 Echo 服务器的业务逻辑以及引导代码。 3. 每个 Channel 都拥有一个与之相关联的 **ChannelPipeline**,其持有一个 ChannelHandler 的实例链。也就是说一个pipeline上有多个channel,这些channel是串联起来的,并且上一个channel的结果会传递到下一个channel。 ![](https://box.kancloud.cn/6a729eb2adb0e1ad312191a225300e1d_700x297.png) :-: 图一 ## 1. 引导(Bootstrap) ### 1.1 ServerBootstrap 服务端启动引导类,设置netty服务端启动参数 两种类型的引导: > 1. 一种用于客户端(简单地称为 Bootstrap) > 2. 而另一种( ServerBootstrap)用于服务器。 > ## 2. EventLoopGroup EventLoop 定义了 Netty 的核心抽象, 用于处理连接的生命周期中所发生的事件。 ![](https://box.kancloud.cn/792969533c082e041c425c17efa20952_833x641.png) :-: 图二 1. 一个 EventLoopGroup 包含一个或者多个 EventLoop; 2. 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定; 3. 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理; 4. 一个 Channel 在它的生命周期内只注册于一个 EventLoop; 5. 一个 EventLoop 可能会被分配给一个或多个 Channel。 注意,在这种设计中, 一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的, 实际上消除了对于同步的需要。 ## 1. channel 基本的 I/O 操作( bind()、 connect()、 read()和 write())依赖于底层网络传输所提供的原语。在基于 Java 的网络编程中,其基本的构造是 class Socket。 Netty 的 Channel 接口所提供的 API,大大地降低了直接使用 Socket 类的复杂性。此外, Channel 也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单: 每个channel都对应一个物理连接,每个连接都有自己独立的TCP参数。 > 1. EmbeddedChannel; > 2. LocalServerChannel; > 3. NioDatagramChannel; > 4. NioSctpChannel; > 5. NioSocketChannel。 1. channel和childHandler netty服务是父级的、初始的channel线程,负责监听整个服务。后接入的链接都会开启一个channel,成为childChannel。 在基类AbstractBootstrap有handler方法,目的是添加一个handler,监听Bootstrap的动作,客户端的Bootstrap中,继承了这一点。 在服务端的ServerBootstrap中增加了一个方法childHandler,它的目的是添加handler,用来监听已经连接的客户端的Channel的动作和状态。 handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行,这是两者的区别。 ![](https://box.kancloud.cn/1e3870519ff613cb8b95dc3538b32f59_859x272.png) 如图所示, 每个 Channel 都将会被分配一个 ChannelPipeline 和 ChannelConfig。ChannelConfig 包含了该 Channel 的所有配置设置, 并且支持热更新。由于特定的传输可能具有独特的设置, 所以它可能会实现一个 ChannelConfig 的子类型。 Netty 的 Channel 实现是线程安全的,因此你可以存储一个到 Channel 的引用,并且每当你需要向远程节点写数据时, 都可以使用它, 即使当时许多线程都在使用它。 ## ChannelHandler 实际的数据业务处理逻辑 1. 针对不同类型的事件来调用 ChannelHandler; 2. 应用程序通过实现或者扩展 ChannelHandler 来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑; 3. 在架构上, ChannelHandler 有助于保持业务逻辑与网络处理代码的分离。这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求。 4. ChannelInitializer。这是关键。当一个新的连接被接受时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把一个你的EchoServerHandler 的实例添加到该 Channel 的 ChannelPipeline 中。正如我们之前所 解释的,这个 ChannelHandler 将会收到有关入站消息的通知 ### 方法 #### channelActive() 当有连接建立时调用 #### channelRead(ChannelHandlerContext ctx, Object msg) 1. 每当接收数据时,都会调用这个方法。 2. 需要注意的是,由服务器发送的消息可能会被分块接收。 也就是说,如果服务器发送了 5 字节, 那么不能保证这 5 字节会被一次性接收。 即使是对于这么少量的数据, channelRead0()方法也可能会被调用两次,第一次使用一个持有 3 字节的 ByteBuf( Netty 的字节容器),第二次使用一个持有 2 字节的 ByteBuf。作为一个面向流的协议, TCP 保证了字节数组将会按照服务器发送它们的顺序被接收。 ## ChannelFuture Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此, Netty 提供了ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。 ## ChannelPipeline ### 与ChannelHandler的关系 1. 当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表ChannelHandler 和 ChannelPipeline 之间的绑定。虽然这个对象可以被用于获取底层的 Channel,但是它主要还是被用于写出站数据。 2. **ChannelPipeline 提供了 ChannelHandler 链的容器**,并定义了用于在该链上传播入站和出站事件流的 API。当 Channel 被创建时, 它会被自动地分配到它专属的 ChannelPipeline。Handler安装过程如下: > 1. 一个ChannelInitializer的实现被注册到了ServerBootstrap中 > 2. 当 ChannelInitializer.initChannel()方法被调用时, ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler > 3. ChannelInitializer 将它自己从 ChannelPipeline 中移除 ### 入、出站 ![](https://box.kancloud.cn/55c6e14a3afd8613a5cd9052ca4c6984_858x209.png) :-: 图三 数据、事件入站出站示意图 从一个客户端应用程序的角度来看,如果事件的运动方向是从客户端到服务器端, 那么我们称这些事件为出站的,反之则称为入站的。 ~~~ ChannelInboundHandler :入站Handler ChannelOutboundHandler :出站Handler ~~~ #### 入站 如果一个消息或者任何其他的入站事件被读取, 那么它会从 ChannelPipeline 的头部开始流动,并被传递给第一个 ChannelInboundHandler。这个 ChannelHandler 不一定会实际地修改数据, 具体取决于它的具体功能,在这之后,数据将会被传递给链中的下一个ChannelInboundHandler。最终,数据将会到达 ChannelPipeline 的尾端, 届时,所有处理就都结束了-**数据处理是一个Handler接着一个Handler执行的。** #### 出站 数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为 Socket。通常情况下,这将触发一个写操作。 ## ChannelHandlerContext 1. 通过使用作为参数传递到每个方法的 ChannelHandlerContext,事件可以被传递给当前ChannelHandler 链中的下一个 ChannelHandler。 2. 因为你有时会忽略那些不感兴趣的事件,所以 Netty提供了抽象基类 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter。通过调用 ChannelHandlerContext 上的对应方法,每个都提供了简单地将事件传递给下一个 ChannelHandler的方法的实现。随后, 你可以通过重写你所感兴趣的那些方法来扩展这些类。 如果将两个类别的 ChannelHandler都混合添加到同一个 ChannelPipeline 中会发生什么。 虽然 ChannelInboundHandle 和ChannelOutboundHandle 都扩展自 ChannelHandler,但是 Netty 能区分 ChannelInboundHandler 实现和 ChannelOutboundHandler 实现,并确保数据只会在具有相同定向类型的两个 ChannelHandler 之间传递. ### netty发送数据方式 在 Netty 中, 有两种发送消息的方式: 1. 直接写到 Channel 中,消息从ChannelPipeline 的尾端开始流动 2. 写到和 ChannelHandler相关联的ChannelHandlerContext对象中,消息从 ChannelPipeline 中的下一个 ChannelHandler 开始流动。 ### 区别 ![](https://box.kancloud.cn/94a08bb290751e92aa1c80bf3bd9edbe_963x123.png) 1. ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接, 而 Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。 2. 引导一个客户端只需要一个 EventLoopGroup,但是一个ServerBootstrap 则需要两个(也可以是同一个实例)。 **为什么ServerBootstrap需要两个EventLoopGroup?** 因为服务器需要两组不同的 Channel。第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的在监听的套接字。 而第二组将包含所有已创建的用来处理传入客户端连接( 对于每个服务器已经接受的连接都有一个) 的 Channel。如下图: ![](https://box.kancloud.cn/f94e81a67fb7a4c5e0f64428deb9676f_906x393.png) 与 ServerChannel 相关联的 EventLoopGroup 将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup就会给它的Channel分配一个EventLoop。 实际上, ServerBootstrap 类也可以只使用一个 EventLoopGroup,此时其将在两个场景下共用同一个 EventLoopGroup。 ## 传输 ![](https://box.kancloud.cn/a42b3c16f9354a6efe06a7df0421d099_880x182.png) ![](https://box.kancloud.cn/ae083cf6b4019846fd1b52243acaff1d_876x212.png) ### 1 非阻塞I/O 选择器背后的基本概念是充当一个注册表,在那里你将可以请求在 Channel 的状态发生变化时得到通知并触发动作。可能的状态变化有: > 1. 新的 Channel 已被接受并且就绪; > 2. Channel 连接已经完成; > 3. Channel 有已经就绪的可供读取的数据; > 4. Channel 可用于写数据。 对应selector模式 ![](https://box.kancloud.cn/9b78b20031c226efcd17d1753f7e0fdb_851x235.png) ## ByteBuf——Netty 的数据容器 ### 读写索引 ## 异常处理 ## channel生命周期 ![](https://box.kancloud.cn/089a7096c9703f94c54276a705e4f88a_1278x369.png)