Netty第三部

继续Netty第二部的内容

一、ChannelHandler

1、ChannelHandler接口

ChannelHandler是Netty的主要组件,处理所有的入站和出站数据的应用程序逻辑的容器,可以应用在数据的格式转换、异常处理、数据报文统计等

继承ChannelHandler的两个子接口:

ChannelInboundHandler:处理入站数据以及各种状态变化

ChannelOutboundHandler:处理出站数据并且允许拦截所有的操作

2、ChannelInboundHandler接口

  1. channelRegistered当Channel已经注册到它的EventLoop并且能够处理I/O时被调用
  2. channelUnregistered当Channel从它的EventLoop注销并且无法处理任何I/O时被调用
  3. channelActive当Channel处于活动状态时被调用;Channel已经连接/绑定并且已经就绪
  4. channelInactive当 Channel离开活动状态并且不再连接它的远程节点时被调用
  5. channelReadComplete当Channel上的一个读操作完成时被调用
  6. channelRead 当从Channel读取数据时被调用
  7. channelWritabilityChanged当Channel的可写状态发生改变时被调用。可以通过调用Channel的 isWritable()方法来检测Channel的可写性。与可写性相关的阈值可以通过 Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置
  8. userEventTriggered当ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用。

3、ChannelOutboundHandler接口

出站操作和数据将由 ChannelOutboundHandler 处理。它的方法将被 Channel、ChannelPipeline 以及 ChannelHandlerContext 调用。

所有由 ChannelOutboundHandler 本身所定义的方法:

  1. bind(ChannelHandlerContext,SocketAddress,ChannelPromise) 当请求将 Channel 绑定到本地地址时被调用
  2. connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise) 当请求将 Channel 连接到远程节点时被调用
  3. disconnect(ChannelHandlerContext,ChannelPromise) 当请求将 Channel 从远程节点断开时被调用
  4. close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
  5. deregister(ChannelHandlerContext,ChannelPromise) 当请求将 Channel 从它的 EventLoop 注销时被调用
  6. read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用
  7. flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调用
  8. write(ChannelHandlerContext,Object,ChannelPromise) 当请求通过 Channel 将数据写到 远程节点时被调用

4、ChannelHandler的适配器

ChannelInboundHandlerAdapter(处理入站)和ChannelOutboundHandlerAdapter(处理出站)是Netty提供的ChannelHandler基类,降低了ChannelHandler实现的复杂度

为什么ChannelOutboundHandlerAdapter会有read()方法

ChannelOutboundHandler.read()是主动触发读事件的,或者是处理读事件的前置处理器;调用ChannelOutboundHandler.read()方法时Channel会向Selector读取数据,读取数据之后会交给ChannelPipeline,这时候ChannelPipeline就会触发ChannelInboundHandler.channelRead()读事件

如何在一个ChannelHandler实现同时实现入栈出战

继承ChannelDuplexHandler,也可以同时实现ChannelOutboundHandler, ChannelInboundHandler这两个接口

5、共享Handler

ChannelHandlerAdapter提供了isSharable()方法,如果实现ChannelHandler的类上指定了@Sharable注解,isSharable()方法就会返回true,反之则否;

每个SocketChannel都有自己的ChannelPipeline,同时SocketChannel会对应一个EventLoop,也就是说只会一个线程来处理,ChannelHandler实例之间是完全独立,只要不是共享了全局变量,ChannelHandler是线程安全的

如何通过共享Handler实现包统计

继承ChannelDuplexHandler类,同时类上加上@Sharable注解注解,指定该实例为static,保证只有一个,添加到ChannelPipeline中,重写read()和flush()方法,就可以具体发包收包记录了

6、资源管理和SimpleChannelInboundHandler

在NIO实现中需要依靠创建buffer来进行Channel之间的数据交换;

Netty也是同样的设计,在read网络数据时由Netty创建Buffer,write时会拿到这个Buffer写到网络中(outBoundHandler处理了write()操作并丢弃了数据,而read()需要继续往下一个Handler传递,所以没有相关处理)

可能产生内存泄露的情况:

  1. 没有调用相关的fireChannelRead方法,也不释放Buffer
  2. 执行channelRead方法抛出异常导致fireChannelRead方法未执行,同时也不释放Buffer

正常执行fireChannelRead方法入站往后传递Buffer,Netty都会释放Buffer,Netty提供了SimpleChannelInboundHandler类来支持这种情况,继承SimpleChannelInboundHandler类,重写channelRead0方法,即使不抛出异常,也不调用fireChannelRead方法,最终Netty也会帮我们做Buffer的释放

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {boolean release = true;try {if (acceptInboundMessage(msg)) {@SuppressWarnings("unchecked")I imsg = (I) msg;channelRead0(ctx, imsg);} else {release = false;ctx.fireChannelRead(msg);}} finally {if (autoRelease && release) {ReferenceCountUtil.release(msg);}}
}/*** Is called for each message of type {@link I}.** @param ctx           the {@link ChannelHandlerContext} which this {@link SimpleChannelInboundHandler}*                      belongs to* @param msg           the message to handle* @throws Exception    is thrown if an error occurred*/
protected abstract void channelRead0(ChannelHandlerContext ctx, I msg) throws Exception;

二、Netty内置通信传输模式

  1. NIO:io.netty.channel.socket.nio 使用java.nio.channels包作为基础——基于选择器的方式
  2. Epoll:io.netty.channel.epoll 由JNI驱动的epoll()和非阻塞 IO。这个传输支持只有在Linux上可用的多种特性,如SO_REUSEPORT,比NIO传输更快,而且是完全非阻塞的。将NioEventLoopGroup替换为EpollEventLoopGroup,并且将NioServerSocketChannel.class替换为EpollServerSocketChannel.class即可。
  3. OIO:io.netty.channel.socket.oio 使用java.net包作为基础——使用阻塞流
  4. Local:io.netty.channel.local 可以在VM内部通过管道进行通信的本地传输
  5. Embedded:io.netty.channel.embedded Embedded 传输,允许使用ChannelHandler而又不需要一个真正的基于网络的传输。在测试ChannelHandler实现时非常有用

三、引导Bootstrap

ServerBootstrap将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap则是由想要连接到远程节点的客户端应用程序所使用的。

引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootstrap则需要两个(也可以是同一个实例)

@Override
public ServerBootstrap group(EventLoopGroup group) {return group(group, group);
}/*** Set the {@link EventLoopGroup} for the parent (acceptor) and the child (client). These* {@link EventLoopGroup}'s are used to handle all the events and IO for {@link ServerChannel} and* {@link Channel}'s.*/
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {super.group(parentGroup);if (this.childGroup != null) {throw new IllegalStateException("childGroup set already");}this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");return this;
}

与ServerChannel相关联的EventLoopGroup将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup就会给它的Channel分配 一个EventLoop。

四、ChannelInitializer

ChannelInitializer是ChannelInboundHandlerAdapter的子类,当中的initChannel()方法提供了一种将多个ChannelHandler添加到一个ChannelPipeline中的简便方法。你只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。

/*** This method will be called once the {@link Channel} was registered. After the method returns this instance* will be removed from the {@link ChannelPipeline} of the {@link Channel}.** @param ch            the {@link Channel} which was registered.* @throws Exception    is thrown if an error occurs. In that case it will be handled by*                      {@link #exceptionCaught(ChannelHandlerContext, Throwable)} which will by default close*                      the {@link Channel}.*/
protected abstract void initChannel(C ch) throws Exception;@SuppressWarnings("unchecked")
private boolean initChannel(ChannelHandlerContext ctx) throws Exception {if (initMap.add(ctx)) { // Guard against re-entrance.try {initChannel((C) ctx.channel());} catch (Throwable cause) {// Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...).// We do so to prevent multiple calls to initChannel(...).exceptionCaught(ctx, cause);} finally {ChannelPipeline pipeline = ctx.pipeline();if (pipeline.context(this) != null) {pipeline.remove(this);}}return true;}return false;
}

五、ChannelOption

  1. ChannelOption.SO_BACKLOG:对应的是 tcp/ip 协议 listen 函数中的 backlog 参数,从Linux2.2开始,backlog的参数行为在Linux2.2中发生了变化,现在它指定等待接受的完全建立的套接字的队列长度,而不是不完整的连接请求的数量
  2. ChannelOption.SO_REUSEADDR:对应于套接字选项中的SO_REUSEADDR,这个参数表示允许重复使用本地地址和端口
  3. ChannelOption.SO_KEEPALIVE:对应于套接字选项中的 SO_KEEPALIVE,如果在两小时内没有数据的通信时,TCP会自动发送一 个活动探测数据报文,确定连接状态
  4. ChannelOption.SO_SNDBUF和ChannelOption.SO_RCVBUF:对应于套接字选项中的 SO_SNDBUF和 SO_RCVBUF,这两个参数用于操作接 收缓冲区和发送缓冲区的大小
  5. ChannelOption.SO_LINGER: 对应于套接字选项中的 SO_LINGER,保证TCP四次回收最后一次发送close()时数据传输完毕
  6. ChannelOption.TCP_NODELAY:对应于套接字选项中的TCP_NODELAY,该参数的使用与Nagle算法有关,Nagle算法是将小的数据包组装为更大的帧然后进行发送,而不是输入一次发送一次,因此在数据包不足的时候会等待其他数据的到了,组装成大的数据包进行发送,虽然该方式有效提高网络的有效负载,但是却造成了延时,而该参数的作用就是禁止使用Nagle算法,使用于小数据即时传输,于TCP_NODELAY相对应的是TCP_CORK,该选项是需要等到发送的数据量最大的时候,一次性发送数据,适用于文件传输。

六、ByteBuf

ByteBuf 维护了两个不同的索引,名称以 read 或者 write 开头的 ByteBuf 方法,将会 推进其对应的索引,而名称以 set 或者 get 开头的操作则不会。 如果打算读取字节直到 readerIndex 达到和 writerIndex 同样的值时会发生什么。在那 时,你将会到达“可以读取的”数据的末尾。就如同试图读取超出数组末尾的数据一样,试 图读取超出该点的数据将会触发一个 IndexOutOf-BoundsException。 可以指定 ByteBuf 的最大容量。试图移动写索引(即 writerIndex)超过这个值将会触发 一个异常。(默认的限制是 Integer.MAX_VALUE。)

七、粘包/半包问题

1、TCP粘包/半包发生的原因

分包产生的原因就简单的多:就是一个数据包被分成了多次接收。 更具体的原因至少包括:

  1. 应用程序写入数据的字节大小大于套接字发送缓冲区的大小
  2. 进行 MSS大小的TCP分段。MSS是最大报文段长度的缩写。MSS是TCP报文段中的 数据字段的最大长度。数据字段加上TCP首部才等于整个的TCP报文段。所以MSS并不是TCP报文段的最大长度,而是:MSS=TCP报文段长度-TCP首部长度。

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节 数是不确定的,故可能存在以下 4 种情况。

  1. 服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;
  2. 服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;
  3. 服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;
  4. 服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第 二次读取到了D1包的剩余内容D1_2和D2包的整包。

如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

2、解决TCP粘包/半包问题

由于底层的 TCP 无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重 组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案, 可以归纳如下。

  1. 在包尾增加分割符,比如回车换行符进行分割,例如 FTP 协议; 参见 cn.tuling.nettybasic.splicing.linebase(回车换行符进行分割)和 cn.tuling.nettybasic.splicing.delimiter(自定义分割符)下的代码
  2. 消息定长,例如每个报文的大小为固定长度 200 字节,如果不够,空位补空格; 参见 cn.tuling.nettybasic.splicing.fixed 下的代码
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度) 的字段,通常设计思路为消息头的第一个字段使用 int32 来表示消息的总长度,使用 LengthFieldBasedFrameDecoder

4、分析channelRead和channelReadComplete

  • Netty是在读到完整的业务请求报文后才调用一次业务ChannelHandler的channelRead方法
  • 如果一个业务消息被TCP协议栈发送了N次,则服务端的channelReadComplete方法就会被调用N次。

九、编码器和解码器

  • 将字节解码为消息——ByteToMessageDecoder
  • 将一种消息类型解码为另一种——MessageToMessageDecoder。

十、序列化问题

1、Java序列化的缺点

  1. 无法跨语言
  2. 序列化后的码流太大
  3. 序列化性能太低

2、序列化框架

具体可以参考:几种Java常用序列化框架的选型与对比-阿里云开发者社区

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

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

相关文章

049-第三代软件开发-软件部署脚本(一)

第三代软件开发-软件部署脚本(一) 文章目录 第三代软件开发-软件部署脚本(一)项目介绍软件部署脚本(一)其他方式 关键字: Qt、 Qml、 bash、 shell、 脚本 项目介绍 欢迎来到我们的 QML & C 项目!这个项目结合了 QML(Qt Meta-Object…

​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​

软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】 课本里章节里所有蓝色字体的思维导图

软文推广中如何搭建媒体矩阵

媒体矩阵简单理解就是在不同的媒体平台上,根据运营目标和需求,建立起全面系统的媒体布局,进行多平台同步运营。接下来媒介盒子就来和大家聊聊,企业在软文推广过程中为什么需要搭建媒体矩阵,又该如何搭建媒体矩阵。 一、…

统信UOS Linux操作系统下怎么删除某个程序在开始菜单或桌面的快捷方式

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 引言 统信操作系统的开始菜单包罗万象,将所有应用的快捷方式都放在了开始菜单内。 虽然提供了分类展示的能力,但无论是分类方式还是未分类方式,都不能像windows一样将这…

Java之SpringCloud Alibaba【八】【Spring Cloud微服务Gateway整合sentinel限流】

一、Gateway整合sentinel限流 网关作为内部系统外的一层屏障,对内起到-定的保护作用&#xff0c;限流便是其中之- - .网关层的限流可以简单地针对不同路由进行限流,也可针对业务的接口进行限流,或者根据接口的特征分组限流。 1、添加依赖 <dependency><groupId>c…

CSDN每日一题学习训练——Java版(克隆图、最接近的三数之和、求公式的值)

版本说明 当前版本号[20231109]。 版本修改说明20231109初版 目录 文章目录 版本说明目录克隆图题目解题思路代码思路参考代码 最接近的三数之和题目解题思路代码思路参考代码 求公式的值题目解题思路代码思路参考代码 克隆图 题目 给你无向 连通(https://baike.baidu.com…

Python算法:动态规划解决0-1背包问题

动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种在数学、计算机科学和经济学中使用的&#xff0c;通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题&#xff0c;它能够将问题…

Spark 读取ES采坑系列

目录 一、使用的插件 二、ES集群和Elasticsearch-hadoop版本问题 三、Elasticsearch-hadoop 和Scala版本以及Spark版本&#xff08;版本不匹配会有各种异常信息 一、使用的插件 <dependency><groupId>org.elasticsearch</groupId><artifactId>elas…

java入坑之类加载器

一、类加载机制 1.1类加载过程 类加载是Java虚拟机将类的字节码数据从磁盘或网络中读入内存&#xff0c;并转换成在JVM中可以被执行的Java类型的过程。类加载器是Java虚拟机的重要组成部分&#xff0c;负责加载和解析类的字节码&#xff0c;将其转换成Java虚拟机中的类对象&am…

nav2 调节纯追踪算法

纯追踪算法 纯追踪基础 The core idea is to find a point on the path in front of the robot and find the linear and angular velocity to help drive towards it. 核心思想是在机器人前方的路径上找到一个点&#xff0c;并找到一个合适的线速度和角速度&#xff0c;以驱…

[量化投资-学习笔记007]Python+TDengine从零开始搭建量化分析平台-布林带

布林带&#xff08;Bollinger Bands&#xff09;也称为布林通道、保力加通道&#xff0c;是由约翰布林格&#xff08;John Bollinger&#xff09;发明的技术分析指标。布林通道通常被用来确认资产价格波动范围。 布林通道是由三条平滑的曲线组成的趋势线图表&#xff0c;中线为…

leetcode刷题 - SQL - 中等

1. 176. 第二高的薪水 筛选出第二大 查询并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水&#xff0c;查询应该返回 null(Pandas 则返回 None) 。查询结果如下例所示。 666中等的第一题就上强度 强行解法 select max(salary) as SecondHighestSalary from Emp…

深入解析 Redis 分布式锁原理

一、实现原理 1.1 基本原理 JDK 原生的锁可以让不同线程之间以互斥的方式来访问共享资源&#xff0c;但如果想要在不同进程之间以互斥的方式来访问共享资源&#xff0c;JDK 原生的锁就无能为力了。此时可以使用 Redis 来实现分布式锁。 Redis 实现分布式锁的核心命令如下&am…

【Git】如何安装git,项目中使用git上传到远程仓库,使用git中对多人使用出现的版本问题的解决

前言&#xff1a; 一&#xff0c;Git的介绍&#xff0c;安装&#xff0c;与SVN的对比 1.1Git的介绍 Git 是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控…

ubuntu下Anaconda环境安装GPU的pytorch(docker镜像)

实验室需要给每个人分配docker的container环境&#xff0c;为了节省系统的空间&#xff0c;打算把anaconda和深度学习的开发环境配置好拉取镜像以省时间。 基础环境配置 apt更新了清华源 安装了基础环境 gcc vim Linux文本编辑库 openssh-server ssh远程连接库 net-tools 包含…

关于Android Studio中开发Flutter配置

配置系统环境变量&#xff1a;path下 &#xff0c;flutter的bin目录下 File->Settings->Languages&Frameworks->FlutterFile->Settings->Languages&Frameworks->DartFile->Settings->Languages&Frameworks->Android SDK 确认是…

接口开发之使用C#插件Quartz.Net定时执行CMD任务工具

C#制作定时任务工具执行CMD命令 概要准备知识点实现原理thinkphp配置winform执行CMD命令读取ini配置文件定时任务Quartz.Net 完整代码Job.csIniFunc.csForm1.csconfig.ini简易定时任务工具雏形 概要 很多时候写接口上线后还会遇到很多修改&#xff0c;类似JAVA,C#,delphi制作的…

面试字节测开岗失败后,被面试官在朋友圈吐槽了......(心累)

在和朋友吃夜宵的时候&#xff0c;朋友向我吐槽说自己在参加某大厂测试面试的时候被面试官怼得哑口无言&#xff0c;场面让他一度十分尴尬 印象最深的就是下面几个问题&#xff1a; 根据你以前的工作经验和学习到的测试技术&#xff0c;说说你对质量保证的理解&#xff1f; 非…

动态规划-构建乘积数组

** 描述 给定一个数组 A[0,1,…,n-1] ,请构建一个数组 B[0,1,…,n-1] ,其中 B 的元素 B[i]A[0]A[1]…*A[i-1]A[i1]…*A[n-1]&#xff08;除 A[i] 以外的全部元素的的乘积&#xff09;。程序中不能使用除法。&#xff08;注意&#xff1a;规定 B[0] A[1] * A[2] * … * A[n-1…

GeoGebra:数学动画制作工具重磅来袭

【线性代数】线性代数可视化工具&#xff1a;manim manim是之前我跟大家分享的一个线性代数动画制作工具。 但我之前的描述有些许偏差&#xff0c;这里要更正一下&#xff0c;manim不仅限于制作线性代数动画&#xff0c;也可以制作数学其他学科的动画&#xff0c;例如微积分&…