在上一篇《ServerBootstrap 与 Bootstrap 初探》中,咱们已经初步的了解了ServerBootstrap是netty进行服务端开发的引导类。 且在上一篇的服务端示例中,咱们也看到了,在使用netty进行网络编程时,咱们是经过bind方法的调用来完成服务器端口的侦听:java
EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler()) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 侦听8000端口 ChannelFuture f = b.bind(8000).sync(); f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); }
从上面的服务端示例中能够看到,咱们只是定义了主线程组及worker线程组,以及指定了channel类型为NioServerSocketChannel等等一些简单的配置, 而后绑定侦听端口,用于网络服务的主体代码基本就完了(业务代码在Handler中实现,后面的文章会详细介绍。
这真的大大简化并方便了java程序员使用netty来进行网络开发,可是想要深刻学习netty的人可能会有下面的一些疑问:程序员
本篇将带着上面这些疑问,咱们将进入bind方法内部,深刻浅出,对netty的工做机制一探究竟。编程
当咱们调用ServerBootstrap的bind方法时,实际上是调用的是父类AbstractBootstrap的bind方法:bootstrap
public ChannelFuture bind(int inetPort) { return bind(new InetSocketAddress(inetPort)); }
进而调用另外一个重载bind方法:segmentfault
public ChannelFuture bind(SocketAddress localAddress) { validate(); if (localAddress == null) { throw new NullPointerException("localAddress"); } return doBind(localAddress); }
此bind(SocketAddress localAddress)内部有两个调用:
一、 调用validate()
顾名思义,validate应该是作校验,因为ServerBootstrap覆盖了AbstractBootstrap方法,所以此validate实际上是调用ServerBootstrap中的validate方法:promise
@Override public ServerBootstrap validate() { super.validate(); if (childHandler == null) { throw new IllegalStateException("childHandler not set"); } if (childGroup == null) { logger.warn("childGroup is not set. Using parentGroup instead."); childGroup = config.group(); } return this; }
在子类ServerBootstrap的validate方法中,首先它有调用了基类的validate()方法:服务器
public B validate() { if (group == null) { throw new IllegalStateException("group not set"); } if (channelFactory == null) { throw new IllegalStateException("channel or channelFactory not set"); } return self(); }
因此经过validate方法咱们得出以下结论:网络
1) 服务端程序必需要设置boss线程组 以及 worker线程组,分别用于Acceptor 以及 I/O操做;
2)必须经过Boostrap的channel()来指定通道类型,用来生成相应的通道(ServerSocketChannel或SocketChannel);
3) 由于是服务端程序,因此必须设置ChildHandler,来指定业务处理器,不然没有业务处理的服务器hi没有意义的;
二、 调用doBind(localAddress)
首先咱们先看一下AbstractBootstrap中doBind方法代码片断:ide
private ChannelFuture doBind(final SocketAddress localAddress) { final ChannelFuture regFuture = initAndRegister(); // (1) final Channel channel = regFuture.channel(); // (2) if (regFuture.cause() != null) { return regFuture; } if (regFuture.isDone()) { ChannelPromise promise = channel.newPromise(); doBind0(regFuture, channel, localAddress, promise); // (3) return promise; } else { final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); regFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { promise.setFailure(cause); } else { promise.registered(); doBind0(regFuture, channel, localAddress, promise); // (3) } } }); return promise; } }
剥去无用代码,其实doBind方法内部,只作了两件事:
1、调用了initAndRegister方法
2、调用用了doBind0方法
到底这两个方法作了啥工做,咱们继续往下分析。oop
1、首先看看 initAndRegister方法内部代码:
final ChannelFuture initAndRegister() { Channel channel = null; try { // (1) 调用工厂方法,生成channel实例 channel = channelFactory.newChannel(); // (2) 初始化通道信息 init(channel); } catch (Throwable t) { if (channel != null) { channel.unsafe().closeForcibly(); } return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); } // (3) 注册通道 ChannelFuture regFuture = config().group().register(channel); if (regFuture.cause() != null) { if (channel.isRegistered()) { channel.close(); } else { channel.unsafe().closeForcibly(); } } return regFuture; }
经过上面代码,咱们能够看出initAndRegister方法作了三件事:
①、调用channelFactory生成通道channel实例:
在上一篇中,咱们已经知道,经过serverbootstrap的channel方法来指定通道类型,实际上是调用基类AbstractBoostrap的channel方法,其内部实际上是实例化了一个产生指定channel类型的channelFactory。
因此,initAndRegister中的channelFactory.newChannel()方法就是生成了一个NioServerSocketChannel的实例。 关于NioServerSocketChannel内部细节,我会有专门的文章进行分析,此处不作详述。
②、调用init(channel)初始化通道信息
init方法在基类AbstractBootstrap中是一个抽象方法:
abstract void init(Channel channel) throws Exception;
因此此处init的具体实如今子类ServerBootstrap类中:
@Override void init(Channel channel) throws Exception { // 设置引导类配置的option final Map<ChannelOption<?>, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); } // 设置引导类配置的attr final Map<AttributeKey<?>, Object> attrs = attrs0(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } } // 获取当前通道的pipeline ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry<ChannelOption<?>, Object>[] currentChildOptions; final Entry<AttributeKey<?>, Object>[] currentChildAttrs; synchronized (childOptions) { currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); } synchronized (childAttrs) { currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); } // 给NioServerSocketChannel的pipeline中添加一个ChannelInitializer类型的Handler p.addLast(new ChannelInitializer<Channel>() { @Override public void initChannel(final Channel ch) throws Exception { final ChannelPipeline pipeline = ch.pipeline(); ChannelHandler handler = config.handler(); if (handler != null) { pipeline.addLast(handler); } ch.eventLoop().execute(new Runnable() { @Override public void run() { pipeline.addLast(new ServerBootstrapAcceptor( ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); } }); } }); }
init内部主要作了一下几件事:
ⅰ、 设置channel的options
final Map<ChannelOption<?>, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); }
ⅱ、设置channel的attribute
final Map<AttributeKey<?>, Object> attrs = attrs0(); synchronized (attrs) { for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey<Object> key = (AttributeKey<Object>) e.getKey(); channel.attr(key).set(e.getValue()); } }
ⅲ、给NioServerSocketChannel的pipeline中添加一个ChannelInitializer类型的Handler(根据类继承ChannelInitializer继承自ChannelInboundHandlerAdapter)
关于pipeline究竟是什么,本篇不作详述,下一篇我会跟NioServerSocketChannel来一块儿给你们分析一下。
③、完成通道的注册
通道初始化完成后,而后就能够注册通道了:
ChannelFuture regFuture = config().group().register(channel);
config()在AbstractBootstrap中也是个抽象方法:
public abstract AbstractBootstrapConfig<B, C> config();
因此具体的实现细节仍是在子类ServerBootstrap中:
@Override public final ServerBootstrapConfig config() { return config; }
此方法只会返回了config实例对象,此属性是在ServerBootstrap初始化时就建立了
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> { ... private final ServerBootstrapConfig config = new ServerBootstrapConfig(this); ... @Override public final ServerBootstrapConfig config() { return config; } }
咱们先看一下ServerBootstrapConfig的类继承结构图:
ServerBootstrapConfig初始化时传入的this对象,此this表示ServerBootstrap,并且ServerBootstrapConfig构造方法内部调用了其基类AbstractBootstrapConfig的构造方法:
ServerBootstrapConfig(ServerBootstrap bootstrap) { super(bootstrap); }
因此config().group()就对应ServerBootstrap的group属性引用(由上一篇得知group指向boss线程组),所以register实际上是调用的NioEventLoopGroup的register方法。
对于NioEventLoopGroup,目前你们只知道是个线程组,其内部到底如何实现的,它的做用究竟是什么,你们也都不太清楚,因为篇幅缘由,这里不做详细介绍,后面会有文章做专门详解。
2、咱们再回到doBind(localAddress)方法,内部在调用玩initAndRegister以后,就是调用doBind0方法
private static void doBind0( final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up // the pipeline in its channelRegistered() implementation. channel.eventLoop().execute(new Runnable() { @Override public void run() { if (regFuture.isSuccess()) { channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); } else { promise.setFailure(regFuture.cause()); } } }); }
此方法内部就是完成channel的侦听端口的绑定。
至此ServerBootstrap的bind工做执行完成。
此篇对服务端绑定的流程作了大致介绍,但因为篇幅问题,下面几个问题未作详尽分析:
一、 NioServerSocketChannel是如何实例化的
二、 Pipeline是什么,为何要经过它添加handler
三、 NioEventLoopGroup内部细节是什么,为何要经过它注册Channel, Java NIO中channel初始化后不是要注册到selector中吗?
带着上面这些疑问,欢迎你们继续关注接下来的几篇文章,在这几篇文章中,bind操做会一直贯穿其中:
Netty4.x 源码实战系列(三):NioServerSocketChannel全剖析
Netty4.x 源码实战系列(四):Pipeline全剖析Netty4.x 源码实战系列(五):NioEventLoopGroup全剖析Netty4.x 源码实战系列(六):NioEventLoop全剖析