Bootstrap

Netty4学习笔记-- Bootstrap

Netty4的代码比我想象的要复杂的多,不过Netty4很好的将这种复杂性隐藏了起来,暴露出来的,是一个相对容易使用的接口。Bootstrap就是Netty试图隐藏这种复杂性的一个例子。

bootstrap包


bootstrap包是Netty4代码里最简单的一个包,总共只有4个类:

Bootstrap继承结构

AbstractBootstrap是抽象类,有两个具体的实现,Bootstrap和ServerBootstrap:

Bootstrap例子

Netty4自带的例子里有一个EchoClient,看看它是如何使用Bootstrap启动一个客户端程序的:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class EchoClient {  
  2.   
  3.     private final String host;  
  4.     private final int port;  
  5.     private final int firstMessageSize;  
  6.   
  7.     public EchoClient(String host, int port, int firstMessageSize) {  
  8.         this.host = host;  
  9.         this.port = port;  
  10.         this.firstMessageSize = firstMessageSize;  
  11.     }  
  12.   
  13.     public void run() throws Exception {  
  14.         // Configure the client.  
  15.         EventLoopGroup group = new NioEventLoopGroup();  
  16.         try {  
  17.             Bootstrap b = new Bootstrap();  
  18.             b.group(group)  
  19.              .channel(NioSocketChannel.class)  
  20.              .option(ChannelOption.TCP_NODELAY, true)  
  21.              .handler(new ChannelInitializer<SocketChannel>() {  
  22.                  @Override  
  23.                  public void initChannel(SocketChannel ch) throws Exception {  
  24.                      ch.pipeline().addLast(  
  25.                              //new LoggingHandler(LogLevel.INFO),  
  26.                              new EchoClientHandler(firstMessageSize));  
  27.                  }  
  28.              });  
  29.   
  30.             // Start the client.  
  31.             ChannelFuture f = b.connect(host, port).sync();  
  32.   
  33.             // Wait until the connection is closed.  
  34.             f.channel().closeFuture().sync();  
  35.         } finally {  
  36.             // Shut down the event loop to terminate all threads.  
  37.             group.shutdownGracefully();  
  38.         }  
  39.     }  
  40.   
  41.     public static void main(String[] args) throws Exception {  
  42.     // ...  
  43.     }  
  44. }  

Builder模式?

看上面的例子,Bootstrap的使用很像Builder模式,Bootstrap就是Builder,EventLoopGroup、Channel和Handler等是各种Part。稍有不同的是,准备好各种Part后,并不是直接build出一个Product来,而是直接通过connect()方法使用这个Product。


AbstractBootstrap

为了弄清楚Bootstrap如何工作,我们先从AbstractBootstrap入手:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {  
  2.   
  3.     private volatile EventLoopGroup group;  
  4.     private volatile ChannelFactory<? extends C> channelFactory;  
  5.     private volatile SocketAddress localAddress;  
  6.     private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();  
  7.     private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();  
  8.     private volatile ChannelHandler handler;  
  9.     // ...  
  10. }  

可以看到,AbstractBootstrap这个抽象Builder一共需要有6个Part,如下图所示:


设置各个Part

AbstractBootstrap有一组方法用来设置各个Part,例如下面这些:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public B group(EventLoopGroup group)  
  2. public B channel(Class<? extends C> channelClass)  
  3. public B channelFactory(ChannelFactory<? extends C> channelFactory)  
  4. public B localAddress(SocketAddress localAddress)  
  5. public <T> B option(ChannelOption<T> option, T value)  
  6. public <T> B attr(AttributeKey<T> key, T value)  
  7. public B handler(ChannelHandler handler)  

还有一组对应方法获得各个Part,如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. final SocketAddress localAddress()  
  2. final ChannelFactory<? extends C> channelFactory()  
  3. final ChannelHandler handler()  
  4. public final EventLoopGroup group()  
  5. final Map<ChannelOption<?>, Object> options()  
  6. final Map<AttributeKey<?>, Object> attrs()  

ChannelFactory

AbstractBootstrap通过ChannelFactory创建Channel实例,channel(channelClass)方法看起来好像是设置了一个Channel,但实际上只是设置了默认的ChannelFactory实现:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public B channel(Class<? extends C> channelClass) {  
  2.     if (channelClass == null) {  
  3.         throw new NullPointerException("channelClass");  
  4.     }  
  5.     return channelFactory(new BootstrapChannelFactory<C>(channelClass));  
  6. }  

默认的ChannelFactory实现使用反射创建Channel实例:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> {  
  2.     private final Class<? extends T> clazz;  
  3.   
  4.     BootstrapChannelFactory(Class<? extends T> clazz) {  
  5.         this.clazz = clazz;  
  6.     }  
  7.   
  8.     @Override  
  9.     public T newChannel() {  
  10.         try {  
  11.             return clazz.newInstance();  
  12.         } catch (Throwable t) {  
  13.             throw new ChannelException("Unable to create Channel from class " + clazz, t);  
  14.         }  
  15.     }  
  16. }  

Bootstrap.connect()

再来看Bootstrap类的connect()方法:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {  
  2.     if (remoteAddress == null) {  
  3.         throw new NullPointerException("remoteAddress");  
  4.     }  
  5.     validate();  
  6.     return doConnect(remoteAddress, localAddress);  
  7. }  


connect()方法调用validate()方法看各个Part是否准备就绪,然后调用doConnect()方法:


[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private ChannelFuture doConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {  
  2.     final ChannelFuture regFuture = initAndRegister();  
  3.     final Channel channel = regFuture.channel();  
  4.     if (regFuture.cause() != null) {  
  5.         return regFuture;  
  6.     }  
  7.   
  8.     final ChannelPromise promise = channel.newPromise();  
  9.     if (regFuture.isDone()) {  
  10.         doConnect0(regFuture, channel, remoteAddress, localAddress, promise);  
  11.     } else {  
  12.         regFuture.addListener(new ChannelFutureListener() {  
  13.             @Override  
  14.             public void operationComplete(ChannelFuture future) throws Exception {  
  15.                 doConnect0(regFuture, channel, remoteAddress, localAddress, promise);  
  16.             }  
  17.         });  
  18.     }  
  19.   
  20.     return promise;  
  21. }  

doConnect()方法首先调用了initAndRegister()方法,然后又调用了doConnect0()方法,方法调用示意图如下:


AbstractBootstrap.initAndRegister()

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. final ChannelFuture initAndRegister() {  
  2.     final Channel channel = channelFactory().newChannel();  
  3.     try {  
  4.         init(channel);  
  5.     } catch (Throwable t) {  
  6.         channel.unsafe().closeForcibly();  
  7.         return channel.newFailedFuture(t);  
  8.     }  
  9.   
  10.     ChannelPromise regPromise = channel.newPromise();  
  11.     group().register(channel, regPromise);  
  12.     // ...  
  13. }  
initAndRegister()方法用ChannelFactory创建了一个Channel的实例,然后调用init()方法初始化Channel,最后将Channel注册到EventLoopGroup上:

而Channel在实例化的时候已经自动关联了Pipeline,这点从AbstractChannel的构造函数可以看出:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. protected AbstractChannel(Channel parent) {  
  2.     this.parent = parent;  
  3.     unsafe = newUnsafe();  
  4.     pipeline = new DefaultChannelPipeline(this);  
  5. }  
 

Bootstrap.init()方法

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void init(Channel channel) throws Exception {  
  2.     ChannelPipeline p = channel.pipeline();  
  3.     p.addLast(handler());  
  4.     // ...  
  5. }  
Bootstrap.init()方法把Handler添加到了Pipeline的末尾,到这里,Channel就准备就绪了:

继续Bootstrap.doConnect()

initAndRegister()方法结束之后,doConnect()方法紧接着调用了doConnect0()方法,doConnect0()方法继而调用了Channel.connect()方法,这样Channel就接通服务器,可以收发消息了!


这篇文章接着上一篇,分析一下Netty4的ServerBootstrp是如何工作的。

EchoServer

先看看Netty自带的EchoServer例子:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.  * Echoes back any received data from a client. 
  3.  */  
  4. public class EchoServer {  
  5.   
  6.     private final int port;  
  7.   
  8.     public EchoServer(int port) {  
  9.         this.port = port;  
  10.     }  
  11.   
  12.     public void run() throws Exception {  
  13.         // Configure the server.  
  14.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  15.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  16.         try {  
  17.             ServerBootstrap b = new ServerBootstrap();  
  18.             b.group(bossGroup, workerGroup)  
  19.              .channel(NioServerSocketChannel.class)  
  20.              .option(ChannelOption.SO_BACKLOG, 100)  
  21.              .handler(new LoggingHandler(LogLevel.INFO))  
  22.              .childHandler(new ChannelInitializer<SocketChannel>() {  
  23.                  @Override  
  24.                  public void initChannel(SocketChannel ch) throws Exception {  
  25.                      ch.pipeline().addLast(  
  26.                              //new LoggingHandler(LogLevel.INFO),  
  27.                              new EchoServerHandler());  
  28.                  }  
  29.              });  
  30.   
  31.             // Start the server.  
  32.             ChannelFuture f = b.bind(port).sync();  
  33.   
  34.             // Wait until the server socket is closed.  
  35.             f.channel().closeFuture().sync();  
  36.         } finally {  
  37.             // Shut down all event loops to terminate all threads.  
  38.             bossGroup.shutdownGracefully();  
  39.             workerGroup.shutdownGracefully();  
  40.         }  
  41.     }  
  42.   
  43.     public static void main(String[] args) throws Exception {  
  44.         int port;  
  45.         if (args.length > 0) {  
  46.             port = Integer.parseInt(args[0]);  
  47.         } else {  
  48.             port = 8080;  
  49.         }  
  50.         new EchoServer(port).run();  
  51.     }  
  52. }  
可以看出,用法和Bootstrap差不多。

作为Builder的ServerBootstrap

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {  
  2.   
  3.     private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();  
  4.     private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();  
  5.     private volatile EventLoopGroup childGroup;  
  6.     private volatile ChannelHandler childHandler;  
  7.     // ...  
  8. }  
看代码可以知道,ServerBootstrap比它的超类多了四个Part,如下图所示:

从bind()方法入手

bind()方法实际上是在AbstractBootstrap里定义的,bind()先调用validate()方法检查各个Part是否准备就绪,然后把工作交给了doBind()方法。doBind()方法首先调用initAndRegister()方法,然后把工作交给doBind0()。initAndRegister()会调用模板方法init()来初始化Channel,initAndRegister()方法的细节上篇文章分析过了,这里不再复述。下面是整个方法调用过程的示意图:


init()方法

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. void init(Channel channel) throws Exception {  
  3.     final Map<ChannelOption<?>, Object> options = options();  
  4.     synchronized (options) {  
  5.         channel.config().setOptions(options);  
  6.     }  
  7.   
  8.     final Map<AttributeKey<?>, Object> attrs = attrs();  
  9.     synchronized (attrs) {  
  10.         for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {  
  11.             @SuppressWarnings("unchecked")  
  12.             AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();  
  13.             channel.attr(key).set(e.getValue());  
  14.         }  
  15.     }  
  16.   
  17.     ChannelPipeline p = channel.pipeline();  
  18.     if (handler() != null) {  
  19.         p.addLast(handler());  
  20.     }  
  21.   
  22.     final EventLoopGroup currentChildGroup = childGroup;  
  23.     final ChannelHandler currentChildHandler = childHandler;  
  24.     final Entry<ChannelOption<?>, Object>[] currentChildOptions;  
  25.     final Entry<AttributeKey<?>, Object>[] currentChildAttrs;  
  26.     synchronized (childOptions) {  
  27.         currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));  
  28.     }  
  29.     synchronized (childAttrs) {  
  30.         currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));  
  31.     }  
  32.   
  33.     p.addLast(new ChannelInitializer<Channel>() {  
  34.         @Override  
  35.         public void initChannel(Channel ch) throws Exception {  
  36.             ch.pipeline().addLast(new ServerBootstrapAcceptor(  
  37.                     currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));  
  38.         }  
  39.     });  
  40. }  

ServerBootstrap类的init()方法虽然很长,但是却不难理解。首先是给Channel设置options和attrs,然后把User提供的针对NioServerSocketChannel的Handler添加到Pipeline的末尾。接下来复制childOptions和childAttrs,最后实例化一个ChannelInitializer,添加到Pipeline的末尾。init()方法执行完毕之后,AbstractBootstrap的initAndRegister()方法会将NioServerSocketChannel注册到group里。到此为止,NioServerSocketChannel的状态如下图所示:

ChannelInitializer在channelRegistered事件触发后会调用initChannel()方法,随后把自己从Pipeline里删除:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. @Sharable  
  2. public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter {  
  3.   
  4.     private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class);  
  5.   
  6.     /** 
  7.      * This method will be called once the {@link Channel} was registered. After the method returns this instance 
  8.      * will be removed from the {@link ChannelPipeline} of the {@link Channel}. 
  9.      * 
  10.      * @param ch            the {@link Channel} which was registered. 
  11.      * @throws Exception    is thrown if an error occurs. In that case the {@link Channel} will be closed. 
  12.      */  
  13.     protected abstract void initChannel(C ch) throws Exception;  
  14.   
  15.     @SuppressWarnings("unchecked")  
  16.     @Override  
  17.     public final void channelRegistered(ChannelHandlerContext ctx)  
  18.             throws Exception {  
  19.         boolean removed = false;  
  20.         boolean success = false;  
  21.         try {  
  22.             initChannel((C) ctx.channel());  
  23.             ctx.pipeline().remove(this);  
  24.             removed = true;  
  25.             ctx.fireChannelRegistered();  
  26.             success = true;  
  27.         } catch (Throwable t) {  
  28.             logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), t);  
  29.         } finally {  
  30.             if (!removed) {  
  31.                 ctx.pipeline().remove(this);  
  32.             }  
  33.             if (!success) {  
  34.                 ctx.close();  
  35.             }  
  36.         }  
  37.     }  
  38. }  

所以注册之后的NioServerSocketChannel状态如下图所示:

ServerBootstrapAcceptor

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private static class ServerBootstrapAcceptor extends ChannelInboundHandlerAdapter {  
  2.   
  3.     private final EventLoopGroup childGroup;  
  4.     private final ChannelHandler childHandler;  
  5.     private final Entry<ChannelOption<?>, Object>[] childOptions;  
  6.     private final Entry<AttributeKey<?>, Object>[] childAttrs;  
  7.   
  8.     @SuppressWarnings("unchecked")  
  9.     ServerBootstrapAcceptor(  
  10.             EventLoopGroup childGroup, ChannelHandler childHandler,  
  11.             Entry<ChannelOption<?>, Object>[] childOptions, Entry<AttributeKey<?>, Object>[] childAttrs) {  
  12.         this.childGroup = childGroup;  
  13.         this.childHandler = childHandler;  
  14.         this.childOptions = childOptions;  
  15.         this.childAttrs = childAttrs;  
  16.     }  
  17.   
  18.     @Override  
  19.     @SuppressWarnings("unchecked")  
  20.     public void channelRead(ChannelHandlerContext ctx, Object msg) {  
  21.         Channel child = (Channel) msg;  
  22.   
  23.         child.pipeline().addLast(childHandler);  
  24.   
  25.         for (Entry<ChannelOption<?>, Object> e: childOptions) {  
  26.             try {  
  27.                 if (!child.config().setOption((ChannelOption<Object>) e.getKey(), e.getValue())) {  
  28.                     logger.warn("Unknown channel option: " + e);  
  29.                 }  
  30.             } catch (Throwable t) {  
  31.                 logger.warn("Failed to set a channel option: " + child, t);  
  32.             }  
  33.         }  
  34.   
  35.         for (Entry<AttributeKey<?>, Object> e: childAttrs) {  
  36.             child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());  
  37.         }  
  38.   
  39.         try {  
  40.             childGroup.register(child);  
  41.         } catch (Throwable t) {  
  42.             child.unsafe().closeForcibly();  
  43.             logger.warn("Failed to register an accepted channel: " + child, t);  
  44.         }  
  45.     }  
  46.     // ...  
  47. }  

ServerBootstrapAcceptor在channelRead事件触发的时候(也就有客户端连接的时候),把childHandler加到childChannel Pipeline的末尾,设置childHandler的options和attrs,最后把childHandler注册进childGroup,如下图所示:











;