《Netty实战》读书笔记

第一部分 Netty 的概念及体系结构

《第一章 Netty-异步事件驱动》

(1)异步和事件驱动

  • 异步、异步方法会立即返回,非阻塞网络的调用使我们不必等待一个操作的完成
  • 选择器使得我们能够通过较少的线程便可监视许多连接上的事件。

(2)Netty核心组件

  • channel、NIO的基本构造负责连接以及缓冲区数据的传输
  • 回调、Netty内部使用回调处理事件,例如一个新的连接建立的时候Channelhandler的channelActive()方法将会被调用
  • 异步、ChannelFuture继承了jdk中的Future,实现了另一种操作完成时的通知,例如channel.connect()将返回一个channelFuture,然后通过get获取连接的结果,这样可以不用等待连接成功后处理结果。netty的异步与回调相互补充例如channelFuture添加channelFutureListener会等到channelFuture操作完成后触发operationComplete
  • 事件与Channelhandler、Channelhandler使用不同的事件触发相应的动作例如:连接事件、断开连接事件、数据读取事件等,netty定义了大量开箱即用的ChannelHandler,每个ChannelHandler组成一个ChannelHandler链,各个ChannelHandler负责各自的功能
    在这里插入图片描述
  • 选择器、事件和EventLoop、Netty通过触发事件将Selector从应用程序中抽象出来,无需手动处理selector事件;netty为每个channel返配一个EventLoop用来处理所有事件,包括注册感兴趣的事件,将事件派发给ChannelHandler

《第二章 你的第一款Netty应用程序》

(1)完整的netty程序设计

  • 服务端的ChannelHandler和业务逻辑处理,继承ChannelInboundHandlerAdapter,然后处理channelRead()、exceptionCaught()等事件,对于exceptionCaught如果不捕获异常那么将沿着ChannelPipeline链传递给下一个ChannelHandler。所以应用程序中至少有一个实现了exceptionCaught()方法的ChannelHandler
  • 客户端的ChannelHandler和业务处理、扩展SimpleChannelInboundHandler类,然后处理channelActive(),channelRead0(),exceptionCaught()等事件。其中SimpleChannelInboundHandler继承了ChannelInboundHandlerAdapter
  • 客户端使用SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter原因:客户端,当channelRead0()完成时,代表客户端收到消息时代表已经处理完他了,SimpleChannelInboundHandler 负责释放指向保存该消息的ByteBuf 的内存引用。而服务端需要将消息回送给发送者,channelRead()方法返回后可能仍然没有完成,为此扩展了ChannelInboundHandlerAdapter接口的服务端不会释放消息,直到writeAndFlush()方法被调用时被释放

《第三章 Netty的组件和设计》

(1)Channel、EventLoop 和ChannelFuture

  • Channel接口,Netty中的Channel降低了Socket类的复杂性,提供了bind()、connect()、read()和write()等基本I/O操作,其中常用的Channel实现类:NioSocketChannel、NioSctpChannel、NioDatagramChannel
  • EventLoop接口、Channel、EventLoop、Thread 以及EventLoopGroup 之间的关系:一个EventLoopGroup 可包含多个EventLoop,一个EventLoop 只绑定一个Thread、所有EventLoop 的IO事件都在它专有的Thread上处理、一个Channel只注册于一个EventLoop、一个EventLoop可以分配多个Channel
    在这里插入图片描述
  • ChannelFuture 接口、可以向ChannelFuture接口的addListener()方法注册ChannelFutureListener可以在异步操作完成时得到通知

(2)ChannelHandler 和ChannelPipeline

  • ChannelPipeline 提供了ChannelHandler 链的容器,ChannelHandler在ChannelPipeline中的加入顺序就是ChannelHandler的执行顺序;
  • 当ChannelHandler 被添加到ChannelPipeline 时,它将会被分配一个ChannelHandlerContext,其代表了ChannelHandler 和ChannelPipeline 之间的绑定,可以用ChannelHandlerContext获取channel进行发送消息,区别是用channel写消息导致消息从ChannelPipeline 的尾端开始流动,而直接用ChannelHandlerContext发送消息将导致消息从ChannelPipeline 中的下一个ChannelHandler 开始流动。
  • 适配器ChannelHandlerAdapter、由ChannelHandler实现的适配器负责把ChannelPipeline中的每个ChannelHandler把事件转发到链中的下一个ChannelHandler。我们自定义ChannelHandler时常用的适配器:ChannelHandlerAdapter、ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapterChannelPipeline 中的下一个ChannelInboundHandler
  • 编码器和解码器、Netty提供的编码器/解码器适配器类都实现了ChannelOutboundHandler 或者ChannelInboundHandler 接口,对于入站数据channelRead 方法/事件被重写,随后调用解码器所提供的decode(),然后将已解码的字节转发到ChannelPipeline 中的下一个ChannelInboundHandler,出站把消息转换为字节并转发到下一个ChannelOutboundHandler

(3)引导

  • 客户端Bootstrap、连接到远程主机和端口,引导客户端只需要一个EventLoopGroup
  • 服务端ServerBootstrap、绑定一个本地端口,引导服务端需要两个EventLoopGroup,服务端第一个EventLoopGroup只包含一个ServerChannel并给它分配一个EventLoop,一旦连接被接受,第二个EventLoopGroup 就会给它的Channel分配一个EventLoop。线程模型如图:在这里插入图片描述

《第四章 传输》

(1)传输API

  • Netty使用阻塞IO只需添加OioServerSocketChannel类,非阻塞添加NioServerSocketChannel类
  • ChannelPipeline实现了拦截过滤器模式、在ChannelPipeline提供的链式容器中,一个ChannelHandler的输出将是另一个ChannelHandler输入,每个channel可获得它所在的eventLoop、pipeline或者它所连接的localAddress、remoteAddress
  • Netty的Channel 实现是线程安全,可多线程操作

(2)内置传输-Netty所提供的传输

  • NIO、基于选择器充当注册表当channel状态变化时得到通知,有零拷贝特性可以快速高效地将数据从文件系统移动到网络接口,而不需要将其从内核空间复制到用户空间
  • Epoll、Linux下多路复用IO接口select/poll的增强版本,Netty为Linux提供的一组NIO API去使用Epoll,性能优于JDK的NIO,只需替换EpollEventLoopGroup、NioServerSocketChannel即可
  • OIO、使用java.net包作为基础-阻塞流
  • Local、用于JVM内部本地传输

《第五章 ByteBuf》

主要内容:ByteBuf API 的优点

  • 它可以被用户自定义的缓冲区类型扩展;
  • 通过内置的复合缓冲区类型实现了透明的零拷贝;
  • 容量可以按需增长(类似于JDK的StringBuilder),通过一个ByteBuf的子类CompositeByteBuf构建一个复合缓冲区,他可以为多个ByteBuf提供一个聚合视图,基于这个特性可以根据需要添加删除ByteBuf示例。
  • 在读和写这两种模式之间切换不需要调用ByteBuffer 的flip()方法,因为读和写使用了不同的索引;
  • 支持方法的链式调用;
  • 支持引用计数;
  • 支持池化。

(1)ByteBuf读写是如何工作的

  • ByteBuf维护了两个不同的索引:一个用于读取,一个用于写入,两个指针互不影响,而原始NIO的ByteBuffer共用一个position由写转换为读的时候需要调用flip()方法重置position

(2)ByteBuf 的使用模式

  • 通过ByteBuf.hasArray()返回的布尔类型的值来确定是否由数组实现的ByteBuf,返回ture代表由数组实现说明此ByteBuf在堆缓冲区,返回fase说明在直接缓冲区;这与jdk中的ByteBuffer类似,都可以使用基于数组的缓冲区和直接缓冲区
  • 复合缓冲区CompositeByteBuf、它为多个ByteBuf 提供一个聚合视图、扩充CompositeByteBuf可以通过追加的方式进行类似于StringBuilder;如果用原始的ByteBuffer来实现扩充将需要分配与复制的操作类似于String,效率低下;复合缓冲区可以是直接缓冲区也可以是非直接缓冲区。

(3)字节级操作

  • 随机访问索引、通过buffer.getByte(i)获取元素不改变readerIndex 也不会改变writerIndex
  • 顺序访问索引、ByteBuf读索引和写索引划分为3个区域。readBytes使readerIndex 指针后移,可以buffer.isReadable()来判断是否是可读字节;同理buffer.isWritable()判断是否可读,然后使用writeByte()使writerIndex指针后移。
    在这里插入图片描述

(4)索引管理(与jdk中的ByteBuffer索引对比)

  • JDK的ByteBuffer定义了一个mark(int readlimit)和reset()方法,reset重置到mark指定的位置
  • netty中的ByteBuf,通过调用markReaderIndex()、markWriterIndex()、resetWriterIndex()和resetReaderIndex()来标记和重置ByteBuf 的readerIndex 和writerIndex

(5)派生缓冲区

  • ByteBuf可以使用诸如:slice(),readSlice(int)派生出一个新的ByteBuf,以专门的方式创建一个呈现其内容的视图,所以创建派生缓冲区的成本很低廉,但是修改其对应的源实例派生缓冲区也会改变,类似于浅拷贝;如果需要有独立的数据副本需要使用buf.copy来创建,类似深拷贝

(6)ByteBuf的池化

  • Netty通过ByteBufAllocator接口实现ByteBuf的池化功能,用来降低分配和释放内存的开销
  • 获取ByteBufAllocator的途径:channel.alloc(),channelHandlerContext.alloc()
  • Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocator和UnpooledByteBufAllocator,前者池化了ByteBuf提高了性能减少了内存碎片,后者未池化每次都返回ByteBuf实例
  • 另外可以通过Unpooled工具类,来创建未池化的ByteBuf

(7)引用计数

  • ByteBuf实现了ReferenceCounted接口,当引用计数大于0时保证对象不会被释放,当调用buf.release()时候计数将归0该对象被释放,降低了内存分配的开销

《第六章 Channelhandler和ChannelPipeline》

(1)ChannelHandler家族

  • Channel 的生命周期、ChannelUnregistered:Channel已经被创建,但还未注册到EventLoop,ChannelRegistered:Channel已经被注册到了EventLoop,ChannelActive:Channel 处于活动态,ChannelInactive-Channel没有连接到远程节点
  • ChannelHandler 的生命周期、调用handlerAdded方法添加channelHandler到ChannelPipeline、调用handlerRemoved方法将channelHandler从ChannelPipeline中移除
  • ChannelInboundHandler接口、ChannelInboundHandler 的生命周期与Channel 的生命周期密切相关,例如Channel 处于活动状态时会调用channelActive方法,channel断开连接时会调用channelInactive方法。使用ChannelInboundHandler需要手动调用ReferenceCountUtil.release()来释放ByteBuf相关内存,而SimpleChannelInboundHandler会自动释放资源,因为它在channelRead方法的finally调用了ReferenceCountUtil.release()方法来释放ByteBuf
  • ChannelHandler适配器、ChannelHandlerAdapter根据方法体调用相关联的ChannelHandlerContext上的等效方法,从而将事件转发到了ChannelPipeline 中的下一个ChannelHandler 中。
  • 资源管理、Netty默认SIMPLE模式,使用1%的默认采样并报告异常;如果没有用ChannelHandlerContext.fireChannelRead()方法将消息传递到下一个ChannelInboundHandle,那么消息处理完毕之后应当使用ReferenceCountUtil.release(msg)方法释放资源。

(2)ChannelPipeline 接口

  • ChannelPipeline提供入站和出站事件的ChannelHandler实例链,在ChannelPipeline传播时,会判断该ChannelHandler类型与事件运动方向是否匹配,若不匹配则跳过直到匹配为止
  • ChannelPipeline有丰富的API来响应入站与出站事件

(3)ChannelHandlerContext接口

  • 若使用channel或ChannelPipeline调用与ChannelHandlerContext同名的方法,它们将会沿着整个ChannelPipeline进行传播绕过了下面需要流经的ChannelHandler,好处就是可以减少他不感兴趣ChannelHandler所带来的开销。而使用ChannelHandlerContext方法只会传递给ChannelPipeline中的下一个能处理该时间的Channelhandler,所以说ChannelHandlerContext方法产生的事件流更短。他们的关系如图:
    在这里插入图片描述
  • 可以用ChannelHandlerContext获取ChannelPipeline然后动态的调整ChannelHandler;如果同一个ChannelHandler需要添加多个ChannelPipeline需要加上@Shareble;

(4)异常处理

  • 处理入站异常、ChannelHandler.exceptionCaught()默认将异常传递到ChannelPipeline中的下一个ChannelHandler,如果需要自定义处理逻辑需要重写exceptionCaught()方法
  • 处理出站异常、每个出站操作都将返回一个ChannelFuture、通过channelFuture.addListener()方法注册ChannelFutureListener(),它会在消息完成或者失败的时候触发operationComplete方法

《第七章 EventLoop和线程模型》

(1)EventLoop 接口

  • EventLoop翻译为事件循环,每个EventLoop对应一个Runnable实例,构建与java.util.concurrent包之上,继承关系如图:
    在这里插入图片描述
  • JDK提供的定时任务的弊端、可以通过Executors.newScheduledThreadPool(10)创建一个定时任务执行器,但是在大量请求的情况下会创建太多额外的线程,EventLoop扩展了ScheduledExecutorService,EventLoopGroup可以设置固定数量的EventLoop这样就不会有大量的线程创建。
  • EventLoop任务处理流程、如果接收到一个新任务,首先判断接收任务的Thread是否是是分配给当前Channel以及它的EventLoop的那一个线程,如果是就会被直接执行,如果否就放入内部队列中稍后执行,每个EventLoop 都有它自已的任务队列,独立于任何其他的EventLoop。执行流程图如下:
    在这里插入图片描述
  • 不要将长时间运行的任务加入到执行队列中、可以通过ChannelPipeline提供的add()方法,添加一个EventExecutorGroup来处理这个任务,EventExecutorGroup中有个EventExecutor会处理这个任务

(2)EventLoop/线程的分配

  • 异步传输模型、每个EventLoop可分配多个Channel
    在这里插入图片描述
  • 阻塞传输传输模型、每个EventLoop分配一个Channel
    在这里插入图片描述

《第八章 引导》

(1)Bootstrap类

  • 在AbstractBootstrap中子类型是其父类型的一个类型参数,所以支持链式调用即流式语法
  • Channel 和EventLoopGroup 的兼容性、对于NIO 以及OIO 传输两者来说,都有相关的EventLoopGroup 和Channel 实现,必须保持这种兼容性否者将爆不兼容性错误

(2)引导服务器-ServerBootstrap类

  • ServerBootstrap类列出一些不存在的方法,childHandler()、childAttr()和childOption(),因为ServerChannel可以创建多个子Channel所以负责引导ServerChannel的ServerBootstrap 提供了这些方法,他们的关系如图:
    在这里插入图片描述
  • 尽可能地重用EventLoop,以减少线程创建所带来的开销。例如:bootstrap.group(ctx.channel().eventLoop())
  • 可以使用ChannelInitializer添加多个childHandler
  • 可以在bootstrap上配置ChannelOption参数,如超时属性、缓冲区设置,这样不用每个Channel都配置这些属性,它将自动的应用到bootstrap引导所创建的Channel

(3)引导DatagramChannel

  • Netty 提供了DatagramChannel 的实现。唯一区别就是,客户端不再使用connect()方法,而是只调用bind()方法

(4)Netty的优雅关闭

  • 可以调用EventLoopGroup.shutdownGracefully()来关闭EventLoopGroup,这个方法将返回一个Future,它将一直阻塞直到关闭完成时获得通知

《第九章 单元测试》

省略。。。。

第二部分 编解码器

《第十章 编解码器框架》

(1)解码器

  • 解码器是负责将入站数据从一种格式转换到另一种格式的,解码器实现了ChannelInboundHandler,解码器覆盖了两种用例:一种是ByteToMessageDecoder将将字节解码为消息,另一种是MessageToMessageDecoder将一种消息类型解码为另一种
  • 编解码器的引用计数,一旦消息被编码或者解码,他们就会被ReferenceCountUtil.release(message)调用释放,如果不想被释放可以调用ReferenceCountUtil.retain(message)方法,增加引用计数从而防止消息被释放
  • 抽象类ByteToMessageDecoder、使用ByteToMessageDecoder必须得实现decode()方法,弊端是如果要读取固定字节数的数据每次都必须验证是否有足够的数据。
  • 抽象类ReplayingDecoder、ReplayingDecoder扩展了ByteToMessageDecoder类,使得我们不必调用readableBytes()方法
  • 抽象类MessageToMessageEncoder、可以将入站数据从一种消息格式解码为另一种,例如Integer类型数据转换为String类型数据

(2)抽象的编解码器类

编解码器类同时实现了ChannelInboundHandler 和ChannelOutboundHandler 接口

  • 抽象类ByteToMessageCodec、需要同时实现decode与encode方法,编解码器将读取传入字节,并将它们解码为一个自定义的消息类型,然后编码回字节以便进行传输
  • 抽象类MessageToMessageCodec、decode() 方法是将INBOUND_IN 类型的消息转换为OUTBOUND_IN 类型的消息, 而encode()方法则进行它的逆向操作
  • CombinedChannelDuplexHandler 类、同时实现编解码器会对可重用性造成影响,而CombinedChannelDuplexHandler 类可以结合编码器与解码器,可以实现可重用性

《第十一章 预置的ChannelHandler和编解码器》

(1)通过SSL/TLS 保护Netty应用程序

  • Netty提供的SslHandler来支持SSL实现数据安全,本质仍然是一个ChannelHandler,也是一种编解码器需要对数据进行解密以及加密,内部通过JDK提供的SSLEngine来完成实际的工作。
  • SslHandler在握手阶段两个节点会互相商定一种加密方式,SSL/TLS在握手阶段完成之后,所有的数据都将会被加密

(2)构建基于Netty的HTTP/HTTPS应用程序

  • HTTP解码器和编码器、一个完整的消息体可能有多个数据部分组成,Netty提供HttpRequestEncoder、HttpResponseEncoder、HttpRequestDecoder、HttpResponseDecoder来实现对多个消息体的字节与消息体的互转
  • 聚合HTTP 消息、Netty提供编解码器HttpClientCodec来聚合Http消息,他可以把多个消息合并为FullHttpRequest 或者FullHttpResponse 消息。
  • HTTP压缩、Http使用压缩可以减少数据的传输,Netty 为压缩和解压缩提供了ChannelHandler 实现,他同样是一种编解码器,可以通过在Pipeline添加HttpContentDecompressor来支持压缩
  • 使用HTTPS、启用HTTPS 只需要将SslHandler添加到ChannelPipeline中即可
  • WebSocket、可以通过添加WebSocketServerProtocolHandler的方式来支持WebSocket,WebSocket可以双向通信,WebSocket连接的过程是通过http(s)升级握手来完成的过程如下:
    在这里插入图片描述

(3)空闲的连接和超时

  • Netty提供了IdleStateHandler用来处理空闲连接,当空闲连接太长时会触发一个IdleStateEvent事件,然后可以在ChannelInboundHandler中重写userEventTriggered()方法来处理该该事件,通常是关闭Channel
  • ReadTimeoutHandler与WriteTimeoutHandler用来处理数据的读写超时、超时的情况下会在exceptionCaught()方法抛出TimeoutException异常

(4)解码基于分隔符的协议和基于长度的协议

  • 基于分隔符的协议、分隔符协议使用定义的特殊字符来标记的消息或者消息段,Netty提供了两种基于分隔符协议的解码器:DelimiterBasedFrameDecoder可以用任何特殊分隔符的通用解码器,LineBasedFrameDecoder提取/n或/r/n分隔符的解码器
  • 基于长度的协议、Netty提供了处理5这种类型的协议的两种解码器:FixedLengthFrameDecoder处理定长帧,LengthFieldBasedFrameDecoder根据进帧头部中的长度值提取帧,专门处理那些消息头部的帧大小不是固定值的协议。

(5)写大型数据

  • 通过零拷贝技术消除将文件的内容从文件系统移动到网络栈的复制过程,需要实现FileRegion 接口
  • 通过逐块传输的方式来避免OutOfMemoryError 的风险,需要实现ChunkedInput接口

(6)序列化数据

  • JDK 序列化、JDK 提供了ObjectOutputStream和ObjectInputStream,Netty同样提供了对JDK序列化反序列化的支持,但是性能不是非常高效的
  • JBoss Marshalling 序列化、通过添加MarshallingDecoder和MarshallingEncoder这两种编解码器就可以实现,比JDK快3倍
  • Protocol Buffers 序列化、跨语言,紧凑高效

第三部分 网络协议

《第十二章 WebSocket》

(1)添加WebSocket支持

  • 使用WebSocket的应用程序将始终以HTTP/S作为开始,然后再执行升级、Netty规定:若请求的URL以/ws 结尾,那么将会把协议升级到WebSocket
  • 处理HTTP 请求、对于Http的请求仍然是Channel进行数据传输,只不过是增加了符合HTTP规范的请求头的格式,比如:keep-alive,写操作完成后是否关闭Channel
  • 聊天应用程序需具备这几种帧类型:CloseWebSocketFrame,PingWebSocketFrame,PongWebSocketFrame,TextWebSocketFrame其中TextWebSocketFrame是我们真正需要处理的,Netty提供了WebSocketServerProtocolHandler用来处理其它类型帧
  • WebSocket 协议升级完成之后、WebSocketServerProtocolHandler将会移除任何不再被WebSocket 连接所需要的ChannelHandler。

《第十三章 使用UDP广播事件》

(1)UDP 的基础知识

  • UDP消除了握手带来的开销比较适合那些容忍消息丢失的程序。UDP相当于投明信片并不知道能不能达到何时到达。
  • 广播系统图解:
    在这里插入图片描述
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值