Netty核心原理与基础实战(二)——详解Bootstrap 备份

接上篇:Netty核心原理与基础实战(一)

1 Bootstrap基础概念

        Bootstrap类是Netty提供的一个便利的工厂类,可以通过它来完成Netty的客户端或服务端的Netty组件的组装,以及Netty程序的初始化和启动执行。Netty的官方解释是:完全可以不用Bootstrap类,可以一点点去手动创建通道、完成各种设置和启动注册到EventLoop反应器,然后开始事件的轮询和处理,但是这个过程会非常麻烦。通常情况下,使用这个便利的Bootstrap工具类的效率会更高。

        在Netty中有两个引导类,分别用于服务器和客户端,如下图:

        这两个引导类仅使用的地方不同,它们大致的配置和使用方法都是相同的。下面以ServerBootStrap类作为重点介绍对象。

        在介绍 ServerBootStrap 的服务器启动流程之前,首先介绍一下涉及的两个基础概念:父子通道、EventLoopGroup(事件轮询线程组)。

1.1 父子通道

        在NEtty中,每一个NioSocketChannel通道所封装的都是Java NIO通道,再往下就对应到了操作系统底层的socket文件描述符。理论上来说,操作系统底层的socket文件描述符分两类:

        1、连接监听类型。连接监听类型的socket描述符处于服务端,负责接收客户端的套接字连接;在服务端,一个“连接监听类型”的socket描述符可以接受成千上万的传输类的socket文件描述符。

        2、数据传输类型。数据传输类型的socket描述符负责传输数据。同一个TCP的socket传输链路在服务器和客户端都分别会有一个与之相对应的数据传输类型的socket文件描述符。

        在NEtty中,异步非阻塞的服务端监听通道NioServerSocketChannel所封装的Linux底层的文件描述符是“连接监听类型”的socket描述符;异步非阻塞的传输通道NioSocketChannel所封装的Linux的文件描述符是“数据传输类型”的socket描述符。

        在NEtty中,将有接收关系的监听通道和传输通道叫做父子通道。其中,负责服务器链接监听和接受的监听通道叫父通道,对应于每一个接收到的传输类型通道叫子通道。

1.2 EventLoopGroup

        前面介绍Reactor模式的具体实现时,分为单线程实现版本和多线程实现版本。NEtty中的Reactor模式实现的是多线程版本。

        实际上,在NEtty中一个EventLoop相当于一个子反应器(SubReactor),一个NioEventLoop子反应器拥有了一个事件轮询线程,同时拥有一个Java NIO选择器。

        NEtty是如何实现多线程版本的Reactor模式呢?是使用EventLoopGroup(事件轮询组)。多个EventLoop线程放在一起,可以组成一个EventLoopGroup。反过来说,EventLoopGroup就是一个多线程版本的反应器,其中的单个EventLoop线程对应于一个子反应器(SubReactor)。

        NEtty的程序开始不会直接使用单个EventLoop(事件轮询器),而是使用EventLoopGroup。EventLoopGroup的构造函数只有一个参数,用于指定内部的线程数。在构造器初始化时,会按照传如的线程数量在内部构造多个线程和多个EventLoop子反应器(一个线程对应一个EventLoop子反应器),进行多线程的IO事件查询和分发。

        如果使用EventLoopGroup的无参构造函数,没有传入线程数量或者传入的数量是0,那么EventLoopGroup内部默认的线程数量为最大可用的CPU处理器是数量的2倍。建设电脑使用的是4核CPU,那么在内部启动8个EventLoop线程,相当于8个子反应器实例。

        从前文可以,为了及时接收新连接,在服务端,一般有两个独立的反应器,一个负责新连接的监听和接收,另一个负责IO事件轮询和分发,并且两个反应器相互隔离。对应到NEtty服务器程序中,则需要设置两个EventLoopGroup,一个组负责新连接的监听和接收,另一个组负责IO传输事件的轮询和分发,另个轮询组的职责具体如下:

        1、负责新连接的监听和接收的EventLoopGroup中的反应器完成查询通道的新连接IO事件查询。这些反应器有点像负责招工的包工头,因此,该轮询组可以形象地称为“Boss轮询组”。

        2、负责IO事件轮询和分发的反应器完成查询所有子通道的IO事件,并且执行对应的Handler处理器完成IO处理——例如数据的输入和输出,这个轮询组可以形象地称为“worker轮询组”。

        NEtty的EventLoopGroup与EventLoop之间、EventLoop与Channel之间的关系如下图:

        到此介绍完了两个重要的基础概念:父子通道与 EventLoopGroup。接下来正是介绍ServerBootstrap的启动流程。

2 Bootstrap启动流程

        Bootstrap的启动流程也就是NEtty组件的组装、配置、以及NEtty服务器或者客户端的启动流程。在本节中对启动流程进行了梳理,大致分为8个步骤。本文仅仅演示的是服务端引导类的使用,用到的引导类为ServerBootstrap。正式使用之前,首选创建一个服务端的引导类实例。

ServerBootstrap b = new ServerBootstrap();

        接下来,结合前面的NettyDiscradServer服务器的程序代码,详细介绍一个Bootstrap启动流程中的8个步骤。

        第一步:创建反应器轮询组,并设置到ServerBootstrap引导类实例。

public static void test01(){
        //创建一个服务端的引导类
        ServerBootstrap b = new ServerBootstrap();
        //1.创建反应器轮询组,并设置到ServerBootstrap引导类实例
        //boss轮询组
        NioEventLoopGroup bossLoopGroups = new NioEventLoopGroup(1);
        //worker轮询组
        NioEventLoopGroup workerLoopGroups = new NioEventLoopGroup();
        //为引导类实例设置反应器轮询组
        b.group(bossLoopGroups,workerLoopGroups);

    }

        在设置反应器轮询组之前,创建了两个NioEventLoopGroup,一个负责处理连接监听IO事件,称为bossLoopGroups;另一个负责数据传输事件和处理,称为workerLoopGroups。在两个轮询组创建完成后,就可以配置给引导类实例,它一次性地给引导类配置两个轮询组。

        如果不需要分开监听新连接事件和输出事件,就不一定非得配置两个轮询组,可以只配置一个EventLoopGroup反应器轮询组。在这种模式下,新连接监听IO事件和数据传输IO事件可能被挤在了同一个线程中处理。这样就会带来一个风险:新连接的接收被更加耗时的数据传输或者业务处理所阻塞。所以在服务端,建议设置两个轮询组的工作模式。

        第二步:设置通道的IO类型。Netty不仅支持Java NIO,也支持阻塞式的OIO。下面配置的是Java NIO类型的通道。

//2.设置传输通道的类型为NIO类型
b.channel(NioServerSocketChannel.class);

        第三步:设置监听端口。

//3.设置监听端口
b.localAddress(new InetSocketAddress(8080));

        第四步:设置传输通道的配置选项。

//4.设置传输通道的参数
b.option(ChannelOption.SO_KEEPALIVE,true);
b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);

        这里调用了Bootstrap的option()选项设置方法。对于服务器的Bootstrap而言,这个方法的作用是:给父通道设置一些与传输协议相关的选项。如果要给子通道设置一些通道选项,则需要调用childOption()方法。

        可以设置那些通道选择呢?在上面的代码中,设置了一个底层TCP相关的选项 ChannelOption.SO_KEEPALIVE。该选项代表是否开始TCP底层心跳机制,true为开启,false为关闭。其他的通道设置选项,下节会介绍。

        第五步:装配子通道的Pipeline。每一个通道都用一条ChannelPipeline流水线,它的内部有一个双向链表。装配流水线的方式是:将业务处理器ChannelHandler实例包装之后加入双向链表中。

        如何装配Pipeline流水线呢?装配子通道的Handler流水线调用引导类的childHandler方法,该方法需要传入一个ChannelInitializer通道初始化类的示例作为参数。每当父通道成功接收一个连接并创建成功一个子通道后,就会初始化子通道,此时这里配置的ChannelInitializer实例就会被调用。在ChannelInitializer通道初始化类的实例中,有一个initChannel初始化方法,在子通道创建后会被执行,向子通道流水线增加业务处理器。装配子通道的Pipeline流水线的代码如下:

        //5.装配子通道流水线
        b.childHandler(new ChannelInitializer<SocketChannel>() {
            //有链接到达时,会创建一个通道的子通道,并初始化
            protected void initChannel(SocketChannel ch){
                //这里可以管理子通道中的Handler
                //向子通道流水线添加一个Handler业务处理器
                ch.pipeline().addLast(new NettyDiscardHandler());
            }
        });

        为什么仅装配子通道的流水线,而不需要装配父通道的流水线呢?因为父通道的内部业务处理是固定的:接收新连接后,创建子通道,然后初始化子通道,所以不需要特别的配置,由Netty自行进行装配。如果需要完成特殊的父通道业务处理,可以类似地调用ServerBootstrap的handler(ChannelHandler handler)方法,为父通道设置初始化器。

        在装配流水线时需要注意:ChannelInitializer处理器有一个泛型参数SocketChannel,这个类型需要和前面的引导类中设置的传输通道类型一一对应。

        第六步:开始绑定服务器新连接的监听端口。

//6.开始板顶端口,通过调用sync()同步方法阻塞直到绑定成功
hannelFuture channelFuture = b.bind().sync();
System.out.println("服务器启动成功,监听端口:" + channelFuture.channel().localAddress());

        这个也很简单,b.bind()方法的功能是返回一个端口绑定Netty的异步任务channelFuture。这里,并没有channelFuture异步任务增加回调监听器,而是阻塞channelFuture异步任务。直到端口板顶任务执行完成。

        在Netty中,所有的IO操作都是异步执行的,这就意味着任何一个IO操作都会立即返回,返回时异步任务还没有真正执行。什么时候执行完成?Netty中的IO操作都会返回异步任务实例(如channelFuture实例)。通过该异步任务实例,既可以实现同步阻塞一直到channelFuture异步任务执行完成,也可以通过为其增加事件监听器的方法注册异步回调逻辑,以获得Netty中的IO操作的真正结果。

        第七步:自我阻塞,直到监听通过关闭。

        //7.自我阻塞,直到通道关闭安的异步任务结束
        ChannelFuture closeFuture = channelFuture.channel().closeFuture();
        closeFuture.sync();

        如果要阻塞当前线程直到通道关闭,可以调用通道的closeFuture()方法,已获得通道关闭的异步任务。当通道关闭时,closeFuture实例的sycn方法会返回。

        第八步:关闭EvectLoopGroup。

        //8 释放所有资源
        workerLoopGroups.shutdownGracefully();
        bossLoopGroups.shutdownGracefully();

        关闭反应器轮询组,同时也会关闭内部的子反应器线程,也会关闭内部的选择器、内部的轮询线程以及负责查询的所有子通道。在子通道关闭后,会释放底层的资源,如Socket文件描述符等。 

3 ChannelOption

        无论是对于NioServerSocketChannel父通道类型还是对于NioSocketChannel子通道类型,都可以设置一系列的ChannelOption(通道选项)。ChannelOption类中定义了一些列选项,下面介绍一些常见的选项。

1.SO_RCVBUF和SO_SNDBUF

        这两个为TCP传输选项,每个TCP socket(套接字)在内核中都有一个类发送缓冲区和一个接收缓冲区,这两个选项就是用来设置TCP连接的两个缓冲区大小的。TCP的全双工作模式以及TCP的滑动窗口对两个独立的缓冲区都有依赖。

2.TCP_NODELAY

        此为TCP传输选项,如果设置为true就表示立即发送数据。TCP_NODELAY用于开启或关闭Nagle算法。如果要求高实时性,有数据发送时就马上发送,就将该选项设置为true(关闭Nagle算法);如果要减少发送次数、减少网路交互,就设置为false(开发Nagle算法),等累计一定大小的数据后再发送。关于TCP_NODELAY的值,Netty模式为true,而操作系统默认为false。

        Nagle算法将小的碎片数据连接成更大的报文(或数据包)来最小化所发送报文的数量,如果需要发送一些较小的报文,则需要禁用该算法。

        Netty模式禁用Nagle算法,报文会立即发送出去,从而最小化报文传输的延时。

3.SO_KEEPALIVE

        此为TCP传输选项,表示是否开启TCP的心跳机制。true为保持连接心跳,默认值为false。启动该功能时,TCP会主动探测空闲连接的有效性。需要注意的是:默认的心跳间隔是7200秒,即2个小时。Netty默认关闭。

4.SO_REUSEADDR

        值为true时表示地址复用,默认为false。有四种情况需要用到这个参数设置:

        (1)当有一个地址和端口相同的连接socket1处于TIME_WAIT状态时,而又希望启动一个新的连接socket2要占用该地址和端口。

        (2)有多块网卡或用IP Alias技术的机器在同一个端口启动多个进程,但每个进程绑定的本地IP地址不能相同。

        (3)同一个进程绑定相同的端口到多个socket上,但每个socket绑定的IP地址不同。

        (4)完全相同的地址和端口的重复绑定,但这只用于UDP的多播,不用于TCP。

5.SO_LINGER

        用来控制socket.close()方法被调用后的行为,包括颜值关闭时间。如果值为 -1,就表示socket.close()方法在调用后立即返回,但操作系统底层会将发送缓冲区的数据全部发送到对端;如果值为0,表示socket.close()方法在调用后会立即返回,但是操作系统会放弃发送缓冲区数据,直接向对端发送RST包,对端将收到复位错误;如果值为非0整数值,就表示调用socket.close()方法的线程被阻塞,直到延迟时间到来,发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。模式值为-1,表示禁用该功能。

6.SO_BACKLOG

        表示服务端接收连接的队列长度,如果队列已满,客户端连接将被拒绝。服务端在处理客户端新连接请求时(三次握手)是顺序处理的,所以同一时间只能处理一个客户端连接,多个客户端到来的时候,服务端将不能处理的客户端连接请求方法队列中等待处理,队列的大小通过SO_BACKLOG指定。

        具体来说,服务端对完成第二次握手的连接放在一个队列(暂称A队列),如果进一步完成第三次握手,再把连接从A队列移动到新队列(B队列),接下来应用程序会通过调用accept()方法取出握手成功后的连接,而系统则会将该连接从B队列中移除。A和B队列的长度之和是SO_BACKLOG指定的值,当A和B队列的长度之和大于SO_BACKLOG值时,新连接将会被TCP内核拒绝。所以,如果SO_BACKLOG过小,accept速度可能会跟不上,A和B队列全满,导致新客户端无法连接。

        SO_BACKLOG对程序迟滞的连接数并无影响,影响的只是还没有被accept取出的连接数,也就是三次握手的排队连接数。如果连接建立频繁,服务器处理新连接较慢,可以适当调大这个参数。

                               

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/367271.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】线程池

线程池 一、线程池的概念1、线程池的优点2、线程池的应用场景 二、线程池的实现1、实现逻辑2、threadpool线程池&#xff08;1&#xff09;threadpool.hpp代码&#xff08;2&#xff09;为什么线程池中需要有互斥锁和条件变量&#xff1f;&#xff08;3&#xff09;注意点&…

elk之简介

写在前面 本文看下es的简介。 1&#xff1a;简介 背后公司&#xff0c;elastic&#xff0c;08年纽交所上市&#xff0c;与腾讯&#xff0c;阿里等云厂商有合作&#xff0c;推出云产品&#xff0c;类似功能的产品由solr&#xff0c;splunk&#xff0c;但使用量es当前遥遥领先…

[pytorch入门] 9. 优化器

介绍 在pytorch的官方文档中&#xff0c;所有的优化器都集中在torch.optim中 在官方文档中&#xff0c;会告诉你如何去创建一个优化器 选择一种优化器创建&#xff0c;传入模型的参数&#xff08;必需的&#xff09;、学习速率&#xff08;几乎是每个优化器都有的参数&#…

【制作100个unity游戏之23】实现类似七日杀、森林一样的生存游戏9(附项目源码)

本节最终效果演示 文章目录 本节最终效果演示系列目录前言回收物品素材绘制UI代码控制垃圾桶回收功能效果 源码完结 系列目录 前言 欢迎来到【制作100个Unity游戏】系列&#xff01;本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中&#xff0c;我们将…

DPVS 多活部署架构部署

一、目标 利用DPVS部署一个基于OSPF/ECMP的提供HTTP服务的多活高可用的测试环境。 本次部署仅用于验证功能&#xff0c;不提供性能验证。 配置两台DPVS组成集群、两台REAL SERVER提供实际HTTP服务。 注&#xff1a;在虚拟环境里面&#xff0c;通过在一台虚拟服务器上面安装FR…

2024牛客寒假算法基础集训营1

文章目录 A DFS搜索M牛客老粉才知道的秘密G why外卖E 本题又主要考察了贪心B 关鸡C 按闹分配 今天的牛客&#xff0c;说是都是基础题&#xff0c;头昏昏的&#xff0c;感觉真不会写&#xff0c;只能赛后补题了 A DFS搜索 写的时候刚开始以为还是比较难的&#xff0c;和dfs有关…

老版本labelme如何不保存imagedata

我的版本是3.16&#xff0c;默认英文且不带取消保存imagedata的选项。 最简单粗暴的方法就是在json文件保存时把传递过来的imagedata数据设定为None&#xff0c;方法如下&#xff1a; 找到labelme的源文件&#xff0c;例如&#xff1a;D:\conda\envs\deeplab\Lib\site-packages…

数据分析基础之《pandas(4)—pandas画图》

1、DataFrame.plot(xNone, yNone, kindline) 说明&#xff1a; x&#xff1a;设置x轴标签 y&#xff1a;设置y轴标签 kind&#xff1a; line 折线图 bar 柱状图 hist 直方图 pie 饼图 scatter 散点图 # 找到p_change和turnover之间的关系 data.plot(xvolume, yturnover, kinds…

dubbo+sentinel最简集成实例

说明 在集成seata后&#xff0c;下面来集成sentinel进行服务链路追踪管理&#xff5e; 背景 sample-front网关服务已配置好 集成 一、启动sentinel.jar 1、官网下载 选择1:在本地启动 nohup java -Dserver.port8082 -Dcsp.sentinel.dashboard.serverlocalhost:8082 -Dp…

Simulink|光伏阵列模拟多类故障(开路/短路/阴影遮挡/老化)

目录 主要内容 模型研究 1.正常模型 2.断路故障 3.短路故障 4.阴影遮挡 5.老化模型 结果一览 1.U-I曲线 2.P-V曲线 下载链接 主要内容 该模型为光伏阵列模拟故障情况simulink模型&#xff0c;程序实现了多种故障方式下的光伏阵列输出功率-电压-电流关系特…

链表——C语言——day17

链表 链表是一种常见的重要的数据结构。它是动态地进行存储分配的一种结构。在用数组存放数据时&#xff0c;必须事先定义固定的长度&#xff08;即元素个数&#xff09;。链表则没有这种缺点&#xff0c;它根据需要开辟内存单元。 链表有一个“头指针“变量&#xff0c;图中…

Docker极速入门掌握基本概念和用法

1、Docker概念 1.1什么是docker Docker是一个快速交付应用、运行应用的技术&#xff0c;具备以下优势 可将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;可以迁移到任意Linux操作系统运行时利用沙箱机制形成隔离容器&#xff0c;各个应用互不干扰启动、移除都可以通…

jmeter-03界面介绍

文章目录 主界面介绍测试计划介绍线程组介绍线程组——选择测试计划&#xff0c;右键-->添加-->线程-->线程组 主界面介绍 测试计划介绍 测试计划&#xff1a;本次测试所需要的所有内容&#xff0c;即父线程 线程组介绍 jmeter讲究一个概念&#xff1a;一个线程一…

如何在docker中访问电脑上的GPU?如何在docker中使用GPU进行模型训练或者加载调用?

如何在docker中访问电脑上的GPU&#xff1f;如何在docker中使用GPU进行模型训练或者加载调用&#xff1f; 其实使用非常简单&#xff0c;只是一行命令的事&#xff0c;最主要的事配置好驱动和权限。 docker run -it --rm --gpus all ycj520/centos:1.0.0 nvidia-smi先看看 st…

AI在线写作软件推荐:5款不可错过的写作工具

现在人工智能&#xff08;AI&#xff09;技术已经渗透到了各个领域&#xff0c;包括写作。AI在线写作软件的出现&#xff0c;为我们提供了更加高效、准确的写作工具。在本文中&#xff0c;我将向大家推荐5款功能强大的AI在线写作软件&#xff0c;这些软件可以帮助我们提高写作效…

一文掌握SpringBoot注解之@Configuration知识文集(3)

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

QEMU源码全解析 —— 内存虚拟化(2)

接前一篇文章&#xff1a; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 QEMU内存管理模型 特此致谢&#xff01; QEMU内存初始化 1. 基本结构 在开始介绍内存初始化…

对象克隆Objects

对象克隆 把A对象的属性值完全拷贝给B对象&#xff0c;也叫对象拷贝&#xff0c;对象复制。 package MyApi.a04objectdemo;public class ObjectDemo03 {public static void main(String[] args) throws CloneNotSupportedException {//1.先创建一个对象int []data{1,2,3,4,5,…

深度学习(12)--Mnist分类任务

一.Mnist分类任务流程详解 1.1.引入数据集 Mnist数据集是官方的数据集&#xff0c;比较特殊&#xff0c;可以直接通过%matplotlib inline自动下载&#xff0c;博主此处已经完成下载&#xff0c;从本地文件中引入数据集。 设置数据路径 from pathlib import Path# 设置数据路…

C# Onnx GroundingDINO 开放世界目标检测

目录 介绍 效果 模型信息 项目 代码 下载 介绍 地址&#xff1a;https://github.com/IDEA-Research/GroundingDINO Official implementation of the paper "Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection" 效果 …