# msb-lijin-netty **Repository Path**: inspiration666/msb-lijin-netty ## Basic Information - **Project Name**: msb-lijin-netty - **Description**: 马士兵学院-李瑾-netty学习 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2022-08-13 - **Last Updated**: 2025-12-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # netty源码学习 ## 一、使用BIO(Socket)通信 ### 1.socket知识点 - 短连接 - 长连接 ## 二、使用BIO做一个rpc(远程过程调用) ### 1.rpc开发 - 使用代理 - 使用反射 > 小知识: > > 1.什么是springcloud? > > - Spring Cloud就是微服务系统架构的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶 > - 在平时我们构建微服务的过程中需要做如服务发现注册、配置中心、负载均衡、断路器、数据监控等操作, > 而Spring Cloud 为我们提供了一套简易的编程模型,使我们能在 Spring Boot 的基础上轻松地实现微服务项目的构建 > > 2.springCloud五大核心组件是什么? > > - 服务发现——Netflix Eureka客服端 > > - 负载均衡——Netflix Ribbon > > - 断路器——Netflix Hystrix > > - 服务网关——Netflix Zuul > > - 分布式配置——Spring Cloud Config > > 3.SpringBoot和SpringCloud有啥关系? > > 答: SpringBoot专注于快速、方便的开发单个微服务个体,SpringCloud关注全局的服务治理框架。 > > //区别:SpringBoot可以离开SpringCloud独立使用开发项目,但是SpringCloud离不开SpringBoot,属于依赖的关系。 > > 4.什么是分布式? > > - 将各个组件分开部署,某个组件占一个服务器,互相独立,互相调用,可以将组件的功能发挥强大 > - 一个业务分拆多个子业务,部署在不同的服务器上(不同的服务器,运行不同的代码,为了同一个目的) > 优点: > - 模块之间独立,各做各的事,便于扩展,复用性高 > - 高吞吐量。某个任务需要一个机器运行20个小时,将该任务用10台机器的分布式跑(将这个任务拆分成10个小任务),可能2个小时就跑完了 > > 5.什么是集群? > > 答:同一个业务,部署在多个服务器上(不同的服务器运行同样的代码,干同一件事)。 > 优点: > > - 通过多台计算机完成同一个工作,达到更高的效率。 > - 两机或多机内容、工作过程等完全一样。如果一台死机,另一台可以起作用。 参考链接: [SpringCloud是什么?](https://blog.csdn.net/weixin_47555380/article/details/108310860) ## 三、NIO研究 ### 1.区分BIO、NIO、AIO - BIO(阻塞IO): 服务器实现模式为一个连接一个线程,**即客户端有连接请求时服务器端就需要启动一个线程进行处理** - BIO(非阻塞IO):NIO使一个线程从某通道发送或者读取数据,但是它仅能得到目前可用的数据,如果目前没有可用的数据时,就什么都不会获取,而不是保持线程阻塞,所以直至数据变的可读取之前,该线程可以继续做其他事情。非阻塞就是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情 > 通俗来讲:NIO是可以做到用一个线程处理多个操作的。假设有10000个请求过来,根据实际情况,可以分配50或100个线程来处理。不想BIO一样需要分配10000个线程来处理 > NIO基本模型: > > ![1660397454518](site/images/1660397454518.png) > > 其中Selector(选择器)的作用是循环监听多个客户端连接通道,如果通道中没有数据即客户端没有请求时它可以去处理别的通道或者做其他的事情,如果通道中有数据他就会选择这个通道然后进行处理,这就做到了一个线程处理多个连接。 > **BIO和NIO的区别:** > > - BIO以流的方式处理数据,NIO以块的方式处理数据,块IO的效率比流IO高很多。(比如说流IO他是一个流,你必须时刻去接着他,不然一些流就会丢失造成数据丢失,所以处理这个请求的线程就阻塞了他无法去处理别的请求,他必须时刻盯着这个请求防止数据丢失。而块IO就不一样了,线程可以等他的数据全部写入到缓冲区中形成一个数据块然后再去处理他,在这期间该线程可以去处理其他请求) > - BIO是阻塞的,NIO是非阻塞的 > - BIO基于字节流和字符流进行操作的,而NIO基于Channel(通道)和Buffer(缓冲区)进行操作的,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择器)用于监听多个通道事件,因此使用单个线程就可以监听多个客户端通道 参考链接: [BIO和NIO的区别](https://blog.csdn.net/weixin_48872249/article/details/113845526) - AIO(异步IO): 读取数据之后,再通知我 (回调) > **异步的体现:** > > 同步的I/O流 服务器端不会"主动"联系客户端 ,但是AIO在数据准备完成后,会主动通知”客户端“ > 异步的I/O是交给OS处理,而异步应用自己会处理。 > > **AIO与NIO的区别:** > AIO与NIO都是通过管道的方式进行I/O操作,但是AIO每一个处理器与通道都是独立的,NIO是通过选择器进行匹配。 参考链接: [NIO和AIO的区别](https://blog.csdn.net/weixin_40990818/article/details/93524259) ### 2.NIO三大核心组件 - Selector - Channel - buffer缓冲区 ### 3.JDK的NIO实战 ![1660398014936](site/images/1660398014936.png) ### 4.什么是 Reactor **Reactor 是一种开发模式,模式的核心流程:** 注册感兴趣的事件 ->扫描是否有感兴趣的事件发生 -> 事件发生后做出相应的处理 ![1660398109510](site/images/1660398109510.png) **Reactor三种版本:** - 单线程 - 多线程 - 主从多线程 ### 5.单线程Reactor模式 ![1660398209746](site/images/1660398209746.png) ### 6.多线程Reactor模式 ![1660398216743](site/images/1660398216743.png) ### 7.主从多线程Reactor模式 ![1660398222256](site/images/1660398222256.png) ### 8.堆内存和堆外内存 > NIO中,堆内存分配内存快,直接内存(堆外内存)读写操作快 > 什么是堆外内存? > > 显然,看名字就知道堆外内存与堆内内存是相对应的:**Java 虚拟机管理堆之外的内存,称为非堆内存,即堆外内存。** > > 换句话说:堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是**能够在一定程度上减少垃圾回收对应用程序造成的影响。** > > 参考链接: > > [JVM——堆外内存详解](https://blog.csdn.net/mlz_2/article/details/120769856) ### 9.对`java.nio.ByteBuffer`的操作 ![1660398548385](site/images/1660398548385.png) ## 四、Netty入门 ### 1.netty知识 - netty最新稳定版:4.1 - netty业务分类: - netty发送字符串等属于无规范的 - netty支持发送http协议,见类`io.netty.handler.codec.http.HttpServerExpectContinueHandler` - netty支持自定义协议 - netty是一个NIO框架 - netty是基于jdk的原生NIO的改进 ### 2.netty应用——Netty实现Http ### 3.netty应用——Netty实现UDP单播和广播 > UDP概念: Internet协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。 > > UDP特征: > > - 面向无连接的通讯协议。 > - 通讯时不需要接收方确认,属于不可靠的传输。 > - 因为不需要建立连接,所以传输速度快,但是容易丢失数据。 > - UDP没有所谓的应答 > - 没有server端、client端概念 > - 响应端接收到udp消息,会解析`源端口`号,将回答发送回去 > > ![1660444305272](site/images/1660444305272.png) #### I.单播模式 > 定义为发送消息给一个由唯一的地址所标识的单一的网络目的地。 使用`io.netty.channel.socket.nio.NioDatagramChannel`来创建 #### II.广播模式 > 传输到网络(或者子网)上的所有主机。 - ip地址是255.255.255.255,表示在当前局域网进行广播(区别于单播需要指定ip地址) - 慎用广播:其一:有些服务器会把广播禁掉;其二:占用网络带宽,导致服务器通信性能下降,可考虑使用组播(组播可以说介于单播和广播之间) > idea如何开启平行运行? > > ![1660448809668](site/images/1660448809668.png) ### 3.Netty应用——Netty实现WebSocket通信 > webSocket:http的升级,可实现群聊 > > ![1660455355121](site/images/1660455355121.png) ## 五、Netty源码——底层原理1 - 一个EventLoop代表一个线程 - netty支持跨平台性(用到jdk的sun包) - 取余可以使用2^n-1 & num - 解决TCP拆包粘包的四种方式 > ![1660827349005](site/images/1660827349005.png) - TCP是建立连接后可以一直发,UDP是建立连接后收到消息后就关闭,UDP不会出现拆包粘包问题 ## 六、Netty源码——底层原理2 ### 1.解码器 - 一次解码器:解决半包粘包问题的常用三种解码器 > ![1661047115711](site/images/1661047115711.png) > > 代码层面解释:io.netty.buffer.ByteBuf (原始数据流) > io.netty.buffer.ByteBuf (用户数据) - 二次解码器:需要和项目中所使用的对象做转化的解码器 > 1.常用的对象转换工具,如: > > - Java序列化 > - Marshaling > - XML > - JSON > - MessagePack > - Protobuf > - 其他 > > 2.转换后的size比较: > > ![1661046743839](site/images/1661046743839.png) > > 3.最高效的对象转换工具——msgpack > > [msgpack官网](https://msgpack.org) > > [msgpack案例](https://github.com/msgpack/msgpack-java/blob/develop/msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java) > > 4.protobuf > > [github官网-Releases](https://github.com/protocolbuffers/protobuf/releases) > > [github官网-v21.2](https://github.com/protocolbuffers/protobuf/releases/tag/v21.2) > > 5.二次解码器的父类是? > > 答:`io.netty.handler.codec.MessageToMessageDecoder` > > 另外,二次编码器的父类是`io.netty.handler.codec.MessageToMessageEncoder` - 一次解码和二次解码为何不合二为一? > 答:为了实现分层:方便组合使用、方便拓展;一次解码是为了解决粘包半包问题,二次解码主要是针对用户的编程习惯做编解码,从而配合来使用一次、二次编解码 ### 2.Netty零拷贝技术 > 1.零拷贝技术 > > 伪代码说明: > > ```java > buffer=File.read();//从文件中读数据 > Socket.send(buffer);//把数据发送到网络 > ``` > > - linux版本2.1以上,支持sendfile(发送文件描述符)零拷贝技术 > > ![1661051553155](site/images/1661051553155.png) > > 2.名词解释 > > - CPU拷贝:CPU需要上下文切换,所以拷贝效率低 > - DMA(direct memory access)拷贝:在内核发生,使用DMA控制器 > > 3.效率比较 > > 零拷贝传输耗时大约是传统数据传输耗时的1/10 > > 4.零拷贝技术的应用 > > - Netty > - Kafka > - RocketMQ(mmap技术,多一次拷贝) > - Nginx > - Apache服务器 > - 其他 ### 3.Netty中锁机制 - 减少锁的粒度 > 尽量使用对象锁 - 减少锁对象的空间占用 > `io.netty.channel.ChannelOutboundBuffer` > > ```java > //使用原子操作类确保多线程安全 > private static final AtomicLongFieldUpdater TOTAL_PENDING_SIZE_UPDATER = > AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize"); > > //统计待发送的字节数(为什么不直接使用原子操作类:AtomicLong) > private volatile long totalPendingSize; > ``` - 提高锁的性能 > `io.netty.util.internal.PlatformDependent#newLongCounter` > > ```java > public static LongCounter newLongCounter() { > if (javaVersion() >= 8) { > return new LongAdderCounter();//1.8使用LongAdder,性能更高 > } else { > return new AtomicLongCounter(); > } > } > ``` > > Atomic类和LongAdder均基于CAS > > ![1661079823729](site/images/1661079823729.png) > > Atomic和LongAdder的比较 > > [案例地址](com.msb.netty.longadder.LongAdderTest) - 根据不同的业务场景选择适合的锁 > `io.netty.util.concurrent.SingleThreadEventExecutor` > > ```java > private final CountDownLatch threadLock = new CountDownLatch(1); > ``` - 能不用锁则不用锁 > `io.netty.util.Recycler` > > ```java > private final FastThreadLocal> threadLocal = new FastThreadLocal>() { > @Override > protected Stack initialValue() { > return new Stack(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor, > ratioMask, maxDelayedQueuesPerThread); > } > > @Override > protected void onRemoval(Stack value) { > // Let us remove the WeakOrderQueue from the WeakHashMap directly if its safe to remove some overhead > if (value.threadRef.get() == Thread.currentThread()) { > if (DELAYED_RECYCLED.isSet()) { > DELAYED_RECYCLED.get().remove(value); > } > } > } > }; > ``` > > 使用`ThreadLocal`需要避免内存泄漏 ## 七、Netty源码——请求处理流程1 ### 1.不同OS中NIO叫法 - linux中叫Epoll - macOS中叫KQueue - windows中叫Nio ![1661262736518](site/images/1661262736518.png) ### 2.判断虚拟机版本 ![1661265161247](site/images/1661265161247.png) ### 3.Netty源码之启动服务 - 主线程 - 创建Selector - 创建ServerSocketChannel - 初始化ServerSocketChannel - 给ServerSocketChannel 从 boss group 中选择一个 NioEventLoop - BossThread - 将ServerSocketChannel 注册到选择的 NioEventLoop的Selector - 绑定地址启动 - 注册接受连接事件(OP_ACCEPT)到Selector 上 > 参考“Netty源码之请求处理流程.md” ### 4.Netty的责任链模式 [案例地址](https://gitee.com/inspiration666/msb-lijin-netty/blob/master/netty-basic/src/main/java/com/msb/netty/pipeline/Pipeline.java) 1)责任链模式优点 - 解耦 - 方便编程——配置灵活(如只需要addLast) 2)Netty责任链模式解析 - 添加处理类的接口——`io.netty.channel.ChannelHandler` > 有两个方法,一个是`handlerAdded`,添加处理类;一个是`handlerRemoved`,移除处理类 - netty的责任处理器接口——这两个接口都继承自`ChannelHandler` - 入站处理器`ChannelInboundHandler` - 出站处理器`ChannelOutboundHandler` > ![1661651517887](site/images/1661651517887.png) - netty添加处理类(addLast解释) - 顶级接口`io.netty.channel.ChannelPipeline`,接口方法具体实现在`io.netty.channel.DefaultChannelPipeline`(双向链表头结点和尾结点实现处) - netty的责任链模式是双向链表 - netty责任链模式之上下文 - 接口`io.netty.channel.ChannelHandlerContext` - 实现类`io.netty.channel.DefaultChannelHandlerContext` > ![1661652194034](site/images/1661652194034.png) - 责任终止机制 - 在pipeline中的任意一个节点,只要我们不手动的往下传播下去,这个事件就会终止传播在当前节点 - 对于入站数据,默认会传递到尾节点,进行回收,如果我们不进行下一步传播,事件就会终止在当前节点,别忘记回收msg - 对于出站数据,用header节点的使用unsafe对象,把数据写回客户端也意味着事件的终止 ## 八、Netty源码——请求处理流程2 ### 1.Netty构建连接 ![1662259494795](site/images/1662259494795.png) ### 2.Netty读数据 ![1662259511059](site/images/1662259511059.png) ### 3.Netty业务处理 ![1662259562124](site/images/1662259562124.png) ### 4.Netty写数据 ![1662259572856](site/images/1662259572856.png) > ```cmd > inspiration666 <2446526038@qq.com> > ```