netty4.0.x源码分析—bootstrap

转载 2015年07月07日 15:03:40

Bootstrap的意思就是引导,辅助的意思,在编写服务端或客户端程序时,我们都需要先new一个bootstrap,然后基于这个bootstrap调用函数,添加eventloop和handler,可见对bootstrap进行分析还是有必要的。

1、bootstrap结构图

bootstrap的结构比较简单,涉及的类和接口很少,如下图所示,其中Bootstrap则是客户端程序用的引导类,ServerBootstrap是服务端程序用的引导类。


2、serverbootstrap分析

这部分,专门对serverbootstrap进行分析,bootstrap过程大同小异就不作详细的分析了。下面是我们编写服务端代码的一般化过程,整个分析过程将基于下面这段代码中用到的函数进行。

  1. // Configure the bootstrap.  
  2.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  3.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  4.         try {  
  5.             ServerBootstrap b = new ServerBootstrap();  
  6.             b.group(bossGroup, workerGroup)  
  7.              .channel(NioServerSocketChannel.class)  
  8.              .childHandler(new HexDumpProxyInitializer(remoteHost, remotePort))  
  9.              .childOption(ChannelOption.AUTO_READ, false)  
  10.              .bind(localPort).sync().channel().closeFuture().sync();  
  11.         } finally {  
  12.             bossGroup.shutdownGracefully();  
  13.             workerGroup.shutdownGracefully();  
  14.         }  

先看关键代码(注意这里面的部分函数是在AbstractBootstrap中定义的)

  1. private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();  
  2. private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();  
  3. private volatile EventLoopGroup childGroup;  
  4. private volatile ChannelHandler childHandler;  
  5.   
  6. /** 
  7.  * Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These 
  8.  * {@link EventLoopGroup}'s are used to handle all the events and IO for {@link SocketChannel} and 
  9.  * {@link Channel}'s. 
  10.  */  
  11. public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {  
  12.     super.group(parentGroup);  
  13.     if (childGroup == null) {  
  14.         throw new NullPointerException("childGroup");  
  15.     }  
  16.     if (this.childGroup != null) {  
  17.         throw new IllegalStateException("childGroup set already");  
  18.     }  
  19.     this.childGroup = childGroup;  
  20.     return this;  
  21. }  
属性值ChildGroup,ChildHandler,是用来处理accpt的Channel的。group函数其实就是将parentGroup和ChildGroup进行赋值,其中parentGroup用于处理accept事件,ChildGroup用于处理accpt的Channel的IO事件。
  1. //channel函数的实现定义在抽象父类中,其实就是通过newInstance函数生成一个具体的channel对象。  
  2. pre name="code" class="java">    /** 
  3.  * The {@link Class} which is used to create {@link Channel} instances from. 
  4.  * You either use this or {@link #channelFactory(ChannelFactory)} if your 
  5.  * {@link Channel} implementation has no no-args constructor. 
  6.  */  
  7. public B channel(Class<? extends C> channelClass) {  
  8.     if (channelClass == null) {  
  9.         throw new NullPointerException("channelClass");  
  10.     }  
  11.     return channelFactory(new BootstrapChannelFactory<C>(channelClass));  
  12. }  
  13.   
  14. /** 
  15.  * {@link ChannelFactory} which is used to create {@link Channel} instances from 
  16.  * when calling {@link #bind()}. This method is usually only used if {@link #channel(Class)} 
  17.  * is not working for you because of some more complex needs. If your {@link Channel} implementation 
  18.  * has a no-args constructor, its highly recommend to just use {@link #channel(Class)} for 
  19.  * simplify your code. 
  20.  */  
  21. @SuppressWarnings("unchecked")  
  22. public B channelFactory(ChannelFactory<? extends C> channelFactory) {  
  23.     if (channelFactory == null) {  
  24.         throw new NullPointerException("channelFactory");  
  25.     }  
  26.     if (this.channelFactory != null) {  
  27.         throw new IllegalStateException("channelFactory set already");  
  28.     }  
  29.   
  30.     this.channelFactory = channelFactory;  
  31.     return (B) this;  
  32. }<pre name="code" class="java">    private static final class BootstrapChannelFactory<T extends Channel> implements ChannelFactory<T> {  
  33.     private final Class<? extends T> clazz;  
  34.   
  35.     BootstrapChannelFactory(Class<? extends T> clazz) {  
  36.         this.clazz = clazz;  
  37.     }  
  38.   
  39.     @Override  
  40.     public T newChannel() {  
  41.         try {  
  42.             return clazz.newInstance();  
  43.         } catch (Throwable t) {  
  44.             throw new ChannelException("Unable to create Channel from class " + clazz, t);  
  45.         }  
  46.     }  
  47.   
  48.     @Override  
  49.     public String toString() {  
  50.         return clazz.getSimpleName() + ".class";  
  51.     }  
  52. }  
Channel函数比较简单,其实就是通过newInstance函数,生成一个具体的Channel对象,例如服务端的NioServerSocketChannel。
  1. /** 
  2.  * Set the {@link ChannelHandler} which is used to serve the request for the {@link Channel}'s. 
  3.  */  
  4. public ServerBootstrap childHandler(ChannelHandler childHandler) {  
  5.     if (childHandler == null) {  
  6.         throw new NullPointerException("childHandler");  
  7.     }  
  8.     this.childHandler = childHandler;  
  9.     return this;  
  10. }  
上面的函数即给serverbootstrap的childHandler赋值。
  1. /** 
  2.  * Allow to specify a {@link ChannelOption} which is used for the {@link Channel} instances once they get created 
  3.  * (after the acceptor accepted the {@link Channel}). Use a value of {@code null} to remove a previous set 
  4.  * {@link ChannelOption}. 
  5.  */  
  6. public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {  
  7.     if (childOption == null) {  
  8.         throw new NullPointerException("childOption");  
  9.     }  
  10.     if (value == null) {  
  11.         synchronized (childOptions) {  
  12.             childOptions.remove(childOption);  
  13.         }  
  14.     } else {  
  15.         synchronized (childOptions) {  
  16.             childOptions.put(childOption, value);  
  17.         }  
  18.     }  
  19.     return this;  
  20. }  
上面的函数是指定accpt的channel的属性,channel有很多属性,比如SO_TIMEOUT时间,Buf长度等等。
  1. /** 
  2.  * Create a new {@link Channel} and bind it. 
  3.  */  
  4. public ChannelFuture bind() {  
  5.     validate();  
  6.     SocketAddress localAddress = this.localAddress;  
  7.     if (localAddress == null) {  
  8.         throw new IllegalStateException("localAddress not set");  
  9.     }  
  10.     return doBind(localAddress);  
  11. }  
  12.   
  13.  /** 
  14.  * Create a new {@link Channel} and bind it. 
  15.  */  
  16. public ChannelFuture bind(int inetPort) {  
  17.     return bind(new InetSocketAddress(inetPort));  
  18. }  
  19.   
  20. /** 
  21.  * Create a new {@link Channel} and bind it. 
  22.  */  
  23. public ChannelFuture bind(String inetHost, int inetPort) {  
  24.     return bind(new InetSocketAddress(inetHost, inetPort));  
  25. }  
  26.   
  27. pre name="code" class="java">    /** 
  28.  * Create a new {@link Channel} and bind it. 
  29.  */  
  30. public ChannelFuture bind(SocketAddress localAddress) {  
  31.     validate();  
  32.     if (localAddress == null) {  
  33.         throw new NullPointerException("localAddress");  
  34.     }  
  35.     return doBind(localAddress);  
  36. }  
  37.   
  38. private ChannelFuture doBind(final SocketAddress localAddress) {  
  39.     final ChannelFuture regPromise = initAndRegister();  
  40.     final Channel channel = regPromise.channel();  
  41.     final ChannelPromise promise = channel.newPromise();  
  42.     if (regPromise.isDone()) {  
  43.         doBind0(regPromise, channel, localAddress, promise);  
  44.     } else {  
  45.         regPromise.addListener(new ChannelFutureListener() {  
  46.             @Override  
  47.             public void operationComplete(ChannelFuture future) throws Exception {  
  48.                 doBind0(future, channel, localAddress, promise);  
  49.             }  
  50.         });  
  51.     }  
  52.   
  53.     return promise;  
  54. }<pre name="code" class="java">    private static void doBind0(  
  55.         final ChannelFuture regFuture, final Channel channel,  
  56.         final SocketAddress localAddress, final ChannelPromise promise) {  
  57.   
  58.     // This method is invoked before channelRegistered() is triggered.  Give user handlers a chance to set up  
  59.     // the pipeline in its channelRegistered() implementation.  
  60.   
  61.     channel.eventLoop().execute(new Runnable() {  
  62.         @Override  
  63.         public void run() {  
  64.             if (regFuture.isSuccess()) {  
  65.                 channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);  
  66.             } else {  
  67.                 promise.setFailure(regFuture.cause());  
  68.             }  
  69.         }  
  70.     });  
  71. }  
Bind函数层层调用过来之后,最后就调用Channel的bind函数了,下面再看channel的bind函数是如何处理的。定义在AbstractChannel中:
  1. @Override  
  2. public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {  
  3.     return pipeline.bind(localAddress, promise);  
  4. }  
channel的bind函数,最终就是调用pipeline的bind,而pipeline的bind实际上就是调用contexthandler的bind,之个之前分析write和flush的时候说过了。所以这里直接看contexthandler的bind函数。下面是定义:
  1. @Override  
  2. public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {  
  3.     if (localAddress == null) {  
  4.         throw new NullPointerException("localAddress");  
  5.     }  
  6.     validatePromise(promise, false);  
  7.   
  8.     final DefaultChannelHandlerContext next = findContextOutbound();  
  9.     EventExecutor executor = next.executor();  
  10.     if (executor.inEventLoop()) {  
  11.         next.invokeBind(localAddress, promise);  
  12.     } else {  
  13.         executor.execute(new Runnable() {  
  14.             @Override  
  15.             public void run() {  
  16.                 next.invokeBind(localAddress, promise);  
  17.             }  
  18.         });  
  19.     }  
  20.   
  21.     return promise;  
  22. }<pre name="code" class="java">    private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {  
  23.     try {  
  24.         ((ChannelOutboundHandler) handler).bind(this, localAddress, promise);  
  25.     } catch (Throwable t) {  
  26.         notifyOutboundHandlerException(t, promise);  
  27.     }  
  28. }  
最终调用Handler的bind函数,还记得之前说的outbound类型的事件吗,这类事件提供了默认的实现方法,HeadHandler的bind函数,下面是它的定义:
  1. @Override  
  2. public void bind(  
  3.         ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)  
  4.         throws Exception {  
  5.     unsafe.bind(localAddress, promise);  
  6. }  
我们又看到了unsafe这个苦力了,最终的操作还是得由它来完成啊,赶紧去看看这个bind函数吧,
  1. @Override  
  2. public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {  
  3.     if (!ensureOpen(promise)) {  
  4.         return;  
  5.     }  
  6.   
  7.     // See: https://github.com/netty/netty/issues/576  
  8.     if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&  
  9.         Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&  
  10.         localAddress instanceof InetSocketAddress &&  
  11.         !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {  
  12.         // Warn a user about the fact that a non-root user can't receive a  
  13.         // broadcast packet on *nix if the socket is bound on non-wildcard address.  
  14.         logger.warn(  
  15.                 "A non-root user can't receive a broadcast packet if the socket " +  
  16.                 "is not bound to a wildcard address; binding to a non-wildcard " +  
  17.                 "address (" + localAddress + ") anyway as requested.");  
  18.     }  
  19.   
  20.     boolean wasActive = isActive();  
  21.     try {  
  22.         doBind(localAddress);  
  23.     } catch (Throwable t) {  
  24.         closeIfClosed();  
  25.         promise.setFailure(t);  
  26.         return;  
  27.     }  
  28.     if (!wasActive && isActive()) {  
  29.         invokeLater(new Runnable() {  
  30.             @Override  
  31.             public void run() {  
  32.                 pipeline.fireChannelActive();  
  33.             }  
  34.         });  
  35.     }  
  36.     promise.setSuccess();  
  37. }  
上面的代码最终调用了Channel的doBind函数,这里我们的Channel是NioServerSocketChannel,所以最终就是调用它的bind函数了,代码如下
  1. @Override  
  2. protected void doBind(SocketAddress localAddress) throws Exception {  
  3.     javaChannel().socket().bind(localAddress, config.getBacklog());  
  4. }  
其实它最终也是调用了JDK的Channel的socket bind函数。


看到这里,你是否会觉得有点怪异,为什么没有注册accpt事件啊,一般的我们的server socket都是要注册accpt事件到selector,用于监听连接。如果你发现了这个问题,说明你是理解socket的编程的,^_^。实际上是前面在分析bind的时候我们漏掉了一个重要的函数,initAndRegister,下面再来看看它的定义:

  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.     if (regPromise.cause() != null) {  
  13.         if (channel.isRegistered()) {  
  14.             channel.close();  
  15.         } else {  
  16.             channel.unsafe().closeForcibly();  
  17.         }  
  18.     }  
  19.   
  20.     // If we are here and the promise is not failed, it's one of the following cases:  
  21.     // 1) If we attempted registration from the event loop, the registration has been completed at this point.  
  22.     //    i.e. It's safe to attempt bind() or connect() now beause the channel has been registered.  
  23.     // 2) If we attempted registration from the other thread, the registration request has been successfully  
  24.     //    added to the event loop's task queue for later execution.  
  25.     //    i.e. It's safe to attempt bind() or connect() now:  
  26.     //         because bind() or connect() will be executed *after* the scheduled registration task is executed  
  27.     //         because register(), bind(), and connect() are all bound to the same thread.  
  28.   
  29.     return regPromise;  
  30. }  
在这里,我们看到了我们之前介绍event时说的register函数,它就是用于将Channel注册到eventloop中去的。eventloop经过层层调用,最终调用了SingleThreadEventLoop类中的register函数,下面是它的定义:
  1. @Override  
  2. public ChannelFuture register(final Channel channel, final ChannelPromise promise) {  
  3.     if (channel == null) {  
  4.         throw new NullPointerException("channel");  
  5.     }  
  6.     if (promise == null) {  
  7.         throw new NullPointerException("promise");  
  8.     }  
  9.   
  10.     channel.unsafe().register(this, promise);  
  11.     return promise;  
  12. }  
还是逃离不了unsafe对象的调用,前面说了那么多的unsafe,这个函数猜都可以猜测出执行过程了,这里就不细细的列举代码了。


还有一个init函数,这里需要说明一下,代码如下:

  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. }  
它就是用来处理channel 的pipeline,并添加一个ServerBootstrapAcceptor的handler,继续看看这个handler的定义,我们就会明白它的意图。
  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.     @Override  
  48.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
  49.         final ChannelConfig config = ctx.channel().config();  
  50.         if (config.isAutoRead()) {  
  51.             // stop accept new connections for 1 second to allow the channel to recover  
  52.             // See https://github.com/netty/netty/issues/1328  
  53.             config.setAutoRead(false);  
  54.             ctx.channel().eventLoop().schedule(new Runnable() {  
  55.                 @Override  
  56.                 public void run() {  
  57.                    config.setAutoRead(true);  
  58.                 }  
  59.             }, 1, TimeUnit.SECONDS);  
  60.         }  
  61.         // still let the exceptionCaught event flow through the pipeline to give the user  
  62.         // a chance to do something with it  
  63.         ctx.fireExceptionCaught(cause);  
  64.     }  
  65. }  
上面就是这个handler的全部代码,它重写了ChannelRead函数,它的目的其实是想将server端accept的channel注册到ChildGroup的eventloop中,这样就可以理解,服务端代码workerGroup这个eventloop的作用了,它终于在这里体现出了它的作用了。

3、总结

这篇文章主要是分析了serverbootstrap的全过程,通过对这个的分析,我们清晰的看到了平时编写socket服务端代码时对bind,register事件,以及accept channel等的处理。

相关文章推荐

Eclipse的自动build选项,制造时别忘了选上~

前言:今天修改代码,发现怎么改都没有效果~ 原因,之前在down一个工程时,把自动build选项关掉了~ ===========================================...
  • sxzlc
  • sxzlc
  • 2012年03月15日 22:17
  • 556

Eclipse NEON.1 安装最新的CDT插件

Eclipse 安装最新的CDT插件 NEON.1 1、启动eclipse, 2、选择Help->Install New Software...,CDT在线安装地址:http://download....

netty4.0.x源码分析—event

备注:本文的分析基于netty4.0.9final版本 1、event总体结构图 event总体结构类图如下: 2、event关键类和接口分析 1)基于NioEventLoop对关键类和接口进...

Netty 源码分析之 一 客户端创建(Bootstrap )

Bootstrap Bootstrap 是 Netty 提供的一个便利的工厂类, 我们可以通过它来完成 Netty 的客户端或服务器端的 Netty 初始化.下面我以 Netty 源码例子中的 Ec...
  • jeffleo
  • jeffleo
  • 2017年04月03日 22:38
  • 672

netty源码分析 之二 transport(bootstrap)

transport 分为两部分  bootstrap channel  由于channel东西比较多,所以分开两篇来写 AbstractBootstrap 顾名思义,引导累   抽象父类, 定义模板方...
  • akfly
  • akfly
  • 2017年01月09日 16:42
  • 194

Android 4.0 Launcher2源码分析—桌面快捷图标的拖拽

Android 4.0 Launcher2源码分析—桌面快捷图标的拖拽 (2013-06-15 20:07:54) 标签: it   http:...

【Netty源码学习】BootStrap

BootStrap是客户端的启动类,其主要功能就是设置必要的参数然后启动客户端。实现如下:Bootstrap b = new Bootstrap(); b.group(...

【Netty4.X】Netty源码分析之NioEventLoopGroup(五)

NioEventLoopGroup实际是NioEventLoop的线程组,主要负责管理EventLoop的生命周期,EventLoop的默认大小是2倍的CPU核数,但这并不是一个恒定的最佳数量,为了避...

【Netty4.X】Netty源码分析之ByteBuf(七)

ByteBuf是一个缓冲区,用于和NIO通道进行交互。缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。每...

【Netty4.X】Netty源码分析之NioEventLoop(六)

继承关系: NioEventLoop初始化NioEventLoop(NioEventLoopGroup parent, ThreadFactory threadFactory, SelectorP...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:netty4.0.x源码分析—bootstrap
举报原因:
原因补充:

(最多只允许输入30个字)