热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Netty引导类(Bootstrap类的解析)

Bootstrap引导类前面讲解了Netty整体架构以及类之间的关系,然后下面就先从框架的入口引导类(就是一个帮助类,存放了一些框架启动时必要的属性,例如ChannelFactor

Bootstrap 引导类

前面讲解了Netty整体架构以及类之间的关系,然后下面就先从框架的入口引导类(就是一个帮助类,存放了一些框架启动时必要的属性,例如ChannelFactory、ChannelPipeline、Map options)开始,看看是如何把这些类组装在一起的。

类关系

Bootstrap:封装了一些服务端和客户端引导类公共的逻辑,通过构造函数初始化参数

ServerBootstrap:继承了Bootstrap,组装了服务端所需要的相关的类 (创建的是需要连接的channel tcp/ip)

ClientBootstrap:继承了Bootstrap,组装了客户端所需要的相关的类 (创建的是需要连接的channel tcp/ip)

ConnectionlessBootstrap:继承了Bootstrap,(创建的是无需连接的channel udp/ip)

Bootstrap

该类提供了公共的数据结构,有创建Channel工厂、初始化了ChannelPipeline和ChannelPipelineFactory、存放了配置Channel参数的Map。

Netty引导类(Bootstrap类的解析)

图-bootstrap0

两个构造方法,一个是无参的,一个是有ChannelFactory(创建Channel对象的工厂),如果在新建类时没有传入,那么一定需要调用setFactory方法传入(如果已经设置了channel factory那么就抛出IllegalStateException异常)

注意: 默认ChannelPipelineFactory 中的实现就是将pipeline中的ChannelHandler浅拷贝到新的DefaultChannelPipeline中,如果对于服务端接受多个Channel时,若ChannelHandler是有状态的话就会有线程安全问题(因为在没有同步情况下,多个线程操作同个内存资源),就必须要自己实现ChannelPipelineFactory接口来创建ChannelPipeline(这样每次获取pipeline都会重新new一个ChannelHandler)。

Map options 存放配置channel的信息,如果给子channel(有服务端接受客户端的channel)配置需要在key的名字用child前缀 例如:child.keepAlive

实现了ExternalResourceReleasable接口,表示该类支持释放外部资源,就是调用factory.releaseExternalResources()方法

ServerBootstrap

下面看看ServerBootstrap是如何绑定某个端口的,做了哪些操作

下面看bind操作,在某个ip和某个端口上进行监听客户端连接操作

Channel bind():会从options获取 key=localAddress,然后会调用bind(SocketAddress localAddress)方法

Channel bind(SocketAddress localAddress) 该方法最终会调用bindAsync(SocketAddress localAddress)方法,该方法是一个异步的,操作结果并不会马上获取到,当一有结果就会notify 添加的ChannelFutureListener,并唤醒阻塞等待结果的线程。 ChannelFututre中的awaitUninterruptibly方法就是会一直阻塞等待结果

ChannelFuture bindAsync(SocketAddress localAddress) 绑定操作都会调用该方法,具体实现如图:

Netty引导类(Bootstrap类的解析)

图-s-bootstrap1

从bindAsync方法中并没有看到去绑定的操作,再认真想下就明白,Netty是一种以事件驱动的框架,所有的逻辑操作都Binder(一个实现ChannelUpstreamHandler类)

内部类Binder相关逻辑:

1、当server side Channel创建的时候(也就是上面当 getFactory().newChannel(pipeline)的时候,在channel具体实现类的构造方法中会通过门面类Channels.channel.getPipeline().sendUpstream(new UpstreamChannelEvent())下发一个Channel打开的事件)

2、binder类中的channelOpen()方法会被调用,然后给Channel中的ChannelConfig 设置pipelineFactory和options参数

3、直接调用Channel 的bind方法,实际上实现还是调用Channels.bind() 方法,下发一个DownstreamChannelEvent channel.getPipeline().sendDownstream(new DownstreamChannelEvent), 当达到ChannelPipeline维护的链表尾部时就会将Event交给ChannelSink进行处理,最终给I/O线程进行绑定操作,若绑定成功 channelFuture.setSuccess() 失败就setFail() 由I/O线程通知ChannelFutureListener。

4、childChannelOpen() 此方法是在子channel(服务端接受的客户端channel)创建的时候触发的

5、exceptionCaught() 处理绑定过程中抛出的异常

private final class Binder extends SimpleChannelUpstreamHandler {

    private final SocketAddress localAddress;
    private final Map childOptiOns=
        new HashMap();
    private final DefaultChannelFuture bindFuture = new DefaultChannelFuture(null, false);
    Binder(SocketAddress localAddress) {
        this.localAddress = localAddress;
    }

    //新建server socket channel后 在bind之前调用该方法
    @Override
    public void channelOpen(
            ChannelHandlerContext ctx,
            ChannelStateEvent evt) {

        try {
            //设置其Channel中ChannelConfig中的pipeline factory
            evt.getChannel().getConfig().setPipelineFactory(getPipelineFactory());

            // Split options into two categories: parent and child.
            Map allOptiOns= getOptions();
            Map parentOptiOns= new HashMap();
            for (Entry e: allOptions.entrySet()) {
                if (e.getKey().startsWith("child.")) {
                    childOptions.put(
                            e.getKey().substring(6),
                            e.getValue());
                } else if (!"pipelineFactory".equals(e.getKey())) {
                    parentOptions.put(e.getKey(), e.getValue());
                }
            }

            // Apply parent options.
            evt.getChannel().getConfig().setOptions(parentOptions);
        } finally {
            //继续下发到下个upstream handler
            ctx.sendUpstream(evt);
        }
        //事件上调用Channels.bind 方法下发一个DownstreamEvent bind事件,成功后异步通过ChannelFuture 进行通知回调
        evt.getChannel().bind(localAddress).addListener(new ChannelFutureListener() {
            //绑定操作完成后回调该方法,成功或失败
            public void operationComplete(ChannelFuture future) throws Exception {
                if (future.isSuccess()) {
                    //绑定本地地址成功 在端口上进行监听
                    bindFuture.setSuccess();
                } else {
                    bindFuture.setFailure(future.getCause());
                }
            }
        });
    }

    //当服务端接受客户端连接时,新建子channel时调用
    @Override
    public void childChannelOpen(
            ChannelHandlerContext ctx,
            ChildChannelStateEvent e) throws Exception {
        // Apply child options.
        try {
            e.getChildChannel().getConfig().setOptions(childOptions);
        } catch (Throwable t) {
            //将异常通过pipeline 传递到handelr进行处理
            fireExceptionCaught(e.getChildChannel(), t);
        }
        ctx.sendUpstream(e);
    }
	//处理异常事件
    @Override
    public void exceptionCaught(
            ChannelHandlerContext ctx, ExceptionEvent e)
            throws Exception {
        bindFuture.setFailure(e.getCause());
        ctx.sendUpstream(e);
    }
}

下面为bind的时序图

Netty引导类(Bootstrap类的解析)

图-boostrap-bind-seq

ClientBootstrap

初始化客户端连接服务端所需要的对象,和ServerBootstrap类包含的属性基本一样,原理也类似,不过这里connect服务端地址为remoteAddress 还可以在本地localAddress进行监听。

下面一步一步分析,client channel是如何连接上server的,又是如何体现出两个reactor模式(main reactor + sub reactor),同样又如何面向事件驱动的?

该为ClientBootstrap中的connect(SocketAddress remoteAddress,SocketAddress localAddress)方法实现图:
Netty引导类(Bootstrap类的解析)
图-client-bootstrap1

从ClientBootstrap类中的connect(SocketAddress remoteAddress)方法中看出,既可以connect到服务端,又可以在本地某个端口进行监听(既是客户端又是服务端),主要看ch.connect方法。

ch.conect()会调用抽象类AbstractChannel(所有具体Channel实现都会继承该类)中的connect方法,如图:

Netty引导类(Bootstrap类的解析)
图-client-bootstrap2

实际上是调用了Channels.connect() 方法 如图:

Netty引导类(Bootstrap类的解析)

图-client-bootstrap3

发送一个将要去connect服务端的事件,对于Channel主动改变状态或主动发送消息会下发一个Downstream ChannelEvent 下游事件,前面Netty架构分析中说到:在ChannelPipeline中链表尾部开始向前执行ChannelDownstreamHandler至头部时,否会将该最终会交给ChannelSink进行处理,具体代码实现如图:

Netty引导类(Bootstrap类的解析)

图-client-bootstrap4

具体看看NioClientSocketPipelineSink中的connect实现

如图:

Netty引导类(Bootstrap类的解析)
图-client-bootstrap4-1

对于上图具体实现流程为:

1、先调用 nio channel中的connect方法,该方法是个异步方法,如果connect返回true表示连接成功,若成功进入2 否则进入 3

2、通过NioWorker(封装了nio selector的一个I/O线程)将可读事件注册到selector上

3、没有立刻连接成功,则通过NioClientBoss(main reactor) 注册一个OP_CONNECT事件,如果该连接被服务端接受那么操作系统层面的selector就会唤醒用户中的阻塞的selector线程,并将就绪的Channel和就绪的状态封装到SelectionKey中,则进入4

4、selector监听到至少一个就绪的Channel就会调用NioClientBoss中的process方法,如果是isConnectable则调用connect(Selection k) 进入5

5、调用channel.finishConnect()方法成功返回true后,将Channel的后面的读和写事件放在NioWorker中(sub reactor)(若不调用的话就抛出java.nio.channels.NotYetConnectedException异常 由于该channel是异步的,所以不会阻塞在connect直到有连接,所以需要判断 nio channel.finishConnect()是否完成连接操作,若完成连接则返回true,否则为false, 后面才可以进行读和写)。

步骤2具体代码实现如图:

Netty引导类(Bootstrap类的解析)

图-client-bootstrap9

步骤3具体代码实现如图:

Netty引导类(Bootstrap类的解析)
图-client-bootstrap5

Netty引导类(Bootstrap类的解析)

图-client-bootstrap6

步骤4、5具体代码如图:

Netty引导类(Bootstrap类的解析)

图-client-bootstrap7

Netty引导类(Bootstrap类的解析)

图-client-bootstrap8

summary:

1、首先连接请求不是直接去调用nio channel connect,而是通过绕了一大圈,首先包装一个connect事件,经过ChannelPipeline中的各个ChannelHandler,处理完后才会进行最终的nio channel connect。

2、由于nio channel是非阻塞的,所以任何操作(连接、读和写)都不会立即返回结果,而是需要通过信号分离器(Selector)进行callback 通知,所以Netty就会将连接(对于server side就是accept)操作用bossCount个线程不过一般就1个Selector处理Channel,将Channel读和写事件就会交给workCount个线程的Selector进行处理这些Channel。

Netty Main Reactor+Sub Reactor的好处是什么?具体看前面讲的Reactor模式

当面对大量并发连接时不会影响现已连接的用户的读和写逻辑,如果是单个线程(一个Selector)来处理时,其他读和写就绪的Channel就会在后面等待前面大量accpet事件的Channel进行处理,如果按现在的线程模型则读和写事件是单独线程处理,互补影响。

瓶颈: java上线程资源是有限的,一个线程上会有很多个Channel,那么在这个线程上的Channel同样也会存在等待问题,最理想情况是一个Channel对应于线程来处理,但是这个又回到了之前阻塞Channel,经过各种系统性能指标分析得到:线程的切换和线程同步、线程占用内存资源(栈空间越大,堆空间会减小)都是会消耗系统资源的,所以综合来说nio 还是大大提高了系统吞吐量和提高了网络传输效率。


推荐阅读
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
author-avatar
你给的未来丶我不要
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有