博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Netty4.x 源码实战系列(二):服务端bind流程详解
阅读量:6469 次
发布时间:2019-06-23

本文共 9586 字,大约阅读时间需要 31 分钟。

在上一篇中,我们已经初步的了解了ServerBootstrap是netty进行服务端开发的引导类。 且在上一篇的服务端示例中,我们也看到了,在使用netty进行网络编程时,我们是通过bind方法的调用来完成服务器端口的侦听:

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
() { @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的人可能会有下面的一些疑问:

  1. netty是继续Java NIO的,那么ServerSocketChannel是什么时候初始化的?
  2. 我怎么没有看到Java NIO中的selector, netty是如何实现多路复用的?
  3. 我们设置的handler 或者 childHandler,是如何应用的?
  4. boss线程组 以及 worker线程组 其实如何分配线程的?
  5. 为什么是boss线程组,难道接收用户请求是多个线程一起工作?
  6. 。。。

本篇将带着上面这些疑问,我们将进入bind方法内部,深入浅出,对netty的工作机制一探究竟。

当我们调用ServerBootstrap的bind方法时,其实是调用的是父类AbstractBootstrap的bind方法:

public ChannelFuture bind(int inetPort) {    return bind(new InetSocketAddress(inetPort));}

进而调用另一个重载bind方法:

public ChannelFuture bind(SocketAddress localAddress) {    validate();    if (localAddress == null) {        throw new NullPointerException("localAddress");    }    return doBind(localAddress);}

此bind(SocketAddress localAddress)内部有两个调用

1、 调用validate()
顾名思义,validate应该是做校验,由于ServerBootstrap覆盖了AbstractBootstrap方法,因此此validate其实是调用ServerBootstrap中的validate方法:

@Overridepublic 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没有意义的;

2、 调用doBind(localAddress)

首先我们先看一下AbstractBootstrap中doBind方法代码片段:

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方法内部,只做了两件事:

一、调用了initAndRegister方法
二、调用用了doBind0方法
到底这两个方法做了啥工作,我们继续往下分析。

一、首先看看 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类中:

@Overridevoid init(Channel channel) throws Exception {    // 设置引导类配置的option    final Map
, Object> options = options0(); synchronized (options) { setChannelOptions(channel, options, logger); } // 设置引导类配置的attr final Map
, Object> attrs = attrs0(); synchronized (attrs) { for (Entry
, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey
key = (AttributeKey) e.getKey(); channel.attr(key).set(e.getValue()); } } // 获取当前通道的pipeline ChannelPipeline p = channel.pipeline(); final EventLoopGroup currentChildGroup = childGroup; final ChannelHandler currentChildHandler = childHandler; final Entry
, Object>[] currentChildOptions; final Entry
, 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
() { @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
, Object> options = options0();synchronized (options) { setChannelOptions(channel, options, logger);}

ⅱ、设置channel的attribute

final Map
, Object> attrs = attrs0();synchronized (attrs) { for (Entry
, Object> e: attrs.entrySet()) { @SuppressWarnings("unchecked") AttributeKey
key = (AttributeKey) 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
config();

所以具体的实现细节还是在子类ServerBootstrap中:

@Overridepublic final ServerBootstrapConfig config() {    return config;}

此方法只会返回了config实例对象,此属性是在ServerBootstrap初始化时就创建了

public class ServerBootstrap extends AbstractBootstrap
{ ... 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,目前大家只知道是个线程组,其内部到底如何实现的,它的作用到底是什么,大家也都不太清楚,由于篇幅原因,这里不作详细介绍,后面会有文章作专门详解。

二、我们再回到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工作执行完成。

此篇对服务端绑定的流程做了大体介绍,但由于篇幅问题,下面几个问题未做详尽分析:

1、 NioServerSocketChannel是如何实例化的
2、 Pipeline是什么,为什么要通过它添加handler
3、 NioEventLoopGroup内部细节是什么,为什么要通过它注册Channel, Java NIO中channel初始化后不是要注册到selector中吗?

带着上面这些疑问,欢迎大家继续关注接下来的几篇文章,在这几篇文章中,bind操作会一直贯穿其中:

Netty4.x 源码实战系列(五):NioEventLoopGroup全剖析
Netty4.x 源码实战系列(六):NioEventLoop全剖析

转载地址:http://cwjko.baihongyu.com/

你可能感兴趣的文章
C++输入补充
查看>>
php.ini memcache
查看>>
多彩绘版
查看>>
我的友情链接
查看>>
Android 自动化测试—robotium(二)初识
查看>>
VMware-桌面虚拟化(二)
查看>>
haproxy 重启提示:cannot bind socket(无法绑定socket)
查看>>
与RMAN有关的初始化参数和动态性能视图
查看>>
2017下半年计划
查看>>
nginx+keepalived做高可用
查看>>
安装盘启动系统,挂载硬盘,然后进入shell模式修改配置文件
查看>>
安装 Active Directory 架构管理单元
查看>>
Linux系统巡检常用命令
查看>>
C++ 中字符串与数值的相互转换
查看>>
邮件江湖群狼环伺 U-Mail邮件系统防狼有术
查看>>
全球五大顶级域名统计:5月第三周新增20.3万个
查看>>
MFS--分布式文件系统
查看>>
我的友情链接
查看>>
nagios下 监控内存的插件
查看>>
linux磁盘分区
查看>>