ChannelInitializer
- ChannelInitializer 是一个特殊的 ChannelInboundHandler。
- 它提供了一种简单的方式来初始化一个 Channel,一旦它被注册到其 EventLoop 中。
- 通常,在
的上下文中使用它来设置 Channel 的 ChannelPipeline。Bootstrap.handler(ChannelHandler); ServerBootstrap.handler(ChannelHandler); ServerBootstrap.childHandler(ChannelHandler); - 用例:
public class MyChannelInitializer extends ChannelInitializer { public void initChannel(Channel channel) { channel.pipeline().addLast("myHandler", new MyHandler()); } } ServerBootstrap bootstrap = ...; ... bootstrap.childHandler(new MyChannelInitializer()); ... - 请注意,这个类被标记为 ChannelHandler.Sharable,因此实现必须安全地重用。
- 这个类被标记为
ChannelHandler.Sharable,这意味着多个Channel可以共享一个MyChannelInitializer实例。因此,实现这个类的时候需要保证线程安全和无状态性,以便可以在多个Channel上重用。
- 这个类被标记为
源码阅读
- 省内存
- 这段代码定义了一个
Set集合,用于保存ChannelHandlerContext对象。在 Netty 的Bootstrap和ServerBootstrap中,ChannelInitializer经常被多个Channel共享。为了避免为每个Channel分别保存ChannelHandlerContext对象导致内存开销过大,这里使用了一个共享的Set集合来保存这些对象。 - 相比于使用
Attribute,使用共享的Set集合可以减少内存开销,因为Attribute会将值保存到每个Channel的属性中,而Set集合只需要保存对ChannelHandlerContext的引用,而不是保存具体的值。这意味着,共享的Set集合不需要为每个Channel分别保存一份值,因此可以减少内存开销。
- 这段代码定义了一个
- initChannel()方法
- 这个方法会在Channel注册后被调用。在方法返回后,这个实例将从Channel的ChannelPipeline中移除。
- 换句话说,这个方法提供了一种在Channel注册之后进行一些初始化操作的机制,初始化完成后,这个实例就不再被ChannelPipeline所使用了。
- 可以用如下代码验证:
class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("httpServerCodec", new HttpServerCodec()) .addLast("testHttpServerHandler", new MyHttpServerHandler()) .addLast("myException",new ExceptionHandler()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("ChannelInitializer 被移除"); } } - 毕竟这个类只是提供了一个简便的初始化Channel的方法,用完就不需要了
class CustomServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline .addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) .addLast(new LengthFieldPrepender(4)) .addLast(new StringDecoder(CharsetUtil.UTF_8)) .addLast(new StringEncoder(CharsetUtil.UTF_8)) .addLast(new CustomServerHandler()); } }
- 这个方法会在Channel注册后被调用。在方法返回后,这个实例将从Channel的ChannelPipeline中移除。
ChannelHandlerContext
-
该类提供了让 ChannelHandler 与其 ChannelPipeline 和其他处理器(Handler)进行交互的功能。其中,处理器可以通知 ChannelPipeline 中的下一个 ChannelHandler,并动态地修改其所属的 ChannelPipeline,以及从其他线程中访问 ChannelHandlerContext 等功能。
-
通知
- 您可以通过调用这里提供的各种方法来通知同一 ChannelPipeline 中最近的处理器。请参阅 ChannelPipeline 以了解事件的流动方式。
-
修改
- Pipeline 您可以通过调用 pipeline() 方法来获取处理器所属的 ChannelPipeline。在运行时,一个复杂的应用程序可以动态地插入、删除或替换处理器管道中的处理器。
public class MyHandler extends ChannelInboundHandlerAdapter { private boolean isOn; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { isOn = true; ctx.fireChannelActive(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { if (isOn) { System.out.println("Handler is ON: " + msg); // 增加一个处理器到 pipeline 中 ctx.pipeline().addLast("myHandler", new MyHandler2()); isOn = false; } else { System.out.println("Handler is OFF: " + msg); // 移除一个处理器 ctx.pipeline().remove(this); } super.channelRead(ctx, msg); } } public class MyHandler2 extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("MyHandler2: " + msg); super.channelRead(ctx, msg); } } /** 在这个例子中,当处理器 MyHandler 第一次接收到消息时,它会在 pipeline 中添加一个新的处理器 MyHandler2,并将 isOn 标志设置为 false。当处理器再次接收到消息时,它会从 pipeline 中移除自身。 可以看到,通过在处理器中使用 pipeline() 方法,我们可以在运行时动态地修改 ChannelPipeline。 */
- Pipeline 您可以通过调用 pipeline() 方法来获取处理器所属的 ChannelPipeline。在运行时,一个复杂的应用程序可以动态地插入、删除或替换处理器管道中的处理器。
-
检索以供以后使用
- 您可以保留 ChannelHandlerContext 以供以后使用,例如从处理程序方法外触发事件,甚至从不同的线程中触发。
public class MyHandler extends ChannelDuplexHandler { private ChannelHandlerContext ctx;//保留 ChannelHandlerContext public void beforeAdd(ChannelHandlerContext ctx) { this.ctx = ctx; } public void login(String username, password) { ctx.write(new LoginMessage(username, password)); } ... } -
存储有状态的信息
- attr(AttributeKey) 允许您存储和访问与 ChannelHandler/Channel 及其上下文相关的有状态信息。请参阅 ChannelHandler 以了解管理有状态信息的各种推荐方式。
public class MyHandler extends SimpleChannelInboundHandler<String> { private static final AttributeKey<String> CLIENT_ID = AttributeKey.valueOf("clientId"); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // 为客户端生成一个唯一的 ID,并将其与 ChannelHandlerContext 关联起来 String clientId = UUID.randomUUID().toString(); ctx.channel().attr(CLIENT_ID).set(clientId); System.out.println("Client " + clientId + " connected."); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { // 处理客户端发送的消息 String clientId = ctx.channel().attr(CLIENT_ID).get(); System.out.println("Received message '" + msg + "' from client " + clientId + "."); } } /** 在这个例子中,当一个新的客户端连接时,我们为它生成一个唯一的 ID, 并将它与客户端的 `ChannelHandlerContext` 关联起来。 当客户端发送消息时,我们可以通过这个 ID 来识别它,从而对消息进行处理。 */
- attr(AttributeKey) 允许您存储和访问与 ChannelHandler/Channel 及其上下文相关的有状态信息。请参阅 ChannelHandler 以了解管理有状态信息的各种推荐方式。
-
处理器(Handler)可以拥有多个 ChannelHandlerContext 请注意,ChannelHandler 实例可以添加到多个 ChannelPipeline 中。这意味着单个 ChannelHandler 实例可以拥有多个 ChannelHandlerContext,并且因此如果将其添加到一个或多个 ChannelPipeline 中,则可以使用不同的 ChannelHandlerContexts 调用单个实例。还请注意,应将要添加到多个 ChannelPipeline 中的 ChannelHandler 标记为 ChannelHandler.Sharable。
public class MyHandler extends ChannelInboundHandlerAdapter { private ChannelHandlerContext ctx1; private ChannelHandlerContext ctx2; @Override public void channelRegistered(ChannelHandlerContext ctx) { if (ctx.name().equals("pipeline1")) { this.ctx1 = ctx; } else if (ctx.name().equals("pipeline2")) { this.ctx2 = ctx; } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (ctx == ctx1) { // 处理 pipeline1 中的消息 } else if (ctx == ctx2) { // 处理 pipeline2 中的消息 } } // 其他处理器方法省略 } ChannelPipeline pipeline1 = ... ChannelPipeline pipeline2 = ... MyHandler myHandler = new MyHandler(); pipeline1.addLast("handler1", myHandler); pipeline2.addLast("handler2", myHandler); /** 由于 MyHandler 实例可以添加到多个 ChannelPipeline 中,因此我们可以在两个不同的 ChannelPipeline 中使用同一个 MyHandler 实例,并且每个 ChannelPipeline 都会创建一个新的 ChannelHandlerContext 对象来处理事件。 */
小结
-
ChannelHandlerContext 是 Netty 框架中用于实现 ChannelHandler 与 ChannelPipeline 之间交互的重要组件,其主要功能包括:
- 提供与 ChannelPipeline 中其他 ChannelHandler 的交互,通过调用 ChannelHandlerContext 的方法向前或向后传递事件。
- 允许动态修改 ChannelPipeline 中的处理器,可以在运行时插入、删除或替换处理器。
- 允许存储和访问与 ChannelHandler/Channel 及其上下文相关的有状态信息。
- 允许在 ChannelHandler 中保留 ChannelHandlerContext,以便稍后触发事件。
- 允许在一个 ChannelHandler 实例被添加到多个 ChannelPipeline 中时拥有多个 ChannelHandlerContext 实例,从而实现对不同 ChannelPipeline 的管理。
-
总的来说,ChannelHandlerContext 提供了 ChannelHandler 和 ChannelPipeline 之间高效交互的方式,并允许开发者实现更为灵活和高效的网络编程。
-
它代表了一个 ChannelHandler 和 ChannelPipeline 中的上下文信息。通过 ChannelHandlerContext,我们可以访问到所属的 Channel、ChannelPipeline,以及相关的一些状态信息。
addLast()方法
-
创建上下文
-
添加进双向链表
- 具体来说,它的实现分为以下几个步骤:
- 获取当前链表尾部的前一个节点
prev。 - 将新的节点
newCtx的prev设置为prev。 - 将新的节点
newCtx的next设置为tail,即链表尾部的哨兵节点。 - 将
prev的next设置为newCtx,即将新的节点插入到链表中。 - 将
tail的prev设置为newCtx,即更新链表尾部的哨兵节点的前驱节点为新的节点。
- 获取当前链表尾部的前一个节点
- 如此看来,ChannelPipeline其实是ChannelHandlerContext的容器:
- pipeline是一个双向链表,每个节点包含一个handler和一个context。
- pipeline有一个head和一个tail节点,分别表示入站和出站的起始点。
- pipeline内部有一个head和一个tail属性,分别指向链表的头节点和尾节点,它们都是DefaultChannelHandlerContext的实例。
- header - InboundHandler1 - InboundHandler2 - OutboundHandler1 - OutboundHandler2 - OutboundHandler3 - InboundHandler3 - tail- 当一个inbound事件从header开始流动时,它会按照顺序经过所有的inbound handler,直到到达tail。
- 当一个outbound事件从tail开始流动时,它会按照逆序经过所有的outbound handler,直到到达header。
- 这样可以保证事件总是从最近的handler开始处理。
- head是outbound事件的终点,tail是inbound事件的终点。
- header负责将outbound事件转换为底层的I/O操作,例如Socket.write()。
- tail负责将inbound事件转发给最近的inbound handler,或者丢弃或记录它们,如果没有inbound handler可以处理它们。
- pipeline内部有一个head和一个tail属性,分别指向链表的头节点和尾节点,它们都是DefaultChannelHandlerContext的实例。
- pipeline可以动态地添加或删除handler,每个handler可以有一个name或者一个默认的id来标识。
- pipeline可以根据handler的类型(入站或出站)和顺序(先添加或后添加)来决定handler的执行顺序。
- pipeline可以通过context来传递事件和数据给下一个或上一个handler,也可以通过channel来传递给整个pipeline。
- 具体来说,它的实现分为以下几个步骤:
-
调用Handler的added()方法
- 注意它的调用逻辑:
- 如果当前 Channel 还未注册到 EventLoop 上,那么设置新的 AbstractChannelHandlerContext 实例为 "addPending" 状态,并且添加一个任务到 EventLoop 中,等待 Channel 注册完成后回调 ChannelHandler.handlerAdded() 方法。
- 如果当前线程(就是调用addLast方法的线程)不是 EventLoop 线程,那么添加一个任务到 EventLoop 中,在 EventLoop 中回调 ChannelHandler.handlerAdded() 方法。
- 如果当前线程就是 EventLoop 线程,直接调用 callHandlerAdded0() 方法回调 ChannelHandler.handlerAdded() 方法。
- 注意它的调用逻辑: