Netty学习笔记(1)

最近工作中用到了Netty,于是借此机会整理出来。关于什么是Netty,以及它的好处就不多说了,网上资料很多。

学习Netty前我们应该着重理解下面这些对象,这些都是Netty的核心概念,对我们学习Netty很有帮助

Bootstrap

一种是用于客户端的Bootstrap,一种是用于服务端的ServerBootstrap

  • Bootstrap用来连接远程主机,有1个EventLoopGroup
  • ServerBootstrap用来绑定本地端口,有2个EventLoopGroup

有两个区别:

1、“ServerBootstrap”监听在服务器监听一个端口轮询客户端的“Bootstrap”,客户端的Bootstrap需要调用connect进行连接,返回ChannelFuture对象,可以异步的阻塞直到关闭成功fu.channel().closeFuture().sync();

2 服务端引导对象包含两个EventLoopGroup对象,客户端只有一个,这里服务端的ServerBootstrap可以认为有2channels组,第一组包含一个单例ServerChannel,代表持有一个绑定了本地端口的socket;第二组包含所有的Channel,代表服务器已接受了的连接。

Netty学习笔记(1)_第1张图片

上图中,EventLoopGroupA唯一的目的就是接受连接然后交给EventLoopGroupBNetty可以使用两个不同的Group,因为服务器程序需要接受很多客户端连接的情况下,一个EventLoopGroup将是程序性能的瓶颈,因为事件循环忙于处理连接请求,没有多余的资源和空闲来处理业务逻辑,最后的结果会是很多连接请求超时。若有两EventLoops即使在高负载下,所有的连接也都会被接受,因为EventLoops接受连接不会和哪些已经连接了的处理共享资源。

下面这幅图表示当服务端只有一个 EventLoopGroup 的情况下,也就是处理 IO 和接受连接只使用一个 EventLoopGroup 对象。

Netty学习笔记(1)_第2张图片

EventLoopGroup

EventLoop的集合,包括一个或多个EventLoop,而EventLoop关联一个channel,是执行实际工作的线程,EventLoop总是绑定一个单一的线程,在其生命周期内不会改变。


Netty学习笔记(1)_第3张图片

当注册一个Channel后,Netty将这个Channel绑定到一个EventLoop,在Channel的生命周期内总是被绑定到一个EventLoop。在Netty IO操作中,你的程序不需要同步,因为一个指定通道的所有IO始终由同一个线程来执行。

      下图显示了EventLoopEventLoopGroup的关系:

Netty学习笔记(1)_第4张图片

这里显示EventLoop是一个EventLoopGroup,两个的关系:EventLoopGroup可以包含很多个EventLoop,每个Channel绑定一个EventLoop不会被改变,因为EventLoopGroup包含少量的EventLoopChannels,很多Channel会共享同一个EventLoop。这意味着在一个Channel保持EventLoop繁忙会禁止其他Channel绑定到相同的EventLoop。我们可以理解为EventLoop是一个事件循环线程,而EventLoopGroup是一个事件循环集合。

Handler

要明白Netty程序wirteread时发生了什么,首先要对Handler是什么有一定的了解。Handlers自身依赖于ChannelPipeline来决定它们执行的顺序,因此不可能通过ChannelPipeline定义怎么处理我们的程序一些业务逻辑,同理也不能通过ChannelHandler定义ChannelPipeline的行为。

ChannelHandler是一段执行业务逻辑处理数据的代码,它们来来往往的通过ChannelPipeline。实际上,ChannelHandler是定义一个handler的父接口,ChannelInboundHandlerChannelOutboundHandler都实现ChannelHandler接口,如下图:

Netty学习笔记(1)_第5张图片

Netty中有两个方向的数据流,上图显示的入站(ChannelInboundHandler)和出站(ChannelOutboundHandler),数据是从用户应用程序到远程主机则是出站(outbound)”,相反若数据时从远程主机到用户应用程序则是入站(inbound)”

为了使数据从一端到达另一端,一个或多个ChannelHandler将以某种方式操作数据。这些ChannelHandler会在程序的引导阶段被添加ChannelPipeline中,(这里的引导就是我们上面所说的创建ServerBootstrap对象)并且被添加的顺序将决定处理数据的顺序。ChannelPipeline的作用我们可以理解为用来管理ChannelHandler的一个容器,每个ChannelHandler处理各自的数据(例如入站数据只能由ChannelInboundHandler处理),处理完成后将转换的数据放到ChannelPipeline中交给下一个ChannelHandler继续处理,直到最后一个ChannelHandler处理完成。

下图显示了ChannelPipeline的处理过程:

Netty学习笔记(1)_第6张图片

上图显示ChannelInboundHandlerChannelOutboundHandler都要经过相同的ChannelPipeline

   ChannelPipeline中,如果消息被读取或有任何其他的入站事件,消息将从ChannelPipeline的头部开始传递给第一个ChannelInboundHandler,这个ChannelInboundHandler可以处理该消息或将消息传递到下一个ChannelInboundHandler中,一旦在ChannelPipeline中没有剩余的ChannelInboundHandler后,ChannelPipeline就知道消息已被所有的饿Handler处理完成了。

       反过来也是如此,任何出站事件或写入将从ChannelPipeline的尾部开始,并传递到最后一个ChannelOutboundHandlerChannelOutboundHandler的作用和ChannelInboundHandler相同,它可以传递事件消息到下一个Handler或者自己处理消息。不同的是ChannelOutboundHandler是从ChannelPipeline的尾部开始,而ChannelInboundHandler是从ChannelPipeline的头部开始,当处理完第一个ChannelOutboundHandler处理完成后会出发一些操作,比如一个写操作。

       一个事件能传递到下一个ChannelInboundHandler或上一个ChannelOutboundHandler,在ChannelPipeline中通过使用ChannelHandlerContext调用每一个方法。Netty提供了抽象的事件基类称为ChannelInboundHandlerAdapterChannelOutboundHandlerAdapter。每个都提供了在ChannelPipeline中通过调用相应的方法将事件传递给下一个Handler的方法的实现。可以覆盖父类的方法,做自己的一些操作。

当一个ChannelHandler添加到ChannelPipeline中时获得一个ChannelHandlerContextNetty中发送消息有两种方法:直接写入通道或写入ChannelHandlerContext对象。这两种方法的主要区别如下:

  • 直接写入通道导致处理消息从ChannelPipeline的尾部开始
  • 写入ChannelHandlerContext对象导致处理消息从ChannelPipeline的下一个handler开始

编码器、解码器和业务逻辑:细看Handlers

Netty提供了一系列的“Adapter”类,这让事情变的很简单。每个handler负责转发时间到ChannelPipeline的下一个handler。在*Adapter(和子类)中是自动完成的,因此我们只需要在感兴趣的*Adapter中重写方法。

Netty有一下适配器:

  • ChannelHandlerAdapter
  • ChannelInboundHandlerAdapter
  • ChannelOutboundHandlerAdapter

我们都知道网络中只能传输的是字节。所以在netty发送或接收消息后,Netty必须将消息数据从一种形式转化为另一种。接收消息后,需要将消息从字节码转成Java对象(由某种解码器解码);发送消息前,需要将Java对象转成字节(由某些类型的编码器进行编码)

一般做游戏开发使用的协议都是简单的基本类型,所以使用字节编解码器就足够了。Netty默认自己提供了ByteToMessageDecoderMessageToByteEncoder。还有Google的协议“ProtobufEncoder”“ProtobufDecoder”

其实针对跨语言平台比较简单的方法就是统一用JSON作为数据的格式。

传输方式

  NIO传输是目前最常用的方式,它通过使用选择器提供了完全异步的方式操作所有的I/ONIOJava 1.4才被提供。NIO中,我们可以注册一个通道或获得某个通道的改变的状态,通道状态有下面几种改变:

  • 一个新的Channel被接受并已准备好
  • Channel连接完成
  • Channel中有数据并已准备好读取
  • Channel发送数据出去

       处理完改变的状态后需重新设置他们的状态,用一个线程来检查是否有已准备好的Channel,如果有则执行相关事件。在这里可能只同时一个注册的事件而忽略其他的。选择器所支持的操作在SelectionKey中定义,具体如下:

  • OP_ACCEPT,有新连接时得到通知
  • OP_CONNECT,连接完成后得到通知
  • OP_READ,准备好读取数据时得到通知
  • OP_WRITE,写入数据到通道时得到通知
Netty 中的 NIO 传输就是基于这样的模型来接收和发送数据,通过封装将自己的接口提供给用户使用,这完全隐藏了内部实现。如前面所说, Netty 隐藏内部的实现细节,将抽象出来的 API 暴露出来供使用,下面是处理流程图:

Netty学习笔记(1)_第7张图片

 NIO在处理过程也会有一定的延迟,若连接数不大的话,延迟一般在毫秒级,但是其吞吐量依然比OIO模式的要高。Netty中的NIO传输是“zero-file-copy”,也就是零文件复制,这种机制可以让程序速度更快,更高效的从文件系统中传输内容,零复制就是我们的应用程序不会将发送的数据先复制到JVM堆栈在进行处理,而是直接从内核空间操作。接下来我们将讨论OIO传输,它是阻塞的。

  OIO就是java中提供的Socket接口,java最开始只提供了阻塞的Socket,阻塞会导致程序性能低。下面是OIO的处理流程图,若想详细了解,可以参阅其他相关资料。

Netty学习笔记(1)_第8张图片
  • OIO,在低连接数、需要低延迟时、阻塞时使用
  • NIO,在高连接数时使用










你可能感兴趣的:(Java,Netty)