code-learning/netty/49-Netty 源码解析-ChannelHandler(二)之 ChannelInitializer.md

205 lines
8.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 精尽 Netty 源码解析 —— ChannelHandler之 ChannelInitializer
# 1. 概述
本文,我们来分享 **ChannelInitializer** 。它是一个**特殊**的ChannelInboundHandler 实现类,用于 Channel 注册到 EventLoop 后,**执行自定义的初始化操作**。一般情况下,初始化自定义的 ChannelHandler 到 Channel 中。例如:
- 在 [《精尽 Netty 源码分析 —— 启动(一)之服务端》](http://svip.iocoder.cn/Netty/bootstrap-1-server) 一文中ServerBootstrap 初始化时,通过 ChannelInitializer 初始化了用于接受( accept )新连接的 ServerBootstrapAcceptor 。
- 在有新连接接入时,服务端通过 ChannelInitializer 初始化,为客户端的 Channel 添加自定义的 ChannelHandler ,用于处理该 Channel 的读写( read/write ) 事件。
OK让我们看看具体的代码实现落。
# 2. ChannelInitializer
`io.netty.channel.ChannelInitializer` ,继承 ChannelInboundHandlerAdapter 类Channel Initializer **抽象类**。代码如下:
```
@Sharable
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {
```
- 通过 `@Sharable` 注解,支持共享。
## 2.1 initChannel
`#initChannel(ChannelHandlerContext ctx)` 方法,执行行自定义的初始化操作。代码如下:
```
// We use a ConcurrentMap as a ChannelInitializer is usually shared between all Channels in a Bootstrap /
// ServerBootstrap. This way we can reduce the memory usage compared to use Attributes.
/**
* 由于 ChannelInitializer 可以在 Bootstrap/ServerBootstrap 的所有通道中共享,所以我们用一个 ConcurrentMap 作为初始化器。
* 这种方式,相对于使用 {@link io.netty.util.Attribute} 方式,减少了内存的使用。
*/
private final ConcurrentMap<ChannelHandlerContext, Boolean> initMap = PlatformDependent.newConcurrentHashMap();
1: private boolean initChannel(ChannelHandlerContext ctx) throws Exception {
2: if (initMap.putIfAbsent(ctx, Boolean.TRUE) == null) { // Guard against re-entrance. 解决并发问题
3: try {
4: // 初始化通道
5: initChannel((C) ctx.channel());
6: } catch (Throwable cause) {
7: // 发生异常时,执行异常处理
8: // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).
9: // We do so to prevent multiple calls to initChannel(...).
10: exceptionCaught(ctx, cause);
11: } finally {
12: // 从 pipeline 移除 ChannelInitializer
13: remove(ctx);
14: }
15: return true; // 初始化成功
16: }
17: return false; // 初始化失败
18: }
```
- 第 2 行:通过 `initMap` 属性,解决并发问题。对应 Netty Git 提交是 https://github.com/netty/netty/commit/26aa34853a8974d212e12b98e708790606bea5fa 。
- 第 5 行:调用 `#initChannel(C ch)` **抽象**方法,执行行自定义的初始化操作。代码如下:
```
/**
* This method will be called once the {@link Channel} was registered. After the method returns this instance
* will be removed from the {@link ChannelPipeline} of the {@link Channel}.
*
* @param ch the {@link Channel} which was registered.
* @throws Exception is thrown if an error occurs. In that case it will be handled by
* {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close
* the {@link Channel}.
*/
protected abstract void initChannel(C ch) throws Exception;
```
- 子类继承 ChannelInitializer 抽象类后,实现该方法,自定义 Channel 的初始化逻辑。
- 第 6 至 10 行:调用 `#exceptionCaught(ChannelHandlerContext ctx, Throwable cause)` 方法,发生异常时,执行异常处理。代码如下:
```
/**
* Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this.
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (logger.isWarnEnabled()) {
logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause);
}
ctx.close();
}
```
- 打印**告警**日志。
- **关闭** Channel 通道。因为,初始化 Channel 通道发生异常,意味着很大可能,无法正常处理该 Channel 后续的读写事件。
- 😈 当然,`#exceptionCaught(...)` 方法,并非使用 `final` 修饰。所以也可以在子类覆写该方法。当然,笔者在实际使用并未这么做过。
- 第 11 至 14 行:最终,调用 `#remove(ChannelHandlerContext ctx)` 方法,从 pipeline 移除 ChannelInitializer。代码如下
```
private void remove(ChannelHandlerContext ctx) {
try {
// 从 pipeline 移除 ChannelInitializer
ChannelPipeline pipeline = ctx.pipeline();
if (pipeline.context(this) != null) {
pipeline.remove(this);
}
} finally {
initMap.remove(ctx); // 从 initMap 移除
}
}
```
- 从 pipeline 移除 ChannelInitializer 后,避免重新初始化的问题。
- 第 15 行:返回 `true` ,表示**有**执行初始化。
- 第 17 行:返回 `false` ,表示**未**执行初始化。
## 2.2 channelRegistered
在 Channel 注册到 EventLoop 上后,会触发 Channel Registered 事件。那么 `ChannelInitializer` 的 `#channelRegistered(ChannelHandlerContext ctx)` 方法,就会处理该事件。而 ChannelInitializer 对该事件的处理逻辑是,初始化 Channel 。代码如下:
```
@Override
@SuppressWarnings("unchecked")
public final void channelRegistered(ChannelHandlerContext ctx) throws Exception {
// Normally this method will never be called as handlerAdded(...) should call initChannel(...) and remove
// the handler.
// <1> 初始化 Channel
if (initChannel(ctx)) {
// we called initChannel(...) so we need to call now pipeline.fireChannelRegistered() to ensure we not
// miss an event.
// <2.1> 重新触发 Channel Registered 事件
ctx.pipeline().fireChannelRegistered();
} else {
// <2.2> 继续向下一个节点的 Channel Registered 事件
// Called initChannel(...) before which is the expected behavior, so just forward the event.
ctx.fireChannelRegistered();
}
}
```
- `<1>` 处,调用 `#initChannel(ChannelHandlerContext ctx)` 方法,初始化 Channel 。
- `<2.1>` 处,若有初始化,**重新触发** Channel Registered 事件。因为,很有可能添加了新的 ChannelHandler 到 pipeline 中。
- `<2.2>` 处,若无初始化,**继续向下一个节点**的 Channel Registered 事件。
## 2.3 handlerAdded
`ChannelInitializer#handlerAdded(ChannelHandlerContext ctx)` 方法,代码如下:
```
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
if (ctx.channel().isRegistered()) { // 已注册
// This should always be true with our current DefaultChannelPipeline implementation.
// The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering
// surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers
// will be added in the expected order.
initChannel(ctx);
}
}
```
- 诶?怎么这里又调用了 `#initChannel(ChannelHandlerContext ctx)` 方法,初始化 Channel 呢?实际上,绝绝绝大多数情况下,因为 Channel Registered 事件触发在 Added **之后**,如果说在 `#handlerAdded(ChannelHandlerContext ctx)` 方法中,初始化 Channel 完成,那么 ChannelInitializer 便会从 pipeline 中移除。也就说,不会执行 `#channelRegistered(ChannelHandlerContext ctx)` 方法。
- ↑↑↑ 上面这段话听起来非常绕噢。简单来说ChannelInitializer 调用 `#initChannel(ChannelHandlerContext ctx)` 方法,初始化 Channel 的调用来源,是来自 `#handlerAdded(...)` 方法,而不是 `#channelRegistered(...)` 方法。
- 还是不理解?胖友在
```
#handlerAdded(ChannelHandlerContext ctx)
```
方法上打上“
断点
”,并调试启动
```
io.netty.example.echo.EchoServer
```
,就能触发这种情况。原因是什么呢?如下图所示:
![register0](49-Netty 源码解析-ChannelHandler之 ChannelInitializer.assets/02.png)
register0
- 😈 红框部分,看到否?明白了哇。
至于说,什么时候使用 ChannelInitializer 调用 `#initChannel(ChannelHandlerContext ctx)` 方法,初始化 Channel 的调用来源,是来自 `#channelRegistered(...)` 方法,笔者暂未发现。如果有知道的胖友,麻烦深刻教育我下。
TODO 1020 ChannelInitializer 对 channelRegistered 的触发
# 666. 彩蛋
小水文一篇。同时也推荐阅读:
- Donald_Draper [《netty 通道初始化器ChannelInitializer》](http://donald-draper.iteye.com/blog/2389352)