netty组件总结

Channel

Channel是Java NIO的一个基本构造,和Jdk的Channel不同,jdk的范围更广,包括了文件读写,netty只针对socket网络编程。

它代表一个到实体(如一个硬件设备、一个文件、一个网络套接字或者一个能够执行一个或者多个不同的IO操作的程序组件)的开放连接,如读操作和写操作

目前,可以把Channel 看作是传入(入站)或者传出(出站)数据的载体。因此,它可以被打开或者被关闭,连接或者断开连接。

实现基本的I/O操作(bind()、connect()、read()和write()依赖于底层网络传输所提供的原语。在基于Java的网络编程中,其基本的构造是class Socket。Netty 的Channel 接口所提供的API,被用于所有的I/O 操作。大大地降低了直接使用Socket 类的复杂性。此外,Channel也是拥有许多预定义的、专门化实现的广泛类层次结构的根。

每个Channel 都将会被分配一个ChannelPipeline和ChannelConfig。ChannelConfig包含了该Channel的所有配置设置,并且支持热更新。由于Channel是独一无二的,所以为了保证顺序将Channel声明为java.lang.Comparable的一个子接口。因此,如果两个不同的Channel实例都返回了相同的散列码,那么AbstractChannel中的compareTo()方法的实现将会抛出一个Error。

	Channel的生命周期状态:
		ChannelUnregistered Channel:已经被创建,但还未注册到EventLoop
		ChannelRegistered Channel:已经被注册到了EventLoop
		ChannelActive Channel:处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
		ChannelInactive Channel:没有连接到远程节点

	最重要Channel的方法:
		eventLoop:返回分配给Channel的EventLoop
		pipeline:返回分配给Channel的ChannelPipeline
		isActive:如果Channel是活动的,则返回true。活动的意义可能依赖于底层的传输。例如,一个Socket传输一旦连接到了远程节点便是活动的,而一个Datagram 传输一旦被打开便是活动的。
		localAddress:返回本地的SokcetAddress
		remoteAddress:返回远程的SocketAddress
		write:将数据写到远程节点。这个数据将被传递给ChannelPipeline,并且排队直到它被冲刷
		flush:将之前已写的数据冲刷到底层传输,如一个Socket
		writeAndFlush:一个简便的方法,等同于调用write()并接着调用flush()

EventLoop和EventLoopGroup

EventLoop定义了Netty 的核心抽象,用于处理连接的生命周期中所发生的事件。io.netty.util.concurrent包构建在JDK 的java.util.concurrent包上。一个EventLoop将由一个永远都不会改变的Thread驱动,同时任务(Runnable或者Callable)可以直接提交给EventLoop 实现,以立即执行或者调度执行。

根据配置和可用核心的不同,可能会创建多个EventLoop实例用以优化资源的使用,并且单个EventLoop可能会被指派用于服务多个Channel。
Netty的EventLoop在继承了ScheduledExecutorService的同时,只定义了一个方法,parent()。在Netty 4 中,所有的I/O操作和事件都由已经被分配给了EventLoop的那个Thread来处理。

		任务调度:
			偶尔,你将需要调度一个任务以便稍后(延迟)执行或者周期性地执行。例如,你可能想要注册一个在客户端已经连接了5 分钟之后触发的任务。一个常见的用例是,发送心跳消息到远程节点,以检查连接是否仍然还活着。如果没有响应,你便知道可以关闭该Channel 了。
			在内部,当提交任务到如果(当前)调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被(直接)执行。否则,EventLoop将调度该任务以便稍后执行,并将它放入到内部队列中。当EventLoop下次处理它的事件时,它会执行队列中的那些任务/事件。

		异步传输:
			异步传输实现只使用了少量的EventLoop(以及和它们相关联的Thread),而且在当前的线程模型中,它们可能会被多个Channel 所共享。这使得可以通过尽可能少量的Thread来支撑大量的Channel,而不是每个Channel分配一个Thread。EventLoopGroup 负责为每个新创建的Channel 分配一个EventLoop。在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的EventLoop可能会被分配给多个Channel。
			一旦一个Channel 被分配给一个EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的Thread)。请牢记这一点,因为它可以使你从担忧你的Channel-Handler 实现中的线程安全和同步问题中解脱出来。
			另外,需要注意的是,EventLoop 的分配方式对ThreadLocal的使用的影响。因为一个EventLoop通常会被用于支撑多个Channel,所以对于所有相关联的Channel来说,ThreadLocal都将是一样的。这使得它对于实现状态追踪等功能来说是个糟糕的选择。然而,在一些无状态的上下文中,它仍然可以被用于在多个Channel之间共享一些重度的或者代价昂贵的对象,甚至是事件。

ChannelFuture接口

Netty 中所有的I/O 操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此,Netty 提供了ChannelFuture 接口,其addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。可以将ChannelFuture 看作是将来要执行的操作的结果的占位符。它究竟什么时候被执行则可能取决于若干的因素,因此不可能准确地预测,但是可以肯定的是它将会被执行。

ctx.write()和channel().write()的区别

如果我们是使用channel().write()方法。可以不用考虑outbound和inbound的添加顺序。每次都会从tail往前找第一个是outbound的handler来执行。
而ctx.write()则是从当前outbound或inbound开始执行。

ChannelHandler接口

从应用程序开发人员的角度来看,Netty的主要组件是ChannelHandler,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 的方法是由网络事件触发的。事实上,ChannelHandler 可专门用于几乎任何类型的动作,例如将数据从一种格式转换为另外一种格式,例如各种编解码,或者处理转换过程中所抛出的异常。

举例来说,ChannelInboundHandler 是一个你将会经常实现的子接口。这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。当你要给连接的客户端发送响应时,也可以从ChannelInboundHandler冲刷数据。你的应用程序的业务逻辑通常驻留在一个或者多个ChannelInboundHandler 中。

这种类型的ChannelHandler 接收入站事件和数据,这些数据随后将会被你的应用程序的业务逻辑所处理。

			ChannelHandler 的生命周期
				下面列出了interface ChannelHandler 定义的生命周期操作,在ChannelHandler被添加到ChannelPipeline 中或者被从ChannelPipeline 中移除时会调用这些操作。这些方法中的每一个都接受一个ChannelHandlerContext 参数。
				handlerAdded 当把ChannelHandler 添加到ChannelPipeline 中时被调用
				handlerRemoved 当从ChannelPipeline 中移除ChannelHandler 时被调用
				exceptionCaught 当处理过程中在ChannelPipeline 中有错误产生时被调用
				Netty 定义了下面两个重要的ChannelHandler 子接口:
				ChannelInboundHandler——处理入站数据以及各种状态变化;
				ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

ChannelInboundHandler接口和ChannelOutboundHandler接口

		ChannelInboundHandler接口:处理入站事件
		ChannelOutboundHandler接口:处理出站事件

ChannelHandler的适配器

有一些适配器类可以将编写自定义的ChannelHandler 所需要的努力降到最低限度,因为它们提供了定义在对应接口中的所有方法的默认实现。因为你有时会忽略那些不感兴趣的事件,所以Netty提供了抽象基类ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter。

ChannelPipeline 接口

当Channel 被创建时,它会被自动地分配到它专属的ChannelPipeline。每一个新创建的Channel都将会被分配一个新的ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个ChannelPipeline,也不能分离其当前的。在Netty组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。

使得事件流经ChannelPipeline 是ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个ChannelHandler。它们的执行顺序是由它们被添加的顺序所决定的。

入站和出站ChannelHandler可以被安装到同一个ChannelPipeline中。如果一个消息或者任何其他的入站事件被读取,那么它会从ChannelPipeline 的头部开始流动,最终,数据将会到达ChannelPipeline 的尾端,届时,所有处理就都结束了。

数据的出站运动(即正在被写的数据)在概念上也是一样的。在这种情况下,数据将从ChannelOutboundHandler 链的尾端开始流动,直到它到达链的头部为止。在这之后,出站数据将会到达网络传输层,这里显示为Socket。通常情况下,这将触发一个写操作。

如果将两个类别的ChannelHandler都混合添加到同一个ChannelPipeline中会发生什么。虽然ChannelInboundHandle和ChannelOutboundHandle 都扩展自ChannelHandler,但是Netty 能区分ChannelInboundHandler实现和ChannelOutboundHandler实现,并确保数据只会在具有相同定向类型的两个ChannelHandler 之间传递。

ChannelHandlerContext

通过使用作为参数传递到每个方法的ChannelHandlerContext,事件可以被传递给当前ChannelHandler链中的下一个ChannelHandler。虽然这个对象可以被用于获取底层的Channel,但是它主要还是被用于写出站数据。

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联,每当有ChannelHandler添加到ChannelPipeline 中时,都会创建ChannelHandler-Context。ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和在同一个ChannelPipeline 中的其他ChannelHandler 之间的交互。

ChannelHandlerContext有很多的方法,其中一些方法也存在于Channel和Channel-Pipeline本身上,但是有一点重要的不同。如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个ChannelPipeline进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联的ChannelHandler开始,并且只会传播给位于该ChannelPipeline中的下一个(入站下一个,出站上一个)能够处理该事件的ChannelHandler。

选择合适的内置通信传输模式

		NIO: 使用java.nio.channels 包作为基础——基于选择器的方式
		Epoll: 由 JNI 驱动的 epoll()和非阻塞 IO。这个传输支持只有在Linux 上可用的多种特性,如SO_REUSEPORT,比NIO传输更快,而且是完全非阻塞的。将NioEventLoopGroup替换为EpollEventLoopGroup,并且将NioServerSocketChannel.class替换为EpollServerSocketChannel.class 即可。
		OIO: 使用java.net 包作为基础——使用阻塞流
		Local: 可以在VM 内部通过管道进行通信的本地传输
		Embedded:Embedded传输,允许使用ChannelHandler而又不需要一个真正的基于网络的传输。在测试ChannelHandler实现时非常有用

启动类Bootstrap

有两种类型的引导:一种用于客户端(简单地称为Bootstrap),而另一种(ServerBootstrap)用于服务器。无论你的应用程序使用哪种协议或者处理哪种类型的数据,唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。

ServerBootstrap 将绑定到一个端口,因为服务器必须要监听连接,而Bootstrap 则是由想要连接到远程节点的客户端应用程序所使用的。

第二个区别可能更加明显。引导一个客户端只需要一个EventLoopGroup,但是一个ServerBootstrap则需要两个(也可以是同一个实例)。

因为服务器需要两组不同的Channel。第一组将只包含一个ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。而第二组将包含所有已创建的用来处理传入客户端连接(对于每个服务器已经接受的连接都有一个)的Channel。

与ServerChannel相关联的EventLoopGroup将分配一个负责为传入连接请求创建Channel的EventLoop。一旦连接被接受,第二个EventLoopGroup 就会给它的Channel分配一个EventLoop。

ChannelOption

		可以配置各种属性

ByteBuf缓冲区

		ByteBuf API 的优点:
		它可以被用户自定义的缓冲区类型扩展;
		通过内置的复合缓冲区类型实现了透明的零拷贝;
		容量可以按需增长(类似于JDK 的StringBuilder);
		在读和写这两种模式之间切换不需要调用ByteBuffer 的flip()方法;
		读和写使用了不同的索引;
		支持方法的链式调用;
		支持引用计数;
		支持池化。
		ByteBuf 维护了两个不同的索引,名称以read 或者write 开头的ByteBuf 方法,将会推进其对应的索引,而名称以set或者get 开头的操作则不会。
		如果打算读取字节直到readerIndex 达到和writerIndex 同样的值时会发生什么。在那时,你将会到达“可以读取的”数据的末尾。就如同试图读取超出数组末尾的数据一样,试图读取超出该点的数据将会触发一个IndexOutOf-BoundsException。可以指定ByteBuf 的最大容量。试图移动写索引(即writerIndex)超过这个值将会触发一个异常。(默认的限制是Integer.MAX_VALUE。)

		分配:
			堆缓冲区:
				最常用的ByteBuf 模式是将数据存储在JVM 的堆空间中。这种模式被称为支撑数组(backingarray),它能在没有使用池化的情况下提供快速的分配和释放。可以由hasArray()来判断检查ByteBuf是否由数组支撑。如果不是,则这是一个直接缓冲区。

			直接缓冲区:
				直接缓冲区是另外一种ByteBuf 模式。
				直接缓冲区的主要缺点是,相对于基于堆的缓冲区,它们的分配和释放都较为昂贵。如果你正在处理遗留代码,你也可能会遇到另外一个缺点:因为数据不是在堆上,所以你不得不进行一次复制。 
				显然,与使用支撑数组相比,这涉及的工作更多。因此,如果事先知道容器中的数据将会被作为数组来访问,你可能更愿意使用堆内存。


		ByteBuf与JDK中的ByteBuffer的区别: 
			(1)netty的ByteBuf采用了读/写索引分离,一个初始化的ByteBuf的readerIndex和writerIndex都处于0位置。 
			(2)当读索引和写索引处于同一位置时,如果我们继续读取,就会抛出异常IndexOutOfBoundsException。 
			(3)对于ByteBuf的任何读写操作都会分别单独的维护读索引和写索引。maxCapacity最大容量默认的限制就是Integer.MAX_VALUE。

猜你喜欢

转载自blog.csdn.net/qq_28822933/article/details/83713540