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

                               

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构与算法】二叉树(Binary Tree)

相关推荐&#xff1a;堆&#xff08;Heap&#xff09; / 堆排序&#xff08;HeapSort&#xff09; / TopK 文章目录 1.树1.1 树相关概念1.2 举例树的应用 2. 二叉树2.1 二叉树分类2.2 特殊的二叉树2.3 二叉树的存储结构 3. 二叉树实现与热门问题 1.树 树是一种非线性的数据结构…

力扣:42. 接雨水 84.柱状图中最大的矩形(单调栈,双指针)

这两道题解题思路类似&#xff0c;一个是单调递增栈&#xff0c;一个是单调递减栈。本篇博客给出暴力&#xff0c;双指针和单调栈解法。 42. 接雨水 题目&#xff1a; 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后…

AMD64 linux 环境中,如何将main.go打包成不带 .exe 的可执行文件?

在终端中先进入main.go所在的文件夹&#xff0c;然后运行这三条命令即可 $env:GOOS"linux" $env:GOARCH"amd64" go build main.go 最终结果&#xff0c;成功出现不带 .exe 结尾的可执行包&#xff1a;

日本失去的三十年:去杠杆用了14年

去年以来&#xff0c;日股在日本央行转鹰预期、基本面改善和一系列监管新规的催化下高歌猛进&#xff0c;日经指数已经逼近90年代资产泡沫时期的高位。今年迄今累计上涨8.51%&#xff0c;领跑全球&#xff0c;“失落的三十年”似乎已经远去。 日本因何走向衰退&#xff1f;“失…

【C语言】位与移位操作符详解

目录 1.⼆进制和进制转换 ①十进制&#xff1a;生活中最常用 ②二进制&#xff1a;计算机中使用的&#xff0c;每个数字称为一个比特 ③八进制、十六进制也如上 ④二进制转十进制 ⑤十进制转二进制 ⑥二进制转八进制 ⑦二进制转十六进制 2.原码、反码、补码 3.移位操…

C++生成动态库给C#使用

在C中编写库文件供C#使用的过程可以分为以下几个步骤&#xff1a; 创建C项目并定义需要导出的函数或类。确保这些函数或类被正确地标记为extern "C"&#xff08;对于C语言&#xff09;或者__declspec(dllexport)&#xff08;对于Windows平台&#xff09;。 示例代码 …

SpringBoot实现即时通讯

SpringBoot实现即时通讯 功能简述 好友管理群组管理聊天模式&#xff1a;私聊、群聊消息类型&#xff1a;系统消息、文本、语音、图片、视频会话列表、发送消息、接收消息 核心代码 package com.qiangesoft.im.core;import com.alibaba.fastjson2.JSONObject; import com.q…

【已更新】2024美赛C题代码教学思路数据处理数学建模分析Momentum in Tennis

问题一完整的代码已给出&#xff0c;预计2号晚上或者3号凌晨全部给出。 代码逻辑如下&#xff1a; C题第一问要求我们开发一个模型&#xff0c;捕捉得分时的比赛流程&#xff0c;并将其应用于一场或多场比赛。你的模型应该确定哪名球员在比赛的特定时间表现得更好&#xff0c;…

AI-数学-高中-24-三角函数一般形式的各参数含义

原作者视频&#xff1a;三角函数】12三角函数一般形式的各参数含义&#xff08;易&#xff09;_哔哩哔哩_bilibili 1.函数中的A标识符&#xff1a;表示曲线中间平衡位置的振幅&#xff0c;值域为正负A&#xff1a;[-A,A]。 2.函数中的B标识符&#xff1a;决定曲线纵向上下平移…

Name or service not known问题解决和分析过程解析

目 录 一、问题描述 二、问题查处过程 &#xff08;一&#xff09;为何不能识别到bogon &#xff08;二&#xff09;为何会出现bogon &#xff08;三&#xff09;能不能更改bogon &#xff08;四&#xff09;能识别其他host的名字 三、问题分析 四、问题解决 …

git 克隆拉取代码出现私钥权限问题。

问题反馈&#xff1a; rootdd:~/android/boost-1.74-for-android-r20b# git clone https://github.com/liulilittle/boost-1.74-for-android-r20b.git Cloning into boost-1.74-for-android-r20b... WARNING: UNPROTECTED PRIVATE KEY FILE! Permissions 0777 for /root/…

C++多态,父类有virtual, 子类继承时, 会拷贝父类的虚函数表吗

在 C 中&#xff0c;在父类中声明的虚函数会在子类中被继承&#xff0c;并且子类中所生成的对象如果重写了父类中的虚函数&#xff0c;其虚函数表将被更新以指向重写后的函数地址。因此&#xff0c;子类不需要再次拷贝一份父类的虚函数表&#xff0c;可以直接继承父类的虚函数表…

基于 SpringBoot 和 Vue.js 的权限管理系统部署教程

大家后&#xff0c;我是 jonssonyan 在上一篇文章我介绍了我的新项目——基于 SpringBoot 和 Vue.js 的权限管理系统&#xff0c;本文主要介绍该系统的部署 部署教程 这里使用 Docker 进行部署&#xff0c;Docker 基于容器技术&#xff0c;它可以占用更少的资源&#xff0c;…

C#面:Error 和 Exception 的区别

在C#中&#xff0c;Error 和 Exception 是两个不同的概念。 Error&#xff08;错误&#xff09;&#xff1a; 是指在程序运行过程中发生的严重问题&#xff0c;它表示了一个不可恢复的错误&#xff0c;通常是由于系统级别的问题导致的。例如&#xff0c;内存溢出、栈溢出、死…

【Android】代码混淆简单介绍

1.代码混淆的目的 1.1增加代码的安全性和保护知识产权。当开发人员编写的代码被编译成可执行文件后&#xff0c;存在被反编译的风险。通过进行代码混淆&#xff0c;可以使得反编译后的代码难以理解和分析&#xff0c;从而增加攻击者逆向工程的难度。 1.2代码混淆通过对代码进…

C++进阶--C++11包装器

目录 一、function包装器1.1 function包装器的概念1.2 function包装器实例化&#xff08;统一类型&#xff09;1.4 function包装器的意义 二、bind包装器2.1 bind包装器的概念2.1.1 bind包装器2.1.2 调用bind的一般形式 2.2 bind包装器绑定固定参数2.2.1 无意义的绑定2.2.2 绑定…

Redis系列——Lua脚本和redis事务的应用

介绍 Lua脚本 背景 Redis是一种抽象数据类型的特定领域语言&#xff0c;由各种命令组成。大多数命令专门用于操作不通的数据类型。每次发送命令均需要执行至此网络请求。所以Redis提供了一个编程接口&#xff0c;支持服务器执行用户自定义的任意脚本。有助于减少网络流量&am…

linux文件的IO函数

open函数: 作用&#xff1a;打开或者新建一个文件 原型&#xff1a; int open(const char*pathname,int flags); int open(const char*pathname,int flags,mode_t mode); 参数&#xff1a; pathname:路径 flags:1-> O_RONLY 只读打开 2 -> O_WONLY只写打开 3->…

83 CTF夺旗-Python考点SSTI反序列化字符串

这里写目录标题 CTF各大题型简介演示案例:CTF夺旗-Python-支付逻辑&JWT&反序列化CTF夺旗-Python-Flask&jinja2&SSTl模版注入CTF夺旗-Python-格式化字符串漏洞&读取对象 涉及资源&#xff1a; 我们这篇文章主要讲的是CTF在web渗透测试方向的3个考点 CTF各大…

elementui常用组件-个人版(间断更新)

Dialog 对话框 el-dialog <el-dialogtitle"提示":visible.sync"dialogVisible"width"30%":before-close"handleClose"><span>这是一段信息</span><span slot"footer" class"dialog-footer"…