netty4 bind启动过程简析

请看一下简单的 一个netty4服务端启动代码样例

 EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class);
            b.handler(new LoggingHandler(LogLevel.DEBUG));
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new echoServerHandler());
                }
            });
            b.option(ChannelOption.SO_KEEPALIVE, true);
            b.childOption(ChannelOption.SO_BACKLOG, 128);

            ChannelFuture channelFuture = b.bind(2000).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("called shutdown gracefully...");
        }

大家看到了,

 ChannelFuture channelFuture = b.bind(2000).sync();发生了什么调用呢 ?下面分析一下其调用链.首先上一个调用的轮廓图

上图是 netty4  bind()执行的时候的调用链.

下面看代码层面的调用 :

1.AbstractBootstrap样例创建的ServerBootstrap 执行bind的时候,直接执行父类的bind() ,它本身没有自己的实现.
//AbstractBootstrap  

  public ChannelFuture bind() {
        validate();
        SocketAddress localAddress = this.localAddress;
        if (localAddress == null) {
            throw new IllegalStateException("localAddress not set");
        }
        return doBind(localAddress);
    }

//--------------------------
 private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regPromise = initAndRegister();
        final Channel channel = regPromise.channel();
        final ChannelPromise promise = channel.newPromise();
        if (regPromise.isDone()) {
            doBind0(regPromise, channel, localAddress, promise);
        } else {
            regPromise.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    doBind0(future, channel, localAddress, promise);
                }
            });
        }

        return promise;
    }

//-------------------3--------------------------
 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());
                }
            }
        });
    }

2.bootStrap的bind会调用大abstractChannel的bind方法

//AbstractChannel

//-
  @Override
    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return pipeline.bind(localAddress, promise);
    }

3.abstractChannel会去调用 DefaultChannelPipeline的bind

///DefaultChannelPipeline

 @Override
    public ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise) {
        return tail.bind(localAddress, promise);
    }

4.DefaultChannelPipeline就是channel的管道.因为bind属于out方法,因此调用管道顺序是 tail ->head (handler)

在调用tail的bind的时候 ,走到了 DefaultChannelHandlerContext的bind,请看

//DefaultChannelHandlerContext
  @Override
    public ChannelFuture bind(final SocketAddress localAddress, final ChannelPromise promise) {
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        }
        validatePromise(promise, false);

        final DefaultChannelHandlerContext next = findContextOutbound();
        EventExecutor executor = next.executor();
        if (executor.inEventLoop()) {
            next.invokeBind(localAddress, promise);
        } else {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeBind(localAddress, promise);
                }
            });
        }

        return promise;
    }

5.上面有一句 final DefaultChannelHandlerContext next = findContextOutbound(); 要找outbound事件执行bind

只有headHandler继承了outBoundHandler,这时候又走到了DefaultChannelHandlerContext 的 invokeBind

// DefaultChannelHandlerContext  private void invokeBind(SocketAddress localAddress, ChannelPromise promise) {
        try {
            ((ChannelOutboundHandler) handler).bind(this, localAddress, promise);
        } catch (Throwable t) {
            notifyOutboundHandlerException(t, promise);
        }
    }

6.此时走到了 headhandler 的bind里面去了.(此时handler只有 tailHandler,headHeadler,ServerBootstrapAcceptor等几个.只有head属于outboundler)

//HeadHandler
   @Override
        public void bind(
                ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise)
                throws Exception {
            unsafe.bind(localAddress, promise);
        }

7.此时调用链走到了unsafe的bind里面.AbstractUnsafe代码调用逻辑如下

  @Override
        public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            if (!ensureOpen(promise)) {
                return;
            }

            // See: https://github.com/netty/netty/issues/576
            if (!PlatformDependent.isWindows() && !PlatformDependent.isRoot() &&
                Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&
                localAddress instanceof InetSocketAddress &&
                !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress()) {
                // Warn a user about the fact that a non-root user can‘t receive a
                // broadcast packet on *nix if the socket is bound on non-wildcard address.
                logger.warn(
                        "A non-root user can‘t receive a broadcast packet if the socket " +
                        "is not bound to a wildcard address; binding to a non-wildcard " +
                        "address (" + localAddress + ") anyway as requested.");
            }

            boolean wasActive = isActive();
            try {
                doBind(localAddress);
            } catch (Throwable t) {
                closeIfClosed();
                promise.setFailure(t);
                return;
            }
            if (!wasActive && isActive()) {
                invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        pipeline.fireChannelActive();
                    }
                });
            }
            promise.setSuccess();
        }

8.unsafe属于AbstractChannel的内部类.因此doBind(localAddress)调用的就是AbstractChannel的子类NioServerSocketChannel的方法

//NioServerSocketChannel
 @Override
    protected void doBind(SocketAddress localAddress) throws Exception {
        javaChannel().socket().bind(localAddress, config.getBacklog());
    }

//
@Overrideprotected ServerSocketChannel javaChannel() {    return (ServerSocketChannel) super.javaChannel();}

//abstractNioChannel 可以看到 这个channle的来源是 java.nio.serverSocketChannel
protected SelectableChannel javaChannel() {    return ch;}
 
此动作完成了 .bind方法的的所有bind操作.
				
时间: 2024-08-10 23:30:02

netty4 bind启动过程简析的相关文章

Linux进程启动过程简析

朱宇轲 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 今天,我们将通过阅读linux的内核代码来对linux系统中进程的创建过程进行简单的分析. 大家都知道,linux通过进程控制块PCB来对进程进行控制和管理,它存放了进程的数据.在linux中,PCB的代码如下(当然是节选的==): struct task_struct { volatile long state;//进程状

Nutch学习笔记——抓取过程简析

Nutch学习笔记二--抓取过程简析 学习环境: ubuntu 概要: Nutch 是一个开源Java 实现的搜索引擎.它提供了我们运行自己的搜索引擎所需的全部工具.包括全文搜索和Web爬虫. 通过nutch,诞生了hadoop.tika.gora. 先安装SVN和Ant环境.(通过编译源码方式来使用nutch) apt-get install ant apt-get install subversion [email protected]:~/data/nutch$ svn co https:

RAP开发入门-运行过程简析(三)

今天通过标准的RAP程序来简单分析下RAP的启动过程 1.新建一个标准的rap plugin-in 项目: 得到的项目结构大概如下: run confi..->..add bundle(配置好bundle 运行结果如下): 全屏控制代码: /** * Configures the initial size and appearance of a workbench window. * 配置初始大小和显示workbench的窗口样式 * -看来以后的主题应该在这里设置了 */ public cla

springboot启动流程简析

Spring Boot可以轻松创建独立的,生产级的基于Spring的应用程序,而这只需要很少的一些Spring配置.本文将从SpringBoot的启动流程角度简要的分析SpringBoot启动过程中主要做了哪些事情. 说明: springboot 2.0.6.RELEASE SpringBoot启动简要流程图 附原始大图链接 启动流程概述 启动流程从角度来看,主要分两个步骤.第一个步骤是构造一个SpringApplication应用,第二个步骤是调用它的run方法,启动应用. 1 构造Sprin

cocos2d-x安卓应用启动调用过程简析

调用org.cocos2dx.cpp.AppActivity AppActivity是位于proj.android/src下的开发者类(即开发者自定义的类),它继承自org.cocos2dx.lib.Cocos2dxActivity,在项目生成后它没有添加任何代码,纯粹是一个Cocos2dxActivity,也是一个Activity. AppActivity被调用是因为被配置在AndroidManifest.xml <application android:label="@string/a

linux文件系统写过程简析

linux写入磁盘过程经历VFS ->  页缓存(page cache) -> 具体的文件系统(ext2/3/4.XFS.ReiserFS等) -> Block IO ->设备驱动 -> SCSI指令(或者其他指令),总体来说linux文件写入磁盘过程比较复杂 1.VFS(虚拟文件系统) Linux中采用了VFS的方式屏蔽了多个文件系统的差别, 当需要不同的设备或者其他文件系统时,采用挂载mount的方式访问其他设备或者其他文件系统(这里可以把文件系统理解为具体的设备).正是

使用GDB进行系统调用过程简析

陈铁 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 作业的难度在增加,实验楼的虚拟机不太稳定,经常用着用着就不能操作了.没有细致研究,于是就在VirtualBox下新建了虚拟机,还算顺利.下载了Ubuntu的mini.iso,结果界面全是Debian的,选择高级里的Xfce,虽然全部下载使安装过程有点长,不过安装完之后没有太多的问题,就达到了实验楼同样的效果.于是下载了最新的k

HDFS读写过程简析

2.1.读文件的过程 客户端(client)用FileSystem的open()函数打开文件 DistributedFileSystem用RPC调用元数据节点,得到文件的数据块信息. 对于每一个数据块,元数据节点返回保存数据块的数据节点的地址. DistributedFileSystem返回FSDataInputStream给客户端,用来读取数据. 客户端调用stream的read()函数开始读取数据. DFSInputStream连接保存此文件第一个数据块的最近的数据节点. Data从数据节点

spring加载过程简析

INFO: Initializing Spring root WebApplicationContextINFO : org.springframework.web.context.ContextLoader - Root WebApplicationContext: initialization started(开始初始化web application 环境)INFO : org.springframework.web.context.support.XmlWebApplicationCont