Netty学习笔记二—服务端/客户端创建

服务端创建

netty服务端创建时序图:

关键步骤:

一、创建ServerBootstrap实例。此为Netty启动辅助类

二、设置并绑定Reactor线程池。 Netty的Reactor线程池是EventLoopGroup,它实际就是EventLoop的数组。

EventLoop的职责是处理注册到本线程多路复用器Selector上的Channel,Selector的轮询操作由绑定的EventLoop线程run方法驱动,在一个循环体内循环执行。值得说明的是,EventLoop的职责不仅仅是处理网络I/O事件,用户自定义的Task和定时任务Task也统一由EventLoop负责处理,这样线程模型就实现了统一。

EventLoopGroup即Reator线程池,一般有两个,一个负责调度和执行客户端的接入,另一个负责网络读写事件的处理、用户自定义任务和定时任务的执行。

三、 设置并绑定服务端Channel。 作为NIO服务端,需要创建ServerSocketChannel,Netty对原生的NIO类库进行了封装, 服务端监听端口往往只需要在系统启动时才会调用

四、链路建立的时候创建并初始化ChannelPipeline。ChannelPipeline并不是NIO服务端必需的,它本质就是一个负责处理网络事件的职责链,负责管理和执行ChannelHandler。网络事件以事件流的形式在ChannelPipeline中流转,由ChannelPipeline根据ChannelHandler的执行策略调度ChannelHandler的执行。

典型事件:链路注册;链路激活;链路断开;接收到请求消息;请求消息接收并处理完毕;发送应答消息;链路发生异常;发生用户自定义事件。

五、初始化ChannelPipeline完成之后,添加并设置ChannelHandler。ChannelHandler是Netty提供给用户定制和扩展的关键接口。利用ChannelHandler用户可以完成大多数的功能定制,例如消息编解码、心跳、安全认证、TSL/SSL认证、流量控制和流量整形等。Netty同时也提供了大量的系统ChannelHandler供用户使用,比较实用的系统ChannelHandler总结如下。

(1)系统编解码框架——ByteToBessageCodec;

(2)通用基于长度的半包解码器——LengthFieldBasedFrameDecoder;

(3)码流日志打印Handler——LoggingHandler;

(4)SSL安全认证Handler——SslHandler;

(5)链路空闲检测Handler——IdleStateHandler;

(6)流量整形Handler——ChannelTrafficShapingHandler;

(7)Base64编解码——Base64Decoder和Base64Encoder。

六、 绑定并启动监听端口。在绑定监听端口之前系统会做一系列的初始化和检测工作,完成之后,会启动监听端口,并将ServerSocketChannel注册到Selector上监听客户端连接。

七、Selector轮询。由Reactor线程NioEventLoop负责调度和执行Selector轮询操作,选择准备就绪的Channel集合。

八、当轮询到准备就绪的Channel之后,就由Reactor线程NioEventLoop执行ChannelPipeline的相应方法,最终调度并执行Channelhandler。

九、执行Netty系统的ChannelHandler和用户添加定制的ChannelHandler。ChannelPipeline根据网络事件的类型,调度并执行ChannelHadler。

客户端创建

netty客户端创建时序图:

关键步骤:

一、用户线程创建Bootstrap实例,通过API设置创建客户端相关的参数,异步发起客户端连接。

二、创建处理客户端连接、I/O读写的Reator线程组NioEventLoopGroup。可以通过构造函数指定I/O线程的个数,默认为CPU内核数的2倍。

三、通过Bootstrap的ChannelFactory和用户指定的Channel类型创建用于客户端连接的NioSocketChannel,它的功能类似于JDK NIO类库提供的SocketChannel。

四、创建默认的Channel Handler Pipeline,用于调度和执行网络事件。

五、异步发起TCP连接,判断连接是否成功。如果成功,则直接将NioSocketChannel注册到多路复用器上,监听读操作位,用于数据报读取和消息发送。如果没有立即连接成功,则注册连接监听位到多路复用器,等待连接结果。

六、注册对应的网络监听状态位到多路复用器。

七、由多路复用器在I/O现场中轮询Channel,处理连接结果。

八、如果连接成功,设置Future结果,发送连接成功事件,触发ChannelPipeline执行。

九、有ChannelPipeline调度执行系统和用户的ChannelHandler,执行业务逻辑。

ByteBuf

当我们进行数据传输的时候,往往需要用到缓冲区,对应NIO而言,主要用到的是ByteBuffer,但由于其长度固定,只有一个标识位置的指针,API功能有限,Netty自己实现了ByteBuf。只要功能有,7中基本类型、byte数组、ByteBuffer等的读写、缓冲区自身的copy和slice(切片)、设置网络字节序、构造缓冲区、操作位置指针等方法。

ByteBuf通过两个位置指针来协助缓冲区的读写操作(ByteBuffer只要一个),读操作用readerIndex,写操作用writerIndex。支持动态扩展,并且有多种API协助开发。

 

Channel

Channel是Netty抽象出来的网络I/O读写相关的接口,为什么不使用jdk nio原生的Channel呢?

  • JDK的SocketChannel和ServerSocketChannel没有统一的Channel接口供业务开发者使用

  • JDK的SocketChannel和ServerSocketChannel的主要职责就是网络I/O操作,集成使用难度大,直接实现与开发一个新的Channel工作量差不多。

  • Netty的Channel需要跟Netty整体架构融合在一起,例如I/O模型,基于ChannelPipeline的定制模型,元数据配置化的TCP参数等。

  • 更加灵活

Channel的主要设计理念

  • 在Channel接口层,采用Facade模式进行统一封装,将网络I/O操作、网络I/O相关联的其他操作封装起来,统一对外提供。

  • Channel接口定义尽量大而全,为SocketChannel和ServerSocketChannel提供统一的视图,不同子类实现不同功能,公共功能在父类中实现,最大程度地实现功能和接口的重用。

  • 具体实现采用聚合而非包含的方式,将相关的功能类聚合在Channel中,由Channel统一负责分配和调度,功能实现更加灵活。

 

ChannelPipeline和ChannelHannel

Netty的Channel过滤器实现原理与Servlet Filter机制一致。它将Channel的管道抽象为ChannelPipeline,消息在ChannelPipeline中流动和传递。ChannelPipeline持有I/O时间拦截器ChannelHandler的连表,有ChannelHandler对I/O事件进行拦截和处理,可以方便的通过新增和删除ChannelHandler来实现不同的业务逻辑定制。这类拦截器实际上是责任链模式的一种变形,主要是为了方便事件的拦截和用户业务逻辑的定制。

ChannelPipeline支持运行态动态的添加或者删除ChannelHandler,在某些场景下这个特性非常实用,例如高峰对系统做拥塞保护。

ChannelPipeline是线程安全的,但是ChannelHandler却不是线程安全的,用户需要自己保证ChannelHandler的线程安全

inbound:从I/O线程流向用户业务Handler的inbound事件。调用HeadHandler对应的fireXXX方法或者执行事件相关的逻辑操作

outbound:由用户线程或者代码发起的I/O操作

 

由于TCP存在粘包和组包问题,通常情况下必须自己处理半包消息,利用LenthFieldBaseFrameDecoder解码器可以自动解决半包问题。

在pipeline中增加LineBasedFrameDecoder解码器,指定正确的参数组合,可以将Netty的ByteBuf 解码成单个的整包消息,后面的业务解码器拿到的就是个完成的数据报。

 

半包处理的细节

ByteToMessageDecoder解码器用于将ByteBuf解码成POJO对象。通过cumulation是否为空判断解码器是否缓存了没有解码完成的半包消息,如果为空,说明是首次解码或者最近一次已经处理完了半包消息,没有缓存的半包消息需要处理,直接将需要解码的ByteBuf赋值给cumulation。如果cumulation缓存有上次没有解码完成的ByteBuf,则进行复制操作,将需要解码的ByteBuf复制到 cumulation中,他的原理如下:

在复制前需要对cumulation的可写缓冲区进行判断,如果不足则需要动态扩展。

 

 

Reactor在下一章

 

ChannelFuture

由于Netty的Future都是与异步I/O操作相关的,命名为ChannelFuture。

在Netty中,所有的I/O操作都是异步的,这意味着任何I/O调用都会立即返回,而不是像传统BIO那样同步等待操作完成。

ChannelFuture有两种状态,uncompleted和completed,当开始一个I/O操作时,一个新的ChannelFuture被创建,此时属于uncompleted——非失败、非成功、非取消,成功便成completed,三种装填,失败、成功、取消。

ChannelFuture提供了一系列新的API,用于获取操作结果、添加事件监听器、取消I/O操作、同步等待等。

Netty强烈建议直接通过添加监听器的方式获取I/O操作结果。ChannelFuture 可以同时增加一个或者多个GenericFutureListener,也可以remove。当I/O操作完成后,I/O线程会回调ChannelFuture中的GenericFutureListener的完成方法,并把ChannelFuture对象当做方法的入参。如果用户需要做上线文相关操作,需要将上线文信息保存到对应的ChannelFuture中。

推荐通过GenericFutureListener代替ChannelFuture的get等方法,原因是时间无法预测,不设置超时时间,会导致线程阻塞,设置超时时间无法精准预测,利用异步通知机制回调GenericFutureListener最佳,性能最优。

也不要在ChannelHandler中调用ChannelFuture的await方法,会导致死锁。原因是发起I/O操作之后,有I/O线程负责异步通知发起I/O操作的用户线程,如果I/O线程和用户线程是一个线程,会导致I/O线程等待自己通知自己通知操作完成,这就导致死锁。

异步I/O操作有两类超时,TCP层面的I/O超时和业务逻辑层面的超时。业务逻辑时间应大于I/O超时时间。ChannelFuture超时并不代表I/O超时,这意味着ChannelFuture超时后,如果没有关闭连接资源,随后连接依旧可以成功,但会有严重的问题。

 

欢迎关注公众号,让我们一起学习探讨问题

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
   本课程由《Netty 4核心原理作者》亲授。    在互联网分布式系统的推动下,Netty 作为一个能够支撑高性能、高并发的底层网络通信框架而存在。Netty 底层是基于 Java NIO 实现的,对 NIO 进行了非常多的优化,因此深受广大开发者尤其是一线大厂开发者的青睐。    作为一个 Java 开发者,如果没有研究过 Netty,那么你对 Java 语言的使用和理解可能仅仅停留在表面,会点 SSH,写几个 MVC,访问数据库和缓存,这些只是初级 Java 开发者做的事。如果你要进阶,想了解 Java 服务器的高阶知识,Netty 是一个必须要跨越的门槛。学会了 Netty,你可以实现自己的 HTTP 服务器、FTP 服务器、UDP 服务器、RPC 服务器、WebSocket 服务器、Redis 的 Proxy 服务器、MySQL 的 Proxy 服务器等。    如果你想知道 Nginx 是怎么写出来的,    如果你想知道 Tomcat 和 Jetty 是如何实现的,    如果你也想实现一个简单的 Redis 服务器,    ……    那么你应该好好研究一下 Netty,它们高性能的原理都是类似的。    因为 Netty 5.x 已被官方弃用,本课程内容基于 Netty 4 分析其核心原理,培养高级开发者自己“造轮子”的能力。本课程不仅讲述理论知识,还围绕能够落地的实战场景,开创手写源码的学习方式,使学习源码更加高效。本书的主要特色是首次提供了基于 Netty 手写 RPC 框架、基于 Netty 手写消息推送系统等实战案例。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值