【Netty】(5)源码 Bootstrap
一、概述
Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化.Bootstrap
: 用于客户端,只需要一个单独的Channel,来与服务端进行数据交互,对应server端的子Channel。作用职责
:EventLoop初始化,channel的注册过程 ,关于pipeline的初始化,handler的添加过程,客户端连接分析。
Netty客户端源码部分
<span style="color:#4b4b4b"><code> EventLoopGroup group = <span style="color:#f92672">new</span> NioEventLoopGroup();
<span style="color:#f92672">try</span> {
Bootstrap b = <span style="color:#f92672">new</span> Bootstrap();
b.group(group) <span style="color:#75715e">// 注册线程池</span>
.channel(NioSocketChannel.class) <span style="color:#75715e">// 使用NioSocketChannel来作为连接用的channel类</span>
.handler(<span style="color:#f92672">new</span> ChannelInitializer<SocketChannel>() { <span style="color:#75715e">// 绑定连接初始化器</span>
<span style="color:#75715e">@Override</span>
<span style="color:#f92672">protected</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">initChannel</span><span style="color:#f8f8f2">(SocketChannel ch)</span> <span style="color:#f92672">throws</span> Exception {
<span style="color:#75715e">//这里放入自定义助手类</span>
ch.pipeline().addLast(<span style="color:#f92672">new</span> EchoClientHandler());
}
});
ChannelFuture cf = b.connect(host, port).sync(); <span style="color:#75715e">// 异步连接服务器</span>
cf.channel().closeFuture().sync(); <span style="color:#75715e">// 异步等待关闭连接channel</span>
} <span style="color:#f92672">finally</span> {
group.shutdownGracefully().sync(); <span style="color:#75715e">// 释放线程池资源</span>
}
}</code></span>
从上面的客户端代码虽然简单, 但是却展示了 Netty 客户端初始化时所需的所有内容:
1. EventLoopGroup: 不论是服务器端还是客户端, 都必须指定 EventLoopGroup. 在这个例子中,
指定了 NioEventLoopGroup, 表示一个 NIO 的EventLoopGroup.
2. ChannelType: 指定 Channel 的类型. 因为是客户端, 因此使用了 NioSocketChannel.
3. Handler: 设置数据的处理器.
4. 这里的option,提供了一系列的TCP参数
下面我们深入代码, 看一下客户端通过 Bootstrap 启动后, 都做了哪些工作.
二、源码分析
1、group(group)
<span style="color:#4b4b4b"><code> <span style="color:#75715e">/**
* 直接调用父类AbstractBootstrap的方法
*/</span>
<span style="color:#f92672">public</span> B <span style="color:#a6e22e">group</span><span style="color:#f8f8f2">(EventLoopGroup group)</span> {
<span style="color:#f92672">if</span> (group == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"group"</span>);
}
<span style="color:#f92672">if</span> (<span style="color:#f92672">this</span>.group != <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> IllegalStateException(<span style="color:#e6db74">"group set already"</span>);
}
<span style="color:#f92672">this</span>.group = group;
<span style="color:#f92672">return</span> self();
}</code></span>
直接调用父类的方法 ,说明该EventLoopGroup,作为客户端 Connector 线程,负责注册监听连接操作位,用于判断异步连接结果。
2、channel(NioServerSocketChannel.class)
在 Netty 中, Channel是一个Socket的抽象, 它为用户提供了关于 Socket 状态(是否是连接还是断开) 以及对 Socket 的读写等操作. 每当 Netty 建立了一个连接后, 都会有一个对应的 Channel 实例。
2.1源码
<span style="color:#4b4b4b"><code><span style="color:#75715e">/**
* 同样也是直接调用父类AbstractBootstrap的方法
*/</span>
<span style="color:#f92672">public</span> B <span style="color:#a6e22e">channel</span><span style="color:#f8f8f2">(Class<? extends C> channelClass)</span> {
<span style="color:#f92672">if</span> (channelClass == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"channelClass"</span>);
}
<span style="color:#f92672">return</span> channelFactory(<span style="color:#f92672">new</span> ReflectiveChannelFactory<C>(channelClass));
}</code></span>
我们再来看下ReflectiveChannelFactory类
<span style="color:#4b4b4b"><code> <span style="color:#f92672">public</span> <span style="color:#f92672">class</span> <span style="color:#f8f8f2">ReflectiveChannelFactory</span><<span style="color:#f8f8f2">T</span> <span style="color:#f92672">extends</span> <span style="color:#f8f8f2">Channel</span>> <span style="color:#f92672">implements</span> <span style="color:#f8f8f2">ChannelFactory</span><<span style="color:#f8f8f2">T</span>> {
<span style="color:#f92672">private</span> <span style="color:#f92672">final</span> Class<? extends T> clazz;
<span style="color:#75715e">/**
* 通过构造函数 传入 clazz
*/</span>
<span style="color:#f92672">public</span> <span style="color:#a6e22e">ReflectiveChannelFactory</span><span style="color:#f8f8f2">(Class<? extends T> clazz)</span> {
<span style="color:#f92672">if</span> (clazz == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"clazz"</span>);
}
<span style="color:#f92672">this</span>.clazz = clazz;
}
<span style="color:#75715e">/**
* 只用这一个方法 通过传入不同的Channel.class 创建不同的Channel 对象。
* newChannel() 什么时候调用呢 仔细追源码 发现是在绑定 IP 和 端口的 doResolveAndConnect方法里会调用
*/</span>
<span style="color:#75715e">@Override</span>
<span style="color:#f92672">public</span> T <span style="color:#a6e22e">newChannel</span><span style="color:#f8f8f2">()</span> {
<span style="color:#f92672">try</span> {
<span style="color:#f92672">return</span> clazz.getConstructor().newInstance();
} <span style="color:#f92672">catch</span> (Throwable t) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> ChannelException(<span style="color:#e6db74">"Unable to create Channel from class "</span> + clazz, t);
}
}</code></span>
在看channelFactory(new ReflectiveChannelFactory(channelClass)) 方法
<span style="color:#4b4b4b"><code> <span style="color:#75715e">/**
* 创建好Channel后,返回对象Bootstrap本身
*/</span>
<span style="color:#75715e">@Deprecated</span>
<span style="color:#f92672">public</span> B <span style="color:#a6e22e">channelFactory</span><span style="color:#f8f8f2">(ChannelFactory<? extends C> channelFactory)</span> {
<span style="color:#f92672">if</span> (channelFactory == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"channelFactory"</span>);
}
<span style="color:#f92672">if</span> (<span style="color:#f92672">this</span>.channelFactory != <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> IllegalStateException(<span style="color:#e6db74">"channelFactory set already"</span>);
}
<span style="color:#f92672">this</span>.channelFactory = channelFactory;
<span style="color:#f92672">return</span> self();
}</code></span>
因此对于我们这个例子中的客户端的 Bootstrap 而言, 生成的的 Channel 实例就是 NioSocketChannel。
2.2 Channel 类型
除了 TCP 协议以外, Netty 还支持很多其他的连接协议, 并且每种协议还有 NIO(异步 IO) 和 OIO(Old-IO, 即传统的阻塞 IO) 版本的区别. 不同协议不同的阻塞类型的连接都有不同的 Channel 类型与之对应下面是一些常用的 Channel 类型:
- NioSocketChannel, 代表异步的客户端 TCP Socket 连接.
- NioServerSocketChannel, 异步的服务器端 TCP Socket 连接.
- NioDatagramChannel, 异步的 UDP 连接
- NioSctpChannel, 异步的客户端 Sctp 连接.
- NioSctpServerChannel, 异步的 Sctp 服务器端连接.
- OioSocketChannel, 同步的客户端 TCP Socket 连接.
- OioServerSocketChannel, 同步的服务器端 TCP Socket 连接.
- OioDatagramChannel, 同步的 UDP 连接
- OioSctpChannel, 同步的 Sctp 服务器端连接.
- OioSctpServerChannel, 同步的客户端 TCP Socket 连接.
3、handler(ChannelHandler handler)
Netty 的一个强大和灵活之处就是基于 Pipeline 的自定义 handler 机制
. 基于此, 我们可以像添加插件一样自由组合各种各样的 handler 来完成业务逻辑. 例如我们需要处理 HTTP 数据, 那么就可以在 pipeline 前添加一个 Http 的编解码的 Handler, 然后接着添加我们自己的业务逻辑的 handler, 这样网络上的数据流就向通过一个管道一样, 从不同的 handler 中流过并进行编解码, 最终在到达我们自定义的 handler 中。
<span style="color:#4b4b4b"><code><span style="color:#75715e">/**
* 同样也是 直接调用父类 AbstractBootstrap 的方法
*/</span>
<span style="color:#f92672">public</span> B <span style="color:#a6e22e">handler</span><span style="color:#f8f8f2">(ChannelHandler handler)</span> {
<span style="color:#f92672">if</span> (handler == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"handler"</span>);
}
<span style="color:#f92672">this</span>.handler = handler;
<span style="color:#f92672">return</span> self();
}</code></span>
不过我们看到代码 一般都是这样写的
<span style="color:#4b4b4b"><code>.handler(<span style="color:#f92672">new</span> ChannelInitializer<SocketChannel>() {
<span style="color:#75715e">@Override</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">initChannel</span><span style="color:#f8f8f2">(SocketChannel ch)</span> <span style="color:#f92672">throws</span> Exception {
ChannelPipeline p = ch.pipeline();
p.addLast(<span style="color:#f92672">new</span> EchoClientHandler());
}
})</code></span>
那是因为Bootstrap.handler 方法接收一个 ChannelHandler, 而我们传递的是一个 派生于 ChannelInitializer 的匿名类, 它正好也实现了 ChannelHandler 接口. 我们来看一下, ChannelInitializer 类部分代码:
<span style="color:#4b4b4b"><code> <span style="color:#75715e">/**
* ChannelInboundHandlerAdapter 父类的父类 最终会继承 ChannelHandler
* 那么ChannelInitializer 也就是 ChannelHandler的 子类
*/</span>
<span style="color:#f92672">public</span> <span style="color:#f92672">abstract</span> <span style="color:#f92672">class</span> <span style="color:#f8f8f2">ChannelInitializer</span><<span style="color:#f8f8f2">C</span> <span style="color:#f92672">extends</span> <span style="color:#f8f8f2">Channel</span>> <span style="color:#f92672">extends</span> <span style="color:#f8f8f2">ChannelInboundHandlerAdapter</span> {
<span style="color:#f92672">private</span> <span style="color:#f92672">static</span> <span style="color:#f92672">final</span> InternalLogger logger
=InternalLoggerFactory.getInstance(ChannelInitializer.class);
<span style="color:#75715e">/**
* 这里只有这一个抽象类 所以我们只需重写这一个方法就可以了
*/</span>
<span style="color:#f92672">protected</span> <span style="color:#f92672">abstract</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">initChannel</span><span style="color:#f8f8f2">(C ch)</span> <span style="color:#f92672">throws</span> Exception;
<span style="color:#75715e">@Override</span>
<span style="color:#75715e">@SuppressWarnings</span>(<span style="color:#e6db74">"unchecked"</span>)
<span style="color:#f92672">public</span> <span style="color:#f92672">final</span> <span style="color:#f92672">void</span> <span style="color:#a6e22e">channelRegistered</span><span style="color:#f8f8f2">(ChannelHandlerContext ctx)</span> <span style="color:#f92672">throws</span> Exception {
initChannel((C) ctx.channel());
ctx.pipeline().remove(<span style="color:#f92672">this</span>);
ctx.fireChannelRegistered();
}
}</code></span>
ChannelInitializer 是一个抽象类, 它有一个抽象的方法 initChannel
, 我们正是实现了这个方法, 并添加的自定义的 handler 的. 那么 initChannel 是哪里被调用的呢?
答案是 ChannelInitializer.channelRegistered 方法中。
我们来关注一下 channelRegistered 方法. 从上面的源码中, 我们可以看到, 在 channelRegistered 方法中, 会调用 initChannel 方法, 将自定义的 handler 添加到 ChannelPipeline 中, 然后调用 ctx.pipeline().remove(this) 将自己从 ChannelPipeline 中删除. 上面的分析过程, 可以用如下图片展示:
一开始, ChannelPipeline 中只有三个 handler, head, tail 和我们添加的 ChannelInitializer.
接着 initChannel 方法调用后, 添加了自定义的 handler
最后将 ChannelInitializer 删除
4、ChannelPipeline对象
<span style="color:#4b4b4b"><code> <span style="color:#75715e">/**
* 我们在initChannel抽象方法的实现方法中 通过 SocketChannel获得 ChannelPipeline对象
*/</span>
ChannelPipeline p = ch.pipeline();
p.addLast(newEchoClientHandler());</code></span>
在实例化一个 Channel 时, 会伴随着一个 ChannelPipeline 的实例化
, 并且此 Channel 会与这个 ChannelPipeline 相互关联, 这一点可以通过NioSocketChannel 的父类 AbstractChannel 的构造器:
<span style="color:#4b4b4b"><code><span style="color:#f92672">protected</span> <span style="color:#a6e22e">AbstractChannel</span><span style="color:#f8f8f2">(Channel parent)</span> {
<span style="color:#f92672">this</span>.parent = parent;
unsafe = newUnsafe();
<span style="color:#75715e">//这个可以看出</span>
pipeline = <span style="color:#f92672">new</span> DefaultChannelPipeline(<span style="color:#f92672">this</span>);
}</code></span>
当实例化一个 Channel(这里以 EchoClient 为例, 那么 Channel 就是 NioSocketChannel), 其 pipeline 字段就是我们新创建的 DefaultChannelPipeline 对象, 那么我们就来看一下 DefaultChannelPipeline 的构造方法。
<span style="color:#4b4b4b"><code><span style="color:#f92672">public</span> <span style="color:#a6e22e">DefaultChannelPipeline</span><span style="color:#f8f8f2">(AbstractChannel channel)</span> {
<span style="color:#f92672">if</span> (channel == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"channel"</span>);
}
<span style="color:#f92672">this</span>.channel = channel;
tail = <span style="color:#f92672">new</span> TailContext(<span style="color:#f92672">this</span>);
head = <span style="color:#f92672">new</span> HeadContext(<span style="color:#f92672">this</span>);
head.next = tail;
tail.prev = head;
}</code></span>
我们调用 DefaultChannelPipeline 的构造器, 传入了一个 channel, 而这个 channel 其实就是我们实例化的 NioSocketChannel, DefaultChannelPipeline 会将这个 NioSocketChannel 对象保存在channel 字段中。DefaultChannelPipeline 中, 还有两个特殊的字段, 即head
和tail
, 而这两个字段是一个双向链表的头和尾
. 其实在 DefaultChannelPipeline 中, 维护了一个以 AbstractChannelHandlerContext 为节点的双向链表, 这个链表是 Netty 实现 Pipeline 机制的关键。
5、.connect(host, port)
经过上面的各种分析后, 我们大致了解了 Netty 初始化时, 所做的工作, 接下来 分析一下客户端是如何发起 TCP 连接的。
<span style="color:#4b4b4b"><code> <span style="color:#75715e">/**
* 1、 这里 终于是Bootstrap 自己的方法了。 传入IP 地址 和 端口号
*/</span>
<span style="color:#f92672">public</span> ChannelFuture <span style="color:#a6e22e">connect</span><span style="color:#f8f8f2">(String inetHost, <span style="color:#f92672">int</span> inetPort)</span> {
<span style="color:#75715e">//通过InetSocketAddress 构造函数 绑定 IP地址+端口号</span>
<span style="color:#f92672">return</span> connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
<span style="color:#75715e">/**
* 2、上面调用该方法 ,该方法在调用 doResolveAndConnect方法
*/</span>
<span style="color:#f92672">public</span> ChannelFuture <span style="color:#a6e22e">connect</span><span style="color:#f8f8f2">(SocketAddress remoteAddress)</span> {
<span style="color:#f92672">if</span> (remoteAddress == <span style="color:#f92672">null</span>) {
<span style="color:#f92672">throw</span> <span style="color:#f92672">new</span> NullPointerException(<span style="color:#e6db74">"remoteAddress"</span>);
}
validate();
<span style="color:#f92672">return</span> doResolveAndConnect(remoteAddress, config.localAddress());
}
<span style="color:#75715e">/**
* 3、这步 实例化 Channer
*/</span>
<span style="color:#f92672">private</span> ChannelFuture <span style="color:#a6e22e">doResolveAndConnect</span><span style="color:#f8f8f2">(<span style="color:#f92672">final</span> SocketAddress remoteAddress, <span style="color:#f92672">final</span> SocketAddress localAddress)</span> {
<span style="color:#75715e">//注意 这里 initAndRegister()方法就是实例化 Channer 的方法 上面说过 真正获取Channer 对象 是在这步获取的</span>
<span style="color:#f92672">final</span> ChannelFuture regFuture = initAndRegister();
<span style="color:#f92672">final</span> Channel channel = regFuture.channel();
<span style="color:#75715e">// 这里省略的 很大一部分逻辑判断的代码</span>
<span style="color:#f92672">return</span> doResolveAndConnect0(channel, remoteAddress, localAddress, channel.newPromise());
}
<span style="color:#75715e">/**
* 3.1 这里 就开始 调 newChannel() 方法 也就创建了 Channel 对象
*/</span>
<span style="color:#f92672">final</span> ChannelFuture <span style="color:#a6e22e">initAndRegister</span><span style="color:#f8f8f2">()</span> {
Channel channel = <span style="color:#f92672">null</span>;
channel = channelFactory.newChannel();
<span style="color:#f92672">return</span> regFuture;
}
<span style="color:#75715e">/**
* 4、在看doResolveAndConnect0方法
* 这一步还是对一些 参数数据 进行校验 省略了校验代码
*/</span>
<span style="color:#f92672">private</span> ChannelFuture <span style="color:#a6e22e">doResolveAndConnect0</span><span style="color:#f8f8f2">(<span style="color:#f92672">final</span> Channel channel, SocketAddress remoteAddress,
<span style="color:#f92672">final</span> SocketAddress localAddress, <span style="color:#f92672">final</span> ChannelPromise promise)</span> {
<span style="color:#75715e">// 获取 当前 EventLoop线程</span>
<span style="color:#f92672">final</span> EventLoop eventLoop = channel.eventLoop();
<span style="color:#f92672">final</span> AddressResolver<SocketAddress> resolver = <span style="color:#f92672">this</span>.resolver.getResolver(eventLoop);
<span style="color:#f92672">final</span> Future<SocketAddress> resolveFuture = resolver.resolve(remoteAddress);
<span style="color:#75715e">//这一步 才是 连接的关键</span>
doConnect(resolveFuture.getNow(), localAddress, promise);
<span style="color:#f92672">return</span> promise;
}</code></span>
接下来看重要的方法,在 connect 中, 会进行一些参数检查后, 最终调用的是 doConnect 方法,有关doConnect之后接下来源码,等自己对Netty了解更细致之后 ,再来写吧。
这里推荐一个博主,有关Netty源码分析的蛮好的:源码之下无秘密 ── 做最好的 Netty 源码分析教程