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