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取出的连接数,也就是三次握手的排队连接数。如果连接建立频繁,服务器处理新连接较慢,可以适当调大这个参数。
相关文章:

Netty核心原理与基础实战(二)——详解Bootstrap
接上篇:Netty核心原理与基础实战(一) 1 Bootstrap基础概念 Bootstrap类是Netty提供的一个便利的工厂类,可以通过它来完成Netty的客户端或服务端的Netty组件的组装,以及Netty程序的初始化和启动执行。Netty的官方解释是…...

C语言常见面试题:C语言中如何进行比较运算?
在C语言中,比较运算用于比较两个值的大小关系。比较运算符包括等于()、不等于(!)、大于(>)、小于(<)、大于等于(>)和小于等于࿰…...

学习总结14
# 【CSGRound1】天下第一 ## 题目背景 天下第一的 cbw 以主席的身份在 8102 年统治全宇宙后,开始了自己休闲的生活,并邀请自己的好友每天都来和他做游戏。由于 cbw 想要显出自己平易近人,所以 zhouwc 虽然是一个蒟蒻,也有能和 c…...

D盘不见了如何恢复?4个恢复方法(新版)!
“很奇怪!我的电脑d盘不知道为什么突然不见了,我还保存了很多重要的文件在里面呢,有什么恢复d盘的方法吗?” 在我们的日常生活中,电脑已经成为了我们工作、学习和娱乐的重要工具。然而,有时候我们会遇到一些…...

vector类的模拟实现
实现基本的vector框架 参考的是STL的一些源码,实现的vector也是看起来像是一个简略版的,但是看完能对vector这个类一些接口函数更好的认识。 我们写写成员变量,先来看看STL的成元变量是那些 namespace tjl {template<class T>class …...

Topaz Photo AI for Mac v2.3.1 补丁版人工智能降噪软件无损放大
想要将模糊的图片变得更加清晰?不妨试试Topaz Photo AI for Mac 这款人工智能、无损放大软件。Topaz Photo AI for Mac 一款强大的人工智能降噪软件,允许用户使用复杂的锐化算法来提高图像清晰度,还包括肖像编辑选项,如面部重塑、…...

【Unity3D小技巧】Unity3D中UI控制解决方案
推荐阅读 CSDN主页GitHub开源地址Unity3D插件分享简书地址我的个人博客 大家好,我是佛系工程师☆恬静的小魔龙☆,不定时更新Unity开发技巧,觉得有用记得一键三连哦。 一、前言 在开发中总是会控制UI界面,如何优雅的控制UI界面是…...

【状态管理一】概览:状态使用、状态分类、状态具体使用
文章目录 一. 状态使用概览二. 状态的数据类型1. 算子层面2. 接口层面2.1. UML与所有状态类型介绍2.2. 内部状态:InternalKvState 将知识与实际的应用场景、设计背景关联起来,这是学以致用、刨根问底知识的一种直接方式。 本文介绍 状态数据管理&#x…...

SQL--多表查询
我们之前在讲解SQL语句的时候,讲解了DQL语句,也就是数据查询语句,但是之前讲解的查询都是单 表查询,而本章节我们要学习的则是多表查询操作,主要从以下几个方面进行讲解。 多表关系 项目开发中,在进行数据…...

多维时序 | Matlab实现CNN-RVM卷积神经网络结合相关向量机多变量时间序列预测
多维时序 | Matlab实现CNN-RVM卷积神经网络结合相关向量机多变量时间序列预测 目录 多维时序 | Matlab实现CNN-RVM卷积神经网络结合相关向量机多变量时间序列预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CNN-RVM卷积神经网络结合相关向量机多变量时间序…...

RK3568平台 安卓hal3适配usb camera
一.RK安卓hal3 camera框架 Camera hal3 在 android 框架中所处的位置如上图, 对上,主要实现 Framework 一整套 API 接口,响应其 控制命令,返回数据与控制参数结果。 对下, 主要是通 V4l2 框架实现与 kernel 的交互。3a…...

使用 Visual Studio Code 在远程计算机上调试 PostgreSQL
使用 Visual Studio Code 在远程计算机上调试 PostgreSQL 1. 概述 PostgreSQL 是一个功能强大的开源关系数据库管理系统,适用于各种应用程序。在开发过程中,调试 PostgreSQL 对于识别和解决问题至关重要。在本博客中,我们将手把手教你使用客…...

javascript设计模式之建造者
工厂模式不关心过程,只关心结果,这与建造者相反,建造者更关心的是过程, 这里我们创建一个基类,其拥有技能跟爱好两个属性,还有两个实例方法用来获取技能跟爱好 // 基类 let Human function (param {}) …...

安擎科技携手华为云区块链共同打造安全天空
当前,低空经济崛起,无人机多并发、混合运行时引发的网络信息安全、空域安全问题已成行业首要课题。 在2024年1月正式实施的《民用无人驾驶航空器运行安全管理规则》(CCAR-92)第549条中规定,“无人驾驶航空器航行服务提…...

学习数据结构的第一天
结构体 如何定义结构体 1、先定义结构体类型,再定义结构体类型变量 struct student/定义学生结构体类型/ { long number; char name[20]; char sex; int age; float score[3];/三科考试成绩/ }2、定义结构体类型同时定义结构体类型变量 struct student/定义学生结…...

5.electron之主进程起一个本地服务
如果可以实现记得点赞分享,谢谢老铁~ Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 Electron 将 Chromium 和 Node.js 嵌入到了一个二进制文件中,因此它允许你仅需一个代码仓库,就可以撰写支持 Windows、…...

爬取58二手房并用SVR模型拟合
目录 一、前言 二、爬虫与数据处理 三、模型 一、前言 爬取数据仅用于练习和学习。本文运用二手房规格sepc(如3室2厅1卫)和二手房面积area预测二手房价格price,只是练习和学习,不代表任何实际意义。 二、爬虫与数据处理 import requests import cha…...

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之RichText组件
鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之RichText组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、RichText组件 鸿蒙(HarmonyOS)富文本组件,…...

7.electron之渲染线程发送事件,主进程监听事件
如果可以实现记得点赞分享,谢谢老铁~ Electron是一个使用 JavaScript、HTML 和 CSS 构建桌面应用程序的框架。 Electron 将 Chromium 和 Node.js 嵌入到了一个二进制文件中,因此它允许你仅需一个代码仓库,就可以撰写支持 Windows、…...

thinkphp6入门(19)-- 中间件向控制器传参
可以通过给请求对象赋值的方式传参给控制器(或者其它地方),例如 <?phpnamespace app\middleware;class Hello {public function handle($request, \Closure $next){$request->hello ThinkPHP;return $next($request);} } 然后在控制…...

Flink Format系列(2)-CSV
Flink的csv格式支持读和写csv格式的数据,只需要指定 format csv,下面以kafka为例。 CREATE TABLE user_behavior (user_id BIGINT,item_id BIGINT,category_id BIGINT,behavior STRING,ts TIMESTAMP(3) ) WITH (connector kafka,topic user_behavior…...

Spring Data Envers 数据审计实战2 - 自定义监听程序扩展审计字段及字段值
上篇讲述了如何在Spring项目中集成Spring Data Envers做数据审计和历史版本查看功能。 之前演示的是业务表中已有的字段进行审计,那么如果我们想扩展审计字段呢? 比如目前对员工表加入了Audited审计,员工表有个字段为dept_id,为…...

一个 SpringBoot 项目能同时处理多少请求?
目录 1 问题分析 2 Demo 3 答案 4 怎么来的? 5 标准答案及影响参数一Tomcat配置 6 影响参数二 Web容器 7 影响参数三 Async 1 问题分析 一个 SpringBoot 项目能同时处理多少请求? 不知道你听到这个问题之后的第一反应是什么? 我大概…...

计算机网络——网络
计算机网络——网络 小程一言专栏链接: [link](http://t.csdnimg.cn/ZUTXU)前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家, [跳转到网站](https://www.captainbed.cn/qianqiu) 无线网络和移动网…...

C语言探索:选择排序的实现与解读
当我们需要对一组数据进行排序时,选择排序(Selection Sort)是一种简单但效率较低的排序算法。它的基本思想是每次从未排序的数据中选择最小(或最大)的元素,然后将其放置在已排序序列的末尾。通过重复这个过…...

Golang 学习(二)进阶使用
二、进阶使用 性能提升——协程 GoRoutine go f();一个 Go 线程上,可以起多个协程(有独立的栈空间、共享程序堆空间、调度由用户控制)主线程是一个物理线程,直接作用在 cpu 上的。是重量级的,非常耗费 cpu 资源。协…...

ubuntu22.04@laptop OpenCV定制化安装
ubuntu22.04laptop OpenCV定制化安装 1. 源由2. 默认配置3. 定制配置4. 定制安装5. 定制OpenCV-4.9.05.1 修改opencv.conf5.2 加载so文件5.3 修改bash环境变量5.4 增加pkgconfig5.5 检查OpenCV-4.9.0安装 6. 总结7. 参考资料 1. 源由 目前,能Google到的代码层次不齐…...

linux系统非关系型数据库redis
redis 介绍redis的特点:缓存 安装安装单机版redisredis的相关工具 介绍 redis是一个开源的、使用C语言编写的、支持网络交互的、可基于内存也可持久化的Key-Value数据库 redis的官网:redis.ioredis的特点: 丰富的数据结构 支持持久化 支持事务 支持主从缓存 类型 …...

【LeetCode: 292. Nim 游戏+ 博弈问题】
🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…...

Android 9.0 禁用adb reboot recovery命令实现正常重启功能
1.前言 在9.0的系统rom定制化开发中,在定制recovery模块的时候,由于产品开发需要要求禁用recovery的相关功能,比如在通过adb命令的 adb reboot recovery的方式进入recovery也需要实现禁用,所以就需要了解相关进入recovery流程来禁用该功能 2.禁用adb reboot recovery命…...