当前位置: 首页 > news >正文

Netty一文搞懂——核心原理篇<随手笔记>

在这里插入图片描述

1.server端工作原理

1.1.原理

server端启动时绑定本地某个端口,将自己NIOServerSocketChannel注册到某个bossNIOEventLoopselector上。

server端包含一个BossNIOEventGroup和一个WorkerNioEventaLoopGroupNioEventaLoopGroup相当于一个事件循环组,这个事件循环组里包含多个事件循环NioEventLoop,每个NioEventLoop包含一个selector和一个事件循环线程

每个BoosNioEventLoop循环执行的任务包含三步:

  1. 轮询read、write事件
  2. 处理I/O任务,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理
  3. 处理任务队列中的任务,runAllTasks

1.2.启动流程

public class NettyServer {public static void main(String[] args) {NioEventLoopGroup bossGroup = new NioEventLoopGroup();NioEventLoopGroup workerGroup = new NioEventLoopGroup();ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(boosGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {protected void initchannel(NioSocketChannel ch) {}});serverBootstrap.bind(8000);}
}
  1. 首先创建了两个NioEventLoopGroup,这两个对象可以看做是传统IO编程模型的两大线程组,bossGroup表示监听端口,accept新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组。
  2. 接下来创建了一个引导类ServerBootstrap,这个类将引导我们进服务端的启动工作,直接new出来。
  3. 通过.group(boosGroup, workerGroup)给引导类配置两大线程组,此引导类的线程模型也就定型了。
  4. 然后指定服务端的I/O模型为NIO,我们通过.channel(NioServerSocketChannel.class)来制定IO模型。
  5. 最后我们调用childHandler()方法给这个引导类创建一个channelInitialilzer,这里主要就是定义后续每条连接的数据读写、业务处理逻辑。channelInitiater这个类中的泛型参数NioSocketChannel是Netty对NIO类型的连接的抽象,而NioServerSocketChannel也是对NIO类型的连接的抽象,NioServerSocketChannel和NioSocketChannel的概念可以喝BIO编程模型中的serverSocket以及Socket两个概念对应上。

创建一个引导类,然后给它指定线程模型、IO模型、连续读写处理逻辑,绑定端口之后服务就启动起来了。

2.client端工作原理

2.1.原理

client端启动时connect到server建立NioSocketChannel,并注册到某个NioEventLoop的Selector上。

client端只包含一个NioEventLoopGroup,每个EventLoop循环执行的任务包含三步:

  1. 轮询connect、read、write事件
  2. 处理I/O任务,即connect、read、write事件,在NioSocketChannel上建立连接。可读、可写事件发生时进行处理
  3. 处理非I/O任务,runAllTasks

2.2.启动流程

@Slf4j
public class NettyClient {public static void main(String[] args) {NioEventLoopGroup workerGroup = new NioEventLoopGroup();Bootstrap bootstrap = new Bootstrap();bootstrap.group(workerGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {}});bootstrap.connect("ip", 80).addListener(future -> {if (future.isSuccess()) {log.info("connect success.");} else {log.info("connect fail.");}});}
}
  1. 首先与服务端的启动一样,需要给它指定线程模型,驱动着连接的数据读写
  2. 然后指定IO模型为NioSocketChannel,表示IO模型为NIO
  3. 接着给引导类指定一个handler,这里主要就是定义连接的业务处理逻辑
  4. 配置完线程模型、IO模型、业务处理逻辑之后,调用connect方法进行连接,可以看到connect方法有两个参数,第一个参数可以填写IP或者域名,第二个参数填写的是端口号,由于方法返回的是Future,所以我们通过addListener方法监听结果。

3.ByteBuf

是一个节点容器,里面包含三部分内容:

  1. 已经丢弃的数据,这部分数据是无效的
  2. 可读字节,这部分数据是ByteBuf的主体
  3. 可写字节

这三段数据被两个指针给划分出来:读指针、写指针

本质:

  • 引用一段内存,此内存可以是堆内也可以是堆外的,然后用引用计数来控制这段内存是否需要被释放,使用读写指针来控制对ByteBuf的读写,可以理解为外观模式的一种使用
  • 基于读写指针和容量、最大可扩容容量,衍生出一系列的读写方法,要注意read或write与get/set的区别
  • 多个ByteBuf可以引用计数来控制内存的释放,遵循谁retain()谁release()的原则

ByteBuf和ByteBuffer的区别:

  1. 可扩展到用户定义的buffer类型
  2. 通过内置的复合buffer类型实现透明的零拷贝(zero-copy)
  3. 容量可以根据需要扩展
  4. 切换读写 模式不需要调用ByteBuffer.flip()方法
  5. 读写采用不同的索引
  6. 支持方法链接调用
  7. 支持引用计数

4.池技术–线程池等

4.1.ByteBuf和设计模式

  1. ByteBufAllocator抽象工厂模式

    在Netty的世界里ByteBuf实例通常应该由ByteBufAllocator来创建

  2. CompositeByteBuf组合模式

    CompositeByteBuf可以让我们把多个ByteBuf当成一个Buf来处理,ByteBufAllocator提供了compositeBuffer()工厂方法来创建 compositeByteBuf。它的实现使用了组合模式。

  3. ReadOnlyByteBuf装饰器模式

    ReadOnlyByteBuf使用装饰器模式把一个ByteBuf变为只读,ReadOnlyByteBuf通过调用unpooled.unmodifiableBuffer(ByteBuf)方法获得。
    ReadOnlyByteBuf->Ab

  4. ByteBufInputStream适配器模式

    ByteBufInputStream使用适配器模式使我们可以把ByteBuf当做Java的InputStream来使用,同理,ByteBufOutputStream允许我们把ByteBuf当做PutputStream来使用。

  5. ByteBuf工厂方法模式

    一般不直接通过构造函数来创建ByteBuf实例,而是通过Allocator来创建。从装饰器模式可以看出另一种获得ByteBuf的方式是调用ByteBuf的工厂方法,如:

    • ByteBuf # duplicate()
    • ByteBuf # slice()

4.2.channelHandler

channelHandler在只会对感兴趣的时间进行拦截处理,servlet的Filter过滤器负责对IO事件或者IO操作进行拦截和处理,他可以选择性地拦截和处理自己感兴趣的事件,也可以透传和终止事件的传递。pipeline和channelHandler他们通过责任链接设计模式来组织代码逻辑,并且支持逻辑的动态添加和删除。

channelHandler有两大子接口:

  • channelInBoundhandler,是处理读数据的逻辑
  • channelOutBoundHandler,是处理写数据的逻辑
    这两个接口分别对应的默认实现,ChannelInBoundHandlerAdapter和ChannelOutBoundHandlerAdapter他们分别实现了两大接口的所有功能,默认情况下会把读写事件传播到下一个handler。

事件的传播:
abstractChannel直接调用了pipeline的write()方法,因为write是个output事件,所以DefaultChannelPipeline直接找到tail部分的context调用其write()方法。

5.NioEventLoop

NioEventLoop除了处理Io事件还有主要:

  1. 非IO操作的系统Task
  2. 定时任务

非IO操作和IO操作各占默认值50%,底层使用Selector(多路复用器)

Selector BUG出现的原因:selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,cpu使用率100%
Netty的解决方法:

  • selectorselect操作周期进行统计,每完成一次空selector操作进行一次计数
  • 若咋某个周期内连接发生N次空轮询,则触发epoll死循环bug
  • 重建selector,判断是否是其他线程发起的重建请求,若不是则将原socketChannel从旧的selector上去除注册,重新注册到新的selector上,并将原来的selector关闭

6.Netty内存池和对象池

6.1.基本概念

内存池: 指为了实现内存池的功能,设计一个内存结构chunk,其内部管理者一个大块的连接内存区域,将这个内存区域切分均等的大小,每一个大小称之为一个page。将从内存池中申请内存的动作映射为从chunk中申请一定数量page。为了方便计算和申请page,chunk内部采用完全二叉树的方式对page进行管理。

对象池: 指recycler整个对象池的核心实现由ThreadLocal和stack及wrakOrderQueue构成,接着来看stack和wrakOrderQueue的具体实现,最后概括整体实现。

6.2.设计核心

  1. stack相当于是一个缓存,同一个线程内的使用和回收都将使用一个stack
  2. 每个线程都会有一个自己对应的stack,如果回收的线程不是stack的线程,将元素放入到Queue中
  3. 所有的Queue组合成一个链表,stack可以从这些链表中回收元素(实现了多线程之间共享回收的实例)

7.心跳和空闲检测

连接假死:

在某一端(服务端或者客户端)看来,底层的TCP连接已经断开了,但是应用程序并没有捕获到,因此会认为这条连接仍然是存在的,从TCP层面来说,只有收到四次握手数据包或者一个RST数据包,连接的状态才表示已断开。

导致的问题:

  1. 对于服务端每条连接都耗费CPU和内存资源,大量假死的连接会耗光服务器的资源
  2. 对于客户端,假死会造成发送数据超时,影响用户体验

链接假死的原因:

  1. 应用线程出现线程堵塞,无法进行数据的读写
  2. 客户端或服务端出现网络相关的故障
  3. 公网丢包

7.1.服务端空闲检测

如果能一直收到客户端发来的数据,则此条连接是活的,因此服务端对于连接假死的应对策略是空闲检测;

简化一下,服务端只需检测一段时间内是否收到客户端发来的数据即可,Netty自带的IdleStateHandler就实现了此功能。
IdleStateHandler构造器有四个参数:

  1. 表示读空闲时间:指的是在这段时间内没有数据读到,则连接假死
  2. 写空闲时间:指的是在这段时间内没有数据写到,则连接假死
  3. 读写空闲时间:如没有产生读或写,则连接假死
  4. 时间单位

7.2.客户端定时心跳

服务端在一段时间内没有收到客户端的数据有两种情况:连接假死;非假死的确没有数据;所以我们要排出第二种情况的假死,定期向服务端发送心跳。

实现了每隔五秒向服务端发送一个心跳数据包,这个时间段通常要比服务端的空间检测时间的一半要短一些,我们这直接定义为空闲检测时间的三分之一,主要是为了排除公网偶发的秒级抖动。

为了排除是否是因为服务端在非假死状态下确实没有发送数据,服务端也要定期发送心跳检测到客户端。

8.拆包粘包理论与解决

TCP是个“流协议”,所谓流就是灭幼界限的一串数据。TCP底层并不了解上层业务数据的具体含义,他会根据TCP缓冲区的实际情况进行包的拆分,所以在业务上认为一个完整的包可能被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包的问题。

解决方法:

  1. 封装自己的包协议:包=包内容(4byte)+包内容
  2. 对于粘包问题先读出包头即包体长度n,然后再读取长度为n的包内容,这样数据包之间的边界就清楚了
  3. 对于断包问题先读出包头即包体长度n,由于此次读取的缓冲区长度小于n,这时候就需要先缓存这部分的内容,等待下次read事件来时拼接起来形成完整的数据包。

8.1.Netty自带的拆包器

  1. 固定长度的拆包器FixedlengthFrameDecoder

    如果应用层协议非常简单,每个数据包的长度都是固定的,比如100,那么只需要把这个拆包器加到pipeline中,netty会把一个长度为100的数据包(ByteBuf)传递到下一个channelhandler.

  2. 分隔符拆包器DelimiterBasedFrameDecode

    从字面意思来看,发送断发送数据包的时候每个数据包之间以换行符作为分隔,接收端通过DelimiterBasedFrameDecode将粘过的ByteBuf拆分成一个完整的应用层数据包。

  3. 行拆包器LinebasedFrameDecode

    LinebasedFrameDecode是行拆包器的通用版本,只不过我们可以自定义分隔符

  4. 基于长度域拆包器lengthFieldbasedFrameDecoder

    这种拆包器是最通用的一种拆包器,只要你的自定义协议中包含长度域字段,均可以使用这个拆包器来实现应用层拆包

相关文章:

  • flink 配置表
  • buuctf-web
  • 一图了解网络通信原理
  • 在linux中查找 / 目录下的以.jar结尾的文件(find / -name *.jar)
  • qt 创建一个左侧边线拖拽的矩形
  • 主机安全-开源HIDS字节跳动Elkeid安装使用
  • pgsql(guass)可获取到对应的表名称、字段名称、注释、字段类型
  • OrangePi AIpro 浅上手
  • MATLAB实现一个车辆悬架PID模拟系统
  • Django相关的基本操作
  • Pixi.js技术探索:开发者必备的视觉开发工具
  • MySQL手机号发送验证码设计与应用
  • Pandas数据可视化宝典:解锁图形绘制与样式自定义的奥秘
  • vscode使用记录
  • PXE、Kickstart和cobbler
  • hexo+github搭建个人博客
  • [ 一起学React系列 -- 8 ] React中的文件上传
  • Brief introduction of how to 'Call, Apply and Bind'
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • ES6--对象的扩展
  • FineReport中如何实现自动滚屏效果
  • HTML-表单
  • HTTP那些事
  • JavaScript异步流程控制的前世今生
  • LeetCode18.四数之和 JavaScript
  • Protobuf3语言指南
  • Redux 中间件分析
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • webgl (原生)基础入门指南【一】
  • 分布式任务队列Celery
  • 搞机器学习要哪些技能
  • 记录一下第一次使用npm
  • 解决iview多表头动态更改列元素发生的错误
  • 理清楚Vue的结构
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 深入浏览器事件循环的本质
  • 使用 @font-face
  • 事件委托的小应用
  • ​埃文科技受邀出席2024 “数据要素×”生态大会​
  • (1)Map集合 (2)异常机制 (3)File类 (4)I/O流
  • (ctrl.obj) : error LNK2038: 检测到“RuntimeLibrary”的不匹配项: 值“MDd_DynamicDebug”不匹配值“
  • (iPhone/iPad开发)在UIWebView中自定义菜单栏
  • (仿QQ聊天消息列表加载)wp7 listbox 列表项逐一加载的一种实现方式,以及加入渐显动画...
  • (免费领源码)python#django#mysql校园校园宿舍管理系统84831-计算机毕业设计项目选题推荐
  • (三)elasticsearch 源码之启动流程分析
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)fock函数详解
  • (转)大型网站的系统架构
  • (转)为C# Windows服务添加安装程序
  • (自用)网络编程
  • .Net MVC + EF搭建学生管理系统
  • .net on S60 ---- Net60 1.1发布 支持VS2008以及新的特性