| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 网络协议 -> netty -> 正文阅读 |
|
[网络协议]netty |
如何理解nettyNetty 是一个异步的、基于事件驱动的网络应用框架Netty
事件驱动的Reactor模型,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式 异步:Netty中的I/O操作是异步的,包括bind、write、connect等操作会简单的返回一个ChannelFuture,调用者并不能立刻获得结果,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。 netty
为什么使用Netty为什么不用NIO呢? NIO的类库和API繁杂,学习成本高,要熟悉多线程对开发者要求高 epollbug会导致空轮询 fd是一个表示连接的文件描述符,这个连接已经关闭,但是在epoll里面还存在,导致epollwait方法一直触发,因为epollwait会等待io事件的触发,然后selector被唤醒一直轮询多个通道但是都没有消息处理最终导致CPU百分之百 Netty的解决策略: \1) 设定一个轮询时间来记录,如果小于这个轮询时间并且连续发生512次次空轮询就认为发生了epollbug \2) 将问题Selector上注册的Channel转移到新建的Selector上; \3) 老的问题Selector关闭,使用新建的Selector替换。
Reactor 模式就是一个或者多个输入同时传递给服务处理器,然后服务处理器又将他们分派给对应线程处理,就是监听事件收到后分发,IO多路复用就是这种思想 Reactor 模式主要由 Reactor 和Acceptor和handler几个核心部分组成,它俩负责的事情如下: Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件
根据Reactor的数量和处理资源池线程的数量不同,有3种典型的实现:
还有一种proactor 是异步网络模式, 感知的是已完成的读写事件。在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,而Reactor是应用进程来做的。所以其他IO接受数据的时候都需要从内核态拷贝到用户态,但是异步IO是唯一不需要的,但是linux对异步IO不完善 Netty 核心组件有哪些?分别有什么作用?Bootstrap、ServerBootstrap 事件循环组 EventLoopGroup当客户端通过 connect 方法连接服务端时,bossGroup 处理客户端连接请求。当客户端处理完成后,会将这个连接提交给 workerGroup 来处理,然后 workerGroup 负责处理其 IO 相关操作。 EventLoopGroup 里面又 EventLoop,Channel 一般会调用 EventLoopGroup 的 register 方法来绑定其中一个 EventLoop,后续这个 Channel 上的 io 事件都由此 EventLoop 来处理(保证了 io 事件处理时的线程安全),类似一个线程池,内部维护了一组eventloop。 这个group默认构造2n线程数,如果不是2 这里group为了追求效率 Group里面调取eventloop是通过轮询机制实现,如果线程数也就是eventloop不是2的n次方采用取模,是2的n次方就采用逻辑与运算达到和取模一样的效果并且效率更高 EventLoop每个 EventLoop 线程都维护一个 Selector 选择器和任务队列 taskQueue。它主要负责处理 I/O 事件、普通任务和定时任务。NioServerSocketChannel会主动注册到某一个NioEventLoop的Selector上,NioEventLoop负责事件轮询。 每个线程(NioEventLoop)负责处理多个Channel上的事件,而一个Channel只对应于一个eventloop。 并且NioEventLoop 通过核心方法 select() 不断轮询注册的 I/O 事件。当没有 I/O 事件产生时,为了避免 NioEventLoop 线程一直循环空转,在获取 I/O 事件或者异步任务时需要阻塞线程,等待 I/O 事件就绪或者异步任务产生后才唤醒线程。
Selector& 可以用作逻辑与的运算符,表示逻辑与(and) 监听事件,管理注册到Selector中的channel,实现多路复用器, 早期这个操作系统调用的名字是select,但是性能低下,后来渐渐演化成了Linux下的epoll Channelchannel和pipeline区别? 把channel理解为目的地,你要向目的地发送消息的话,会经过管道pipeline ,这个管道上有重重阻碍,就是handler,channelpipeline,类似于一个中间件,在中间帮你进行拦截和过滤 一种连接到网络套接字或能进行读、写、连接和绑定等I/O操作的组件。 channel为用户提供:
通道类型: NioServerSocketChannel: 异步非阻塞的服务器端 TCP Socket 连接。 OioSocketChannel: 同步阻塞的客户端 TCP Socket 连接。 常用的就是这两个通道类型,因为是异步非阻塞的。所以是首选。 它除了包括基本的 I/O 操作,如 bind()、connect()、read()、write() 等。 最主要的是它内部的pipeline和handler,才是通信的关键 ChannelHandler 和 ChannelPipeline总结: 一 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式 pipeline相当于handler的容器。初始化channel时,把channelHandler按顺序装在pipeline中,就可以实现按序执行channelHandler了。ChannelPipeline并不是直接管理ChannelHandler,而是通过ChannelHandlerContext来间接管理 pipeline是结构是一个带有head与tail指针的双向链表,其中的节点为handlercontext,handlercontext关联了一个handler,每个handler将当前handler的处理结果传递给下一个handler 我们可以在 ChannelPipeline 上通过 addLast() 方法添加一个或者多个ChannelHandler ,因为一个数据或者事件可能会被多个 Handler 处理。当一个 ChannelHandler 处理完之后就将数据交给下一个 ChannelHandler 。
ChannelHandler 是消息的具体处理器。他负责处理读写操作、客户端连接等事情。 ChannelHandlerContext用于在Handler中获取pipeline对象,或者channel对象,进行读写等操作 保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象 ChannelFutureNetty 是异步非阻塞的,所有的 I/O 操作都为异步的。 因此,我们不能立刻得到操作是否执行成功,但是,你可以通过 ChannelFuture 接口的 addListener() 方法注册一个 ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。 通过 并且,你还可以通过ChannelFuture 的 channel() 方法获取关联的Channel writeAndFlush
Netty 服务端和客户端的启动过程了解么?首先你创建了两个 NioEventLoopGroup 对象实例:bossGroup 和 workerGroup。
一般情况下我们会指定 bossGroup 的 线程数为 1(并发连接量不大的时候) ,workGroup 的线程数量为 CPU 核心数 *2 ,如果不指定默认的话就是2n 接下来 我们创建了一个服务端启动引导/辅助类:ServerBootstrap,这个类将引导我们进行服务端的启动工作。 通过 .group() 方法给引导类 ServerBootstrap 配置两大线程组,确定了线程模型。 通过channel()方法给引导类 ServerBootstrap指定了 IO 模型为NIO 通过 .childHandler()给引导类创建一个ChannelInitializer ,然后制定了服务端消息的业务处理逻辑 HelloServerHandler 对象 调用 ServerBootstrap 类的 bind()方法绑定端口 客户端: 1.创建一个 NioEventLoopGroup 对象实例 2.创建客户端启动的引导类是 Bootstrap 3.通过 .group() 方法给引导类 Bootstrap 配置一个线程组 4.通过channel()方法给引导类 Bootstrap指定了 IO 模型为NIO 5.通过 .childHandler()给引导类创建一个ChannelInitializer ,然后制定了客户端消息的业务处理逻辑 HelloClientHandler 对象 6.调用 Bootstrap 类的 connect()方法进行连接,这个方法需要指定两个参数:
ByteBufByteBuf实现了两个接口,分别是ReferenceCounted和Comparable。Comparable是JDK自带的接口,表示该类之间是可以进行比较的。而ReferenceCounted表示的是对象的引用统计。当一个ReferenceCounted被实例化之后,其引用count=1,每次调用retain() 方法,就会增加count,调用release() 方法又会减少count。当count减为0之后,对象将会被释放,如果试图访问被释放过后的对象,则会报访问异常。ByteBuf是一个可以比较的,可以计算引用次数的对象。他提供了序列或者随机的byte访问机制。 传统IO java语言本身不具备磁盘读写能力,要调用磁盘就要从用户态切换到内核态调用操作系统的本地方法,然后读取到系统缓冲区,再在用户态堆里面分配一块java缓冲区,再从系统缓冲区拷贝到java缓冲区,数据写入的时候就从java缓冲区再拷贝回去,做了一个不必要的数据复制,因而效率不会很高 ByteBuf 有多种实现类,每种都有不同的特性,下图是 ByteBuf 的家族图谱,可以划分为三个不同的维度:Heap/Direct、Pooled/Unpooled和Unsafe/非 Unsafe, 直接内存(Direct Memory)在 buffer 方法中使用了ByteBuffer.allocateDirect,就是说分配了一个直接内存,这个方法调用了之后表示在操作系统中划出了一个为 1M 大小的缓冲区供当前使用。这块区域对于Java程序来说是可以直接访问的,java程序可以使用,计算机系统也可以进行使用是一块共享的区域。 内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。提升复制速度(io效率) JDK的ByteBuffer类提供了一个接口allocateDirect(int capacity)进行堆外内存的申请 Heap/Direct 就是堆内和堆外内存。Heap 指的是在 JVM 堆内分配,底层依赖的是字节数据;Direct 则是堆外内存,不受 JVM 限制,分配方式依赖 JDK 底层的 ByteBuffer。 Pooled/Unpooled 表示池化还是非池化内存。Pooled 是从预先分配好的内存中取出,使用完可以放回 ByteBuf 内存池,等待下一次分配。而 Unpooled 是直接调用系统 API 去申请内存,确保能够被 JVM GC 管理回收。 Unsafe/非 Unsafe 的区别在于操作方式是否安全。 Unsafe 表示每次调用 JDK 的 Unsafe 对象操作物理内存,依赖 offset + index 的方式操作数据。非 Unsafe 则不需要依赖 JDK 的 Unsafe 对象,直接通过数组下标的方式操作数据。 内存分配的角度来看,ByteBuf 可以分为堆内存 HeapByteBuf 和堆外内存 DirectByteBuf。 堆内存 HeapByteBuf 虽然有GC垃圾回收机制使得回收效率较高但是会增加两次拷贝操作。 堆外内存DirectByteBuf可以减少内核到用户空间和用户空间到内核两次拷贝,但是回收效率比较低 并且为了避免堆外内存的频繁创建和销毁,Netty 提供了池化类型的 PooledDirectByteBuf Netty 提前申请一块连续内存作为 ByteBuf 内存池,如果有堆外内存申请的需求直接从内存池里获取即可,使用完之后必须重新放回内存池,否则会造成严重的内存泄漏 为什么需要 DirectByteBuffer主要是零拷贝 首先介绍:继承于MappedByteBuffer
DirectByteBuffer是在堆外内存分配的空间,堆里面只存那块内存的引用,避免了堆外堆内的来回拷贝 其次,回答为什么需要 Buffer 。 缓冲区就是在内存中预留出指定大小的存储空间,然后对输入/输出(简称i/o)进行数据的临时存储,这部分区域就称为缓冲区 也叫Buffer 我们知道 GC 会管理内存,大致上可以这么认为,其主要做两件事:
、 堆外内存垃圾回收怎么实现的?DirectByteBuffer是通过虚引用(Phantom Reference)来实现堆外内存的释放的。 堆外内存的回收其实依赖于我们的GC机制 首先,我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它在堆里面记录了这块内存的基地址以及大小,那么既然和GC也有关,那就是GC能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。 DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference其实主要是用来跟踪对象何时被回收的,它不能影响GC决策。 GC过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到一个队列里,在GC完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块。 操作系统不能直接使用JVM堆内存进行 I/O 的读写?如果在JVM 内部执行 I/O 操作时,必须将数据拷贝到堆外内存,才能执行系统调用。 VM语言都会存在的问题,那么为什么操作系统不能直接使用JVM堆内存进行 I/O 的读写呢? 主要有两点原因:第一,操作系统并不感知 JVM 的堆内存,而且 JVM 的内存布局与操作系统所分配的是不一样的,操作系统并不会按照 JVM 的行为来读写数据。第二,同一个对象的内存地址随着 JVM GC 的执行可能会随时发生变化,例如 JVM GC 的过程中会通过压缩来减少内存碎片,这就涉及对象移动的问题了。 零拷贝「内核缓冲区」实际上是**磁盘?速缓存(****PageCache)**PageCache 的优点主要是两个: 缓存最近被访问的数据; 预读功能; 2 次 DMA 拷贝都是依赖硬件来完成,不需要 CPU 参与,零拷贝是一个广义的概念,可以认为只要能够减少不必要的 CPU 拷贝,都可以理解为是零拷贝。 netty零拷贝和java零拷贝不一样,是基于应用层面的 1.Netty 在进行 I/O 操作时都是使用的堆外内存,可以避免数据从 JVM 堆内存到堆外内存的拷贝。 2.Composite Buf CompositeByteBuf 可以理解为一个虚拟的 Buffer 对象,它是由多个 ByteBuf 组合而成,但是在 CompositeByteBuf 内部保存着每个 ByteBuf 的引用关系,从逻辑上构成一个整体。 CompositeByteBuf 内部维护了一个 Components 数组。在每个 Component 中存放着不同的 ByteBuf,各个 ByteBuf 独立维护自己的读写索引,而 CompositeByteBuf 自身也会单独维护一个读写索引。 传统的ByteBuffer,如果需要将两个ByteBuffer中的数据组合到一起,比如有一个size1大小的buffer和一个size2大小的buffer,我们需要首先创建一个size=size1+size2大小的新的数组,然后将两个数组size1和size2中的数据分别拷贝到新的数组中,这里涉及到两次CPU拷贝操作。但是netty只需要通过读写索引找到可读字节和可写字节就能找到size1和size2并且逻辑上将他们组合到一起。 wrapper wrappedBuffer 同时也是创建 CompositeByteBuf 对象的另一种推荐做法。 wrappedBuffer 方法可以将不同的数据源的一个或者多个数据包装成一个大的 ByteBuf 对象,其中数据源的类型包括 byte[]、ByteBuf、ByteBuffer。包装的过程中不会发生数据拷贝操作,包装后生成的 ByteBuf 对象和原始 ByteBuf 对象是共享底层的 byte 数组。 ByteBuf.slice 操作 ByteBuf.slice 和 wrappedBuffer 的逻辑正好相反,ByteBuf.slice 是将一个 ByteBuf 对象切分成多个共享同一个底层存储的 ByteBuf 对象。也就是说虽然逻辑上切分了,但是底层的存储仍然是共享。 通过 slice 切分后都会返回一个新的 ByteBuf 对象,而且新的对象有自己独立的 readerIndex、writerIndex 索引,由于新的 ByteBuf 对象与原始的 ByteBuf 对象数据是共享的,所以通过新的 ByteBuf 对象进行数据操作也会对原始 ByteBuf 对象生效。 对于FileChannel.transferTo的使用 Netty中使用了FileChannel的transferTo方法,该方法依赖于操作系统实现零拷贝。 DMA 引擎从文件中读取数据拷贝到内核态缓冲区之后,由操作系统直接拷贝到 Socket 缓冲区,不再拷贝到用户态缓冲区,所以数据拷贝的次数从之前的 4 次减少到 3 次。 java的零拷贝读取磁盘数据的时候,之所以要发生上下文切换,这是因为用户空间没有权限操作磁盘或网卡 DMA :进行IO数据磁盘与系统内核交互的时候,磁盘缓冲区数据满了以后由DMA去调度资源拷贝到内核中。主要是避免CPU进行大量数据搬运。 传统情况下数据会被拷贝四次,先是DMA控制从磁盘到内核缓冲区,再由CPU负责从内核到用户,CPU再负责从用户到内核socket缓冲区,然后DMA控制从内核socket缓冲区到网卡。用户缓冲区的传输是没必要的,因为并没有对数据加工处理。这个过程会有read和write来调用应用到内核 通过使用
sendfile还有一种CPU一次拷贝都不需要的零拷贝,这种需要网卡支持 SG-DMA,DMA搬运磁盘数据到内核缓冲区以后,缓冲区描述符和长度直接传给socket缓冲区,然后SG-DMA直接把磁盘搬运到缓冲区的数据拷贝到网卡上,连socket缓冲区都不需要过去,全程DMA控制,真正意义的零拷贝 如果不支持SG-DMA,就还是把数据拷贝到 socket 缓冲区,但是不需要read和write这两个系统调度了 PageCache 的优点主要是两个:
Netty 支持哪些常用的解码器?固定长度解码器 FixedLengthFrameDecoder 特殊分隔符解码器 DelimiterBasedFrameDecoder 长度域解码器 LengthFieldBasedFrameDecoder 粘包拆包问题:如何获取一个完整的网络包消息长度固定每个数据报文都需要一个固定的长度。当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。当发送方的数据小于固定长度时,则需要空位补齐。 消息定长法使用非常简单,但是缺点也非常明显,无法很好设定固定长度的值,如果长度太大会造成字节浪费,长度太小又会影响消息传输,所以在一般情况下消息定长法不会被采用。 特定分隔符既然接收方无法区分消息的边界,那么我们可以在每次发送报文的尾部加上特定分隔符,接收方就可以根据特殊分隔符进行消息拆分。以下报文根据特定分隔符 \n 按行解析,即可得到 AB、CDEF、GHIJ、K、LM 五条原始报文。 由于在发送报文时尾部需要添加特定分隔符,所以对于分隔符的选择一定要避免和消息体中字符相同,以免冲突。否则可能出现错误的消息拆分。比较推荐的做法是将消息进行编码,例如 base64 编码,然后可以选择 64 个编码字符之外的字符作为特定分隔符。特定分隔符法在消息协议足够简单的场景下比较高效,例如大名鼎鼎的 Redis 在通信过程中采用的就是换行分隔符。 Base64,顾名思义,就是包括小写字母a-z、大写字母A-Z、数字0-9、符号"+"、"/“一共64个字符的字符集,(另加一个“=”,实际是65个字符,至于为什么还会有一个“=”,这个后面再说)。任何符号都可以转换成这个字符集中的字符,这个转换过程就叫做base64编码。 Redis 在通信过程中采用的就是换行分隔符。 消息头+消息内容 消息头用四字节int值存取消息总长度,消息体存数据。接收方接受数据先取出这个总长度值,然后再解析整个消息判断这是不是一个完整的报文。这种方式更加灵活,再消息头还可以放其他东西 常见的序列化方式序列化考虑优先级 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-81tYP2kw-1642241141544)(image-20211224062529861.png)] 用JDK原生自带的inputstream和outputstream。原理就是序列化的时候加入一些分割符,分割头部数据和对象数据,头部一般用来声明序列化协议和版本,对象数据包括类和属性这些,然后反序列化根据分割符解析出来创建一个新的对象。
json是key-value形式文本型序列化框架,可以用来http和前台web调用 优点就是数据格式简单方便读写,兼容性好。 问题:空间开销大,json没有类型使用java的时候要用反射解决所以导致性能不高因为反射降低性能。rpc用它的时候只能用在数据比较少的情况 kryo目前协议中序列化反序列化性能极高,花费的时间比其他协议都少,序列化后体积小,并且API友好不需要实现序列化接口 但是缺点就是兼容性不好和线程不安全 protobuf(Protocol Buffers)底层用C++实现的,谷歌开源的序列化协议,也可以跨语言但是要使用工具进行编译成二进制文件,它的消耗主要就是编译过程,本身性能非常好。并且比起其他几种序列化方式安全性更高。性能只比kyro差一点,比json,hessian,java原生化都强,但是兼容性比kyro强,在序列化协议里面我们一般优先考虑安全然后考虑兼容之后才考虑性能,所以它相比之下更加优秀 .Hessian性能比jdk和json高,生成字节数更小而且支持跨语言而且兼容性更好因为跨语言不需要工具编译,dubbo就是用这个 缺点:但是对java里面linked系列不支持,比如linkedhahsmap,linkedhashset,byteshort反序列化回生成int,
hessian使用更加方便兼容性做的更好,protobuf性能更好一点 简单说下 BIO、NIO 和 AIONetty 是如何保持长连接的什么是长连接? 客户端和服务器之间定期发送的一种特殊的数据包,通知对方自己还在线, 以确保 如何保持长连接? 利用心跳维护长连接信息。 在服务器和客户端之间一定时间内没有数据交互时,即处于 当某一端收到心跳消息后, 就知道了对方仍然在线, 这就确保 TCP 连接的有效性。 Netty 发送消息有几种方式? 直接写入 Channel 中,消息从 ChannelPipeline 当中尾部开始移动; netty长连接短连接长连接就是tcp三握四挥建立连接完成一次读写以后以后不会立刻完毕,发送完就关闭就是短连接,通常用长连接来处理资源请求比较频繁的客户端,避免反复三次握手四次挥手造成网络资源浪费 TCP 保持长连接的过程中,可能会出现断网等网络异常出现,异常发生的时候, client 与 server 之间如果没有交互的话,他们是无法发现对方已经掉线的。这种情况叫连接假死,为了解决这个问题, 我们就需要引入 心跳机制 。 TPS/QPS 很高的 REST 服务中,如果使用的是短连接(即没有开启keep-alive),则很可能发生客户端端口被占满的情形。 这是由于短时间内会创建大量TCP 连接,而在 TCP 四次挥手结束后,客户端的端口会处于 TIME_WAIT一段时间(2*MSL), 这期间端口不会被释放,从而导致端口被占满。这种情况下最好使用长连接。 netty设计模式NioEventLoop 通过核心方法 select() 不断轮询注册的 I/O 事件,Netty 提供了选择策略 SelectStrategy 对象,它用于控制 select 循环行为,SelectStrategy 对象的默认实现就是使用的饿汉式单例
Netty 在创建 Channel 的时候使用的就是工厂方法模式,因为服务端和客户端的 Channel 是不一样的。Netty 将反射和工厂方法模式结合在一起,只使用一个工厂类,然后根据传入的 Class 参数然后利用反射来构建出对应的 Channel,不需要再为每一种 Channel 类型创建一个工厂类。NioServerSocketChannel类的构造函数里通过反射拿到jdk底层的channel,还有tcp配置参数,阻塞模式,pipeline这些 Bootstrap采用了典型的Builder模式构造对象,首先创建一个空实例,然后调用方法设置Bootstrap的必要属性。 把复杂的对象通过一个个简单的对象构造而成
责任链模式对请求发送者和接收者解耦,让接受请求的对象形成一条链,并且沿着这条链传递请求,直到有一个对象处理它为止。 ChannlPipeline 和 ChannelHandler。ChannlPipeline 内部是由一组 ChannelHandler 实例组成的,内部通过双向链表将不同的 ChannelHandler 链接在一起,ChannelHandlerContext 会默认将处理器上下文信息传递到下一个处理器,也可以指定传到某个处理器上。 观察者模式被观察者发布消息,观察者订阅消息 addListener 方法会将添加监听器添加到 ChannelFuture 当中,然后channelFuture执行完毕就通知注册了的监听器,进行下一步操作 这里被观察者就是channelfuture,观察者就是addlistener 装饰者模式装饰器模式是对被装饰类的功能增强,在不修改被装饰类的前提下,能够为被装饰类添加新的功能特性。 wrappedBuffer 同时也是创建 CompositeByteBuf 对象的另一种推荐做法。 wrappedBuffer 方法可以将不同的数据源的一个或者多个数据包装成一个大的 ByteBuf 对象,其中数据源的类型包括 byte[]、ByteBuf、ByteBuffer。包装的过程中不会发生数据拷贝操作,包装后生成的 ByteBuf 对象和原始 ByteBuf 对象是共享底层的 byte 数组。 CompositeByteBuf 就是实现零拷贝的关键 netty线程池作用线程池隔离我们知道,如果有复杂且耗时的业务逻辑,推荐的做法是在 ChannelHandler 处理器中自定义新的业务线程池,将耗时的操作提交到业务线程池中执行。建议根据业务逻辑的核心等级拆分出多个业务线程池,如果某类业务逻辑出现异常造成线程池资源耗尽,也不会影响到其他业务逻辑,从而提高应用程序整体可用率。对于 Netty I/O 线程来说,每个 EventLoop 可以与某类业务线程池绑定,避免出现多线程锁竞争。 连接空闲检测 + 心跳检测TCP KeepAlive 是用于检测连接的死活,而心跳机制则附带一个额外的功能:检测通讯双方的存活状态。两者听起来似乎是一个意思,但实际上却大相径庭。 连接空闲检测就是服务器每隔一段时间检测是否有数据读写,如果一直能收到客户端发来的数据就说明还是活跃状态,如果没有收到也不一定是假死状态,可能是客户端没数据发,但是连接还是健康的,所以需要心跳机检测,客户端定时向服务器发送一次心跳包,如果服务器没有收到就说明客户端已经下线了。心跳包就是为了方式没有数据读写这种情况,它是用来判断连接是否可用的。而keepalive TCP的断开可能有时候是不能瞬时探知的,要服务端维持一个2h+10*75秒的死链接,keepalive设计初衷清除和回收死亡时间长的连接,不适合实时性高的场合,而且它会先要求连接一定时间内没有活动,周期长,这样其实已经断开很长一段时间,没有及时性。 心跳包就改善了这一点。并且可以防止TCP的死连接问题,避免出现长时间不在线的死链接仍然出现在服务端的管理任务中。 |
|
网络协议 最新文章 |
使用Easyswoole 搭建简单的Websoket服务 |
常见的数据通信方式有哪些? |
Openssl 1024bit RSA算法---公私钥获取和处 |
HTTPS协议的密钥交换流程 |
《小白WEB安全入门》03. 漏洞篇 |
HttpRunner4.x 安装与使用 |
2021-07-04 |
手写RPC学习笔记 |
K8S高可用版本部署 |
mySQL计算IP地址范围 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/26 11:44:14- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |