flame

路漫漫其修远兮 吾将上下而求索

0%

网络编程框架-Netty组件

前言

Netty是一款异步事件驱动网络应用程序框架,本文梳理了Netty的主要组件。

概述

Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端,并且提供了丰富的网络编程工具集。

开发一套高性能的网络系统,需要超一流的编程技巧和几个复杂领域(网络编程、多线程处理和并发)的专业知识,在高负载下可靠和高效地处理和调度 I/O 操作也是一项繁琐而且容易出错的任务。

在Java领域,直接使用Java NIO API也能构建出正确和安全的网络应用程序,但并不容易。而Netty将高超的编程技巧和复杂的专业知识隐藏在易于使用的API之后, 使得网络编程新手也能使用。


基于异步和事件驱动的设计

Netty的关键设计是异步和事件驱动,可以保证可伸缩性。

可伸缩性(Scalability)的定义:The ability of a system, network, or process to handle a growing amount of work in a capable manner or its ability to be enlarged to accommodate that growth.

通过合理的设计,Netty可以使应用程序逻辑独立于任何网络操作而独立地演变。

4种主要构件块:

  • Channel

  • Callback

  • Future

  • Event和ChannelHandler

Channel

A Channel is a basic construct of Java NIO.

It represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, for example reading or writing.

Callback

A callback is simply a method, a reference to which has been provided to another method. This enables the latter to call the former at an appropriate time.

Future

A Future acts as a placeholder for the result of an asynchronous operation; it will complete at some point in the future and provide access to the result.

Event和ChannelHandler

Netty uses distinct events to notify us about changes of state or the status of operations. Every event can be dispatched to a user-implemented method of a channelHandler instance.

ChannelHandler: can think of each handler instance as a kind of callback to be executed in response to a specific event.


Future

Future代表异步操作的结果,可以看作是将来要执行操作的结果的占位符。

Netty中两种Future类型是: ChannelFuture 、Promise。

Future提供的方法

io.netty.util.concurrent.Future接口

ChannelFuture

ChannelFuture可以通过添加监听器,来获取I/O操作结果或者进行后续的相关操作

ChannelFuture有两种状态:Uncompleted 和 Completed,状态迁移如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
ChannelFuture的状态迁移

* +---------------------------+
* | Completed successfully |
* +---------------------------+
* +----> isDone() = true |
* +--------------------------+ | | isSuccess() = true |
* | Uncompleted | | +===========================+
* +--------------------------+ | | Completed with failure |
* | isDone() = false | | +---------------------------+
* | isSuccess() = false |----+----> isDone() = true |
* | isCancelled() = false | | | cause() = non-null |
* | cause() = null | | +===========================+
* +--------------------------+ | | Completed by cancellation |
* | +---------------------------+
* +----> isDone() = true |
* | isCancelled() = true |
* +---------------------------+

ChannelFuture提供的方法

io.netty.util.concurrent.ChannelFuture接口

Promise

Promise对Future进行了扩展,用于设置I/O操作结果。Netty发起I/O操作时,会创建一个新的Promise对象,当I/O操作发送异常或者完成时,设置Promise结果。

Promise提供的方法

io.netty.util.concurrent.Promise接口

Channel

Channel接口被用于所有的I/O操作,其实现是线程安全的,可以在并发环境下使用。

Channel的生命周期

Channel定义了一组和ChannelInboundHandler API密切相关的状态模型,当这些状态发生改变时,将会触发对应的事件。

这些事件将会被转发给ChannelPipeline中的ChannelHandler,做出对应的响应。

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

Channel的状态模型如下图

Channel的状态模型

Channel的层次结构

Channel接口的层次结构

每个Channel都将会被分配一个ChannelConfig和ChannelPipeline。

ChannelConfig包含了该Channel的所有配置设置,并且支持热更新。

ChannelPipeline持有所有的ChannelHandlers,这些ChannelHandler实现了应用程序用于处理状态变化以及处理数据的逻辑。

Channel的重要方法

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

Channel的分类

在选择Channel的具体实现时,必须选择和应用程序的协议相容的Channel实现。

名称 package 描述 常用的实现
NIO io.netty.channel.socket.nio 使用java.nio.channels包作为基础——基于选择器的方式 NioSocketChannel NioServerSocketChannel NioDatagramChannel
Epoll io.netty.channel.epoll 由 JNI驱动的epoll()和非阻塞 IO。这个传输支持只有在Linux上可用的多种特性,如SO_REUSEPORT, 比 NIO 传输更快,而且是完全非阻塞的 EpollSocketChannel EpollServerSocketChannel
OIO io.netty.channel.socket.oio 使用 java.net 包作为基础——使用阻塞流 OioSocketChannel OioServerSocketChannel
Local io.netty.channel.local 可以在VM内部通过管道进行通信的本地传输 LocalChannel LocalServerChannel
Embedded io.netty.channel.embedded Embedded 传输,允许使用 ChannelHandler 而又 不需要一个真正的基于网络的传输。这在测试你的 ChannelHandler 实现时非常有用 EmbeddedChannel

NIO Channel

NIO Channel提供了一个所有I/O操作的全异步的实现,底层是基于JDK选择器的实现。

选择器可以选择操作的位模式:组合起来定义一组应用程序正在请求通知的状态变化集

名称 描述
OP_ACCEPT 请求在接受新连接并创建 Channel 时获得通知
OP_CONNECT 请求在建立一个连接时获得通知
OP_READ 请求当数据已经就绪,可以从 Channel 中读取时获得通知
OP_WRITE 请求当可以向 Channel 中写更多的数据时获得通知。这处理了套接字缓冲区被完 全填满时的情况,这种情况通常发生在数据的发送速度比远程节点可处理的速度更 快的时候

选择器运行在一个检查状态变化并对其做出相应响应的线程上,在应用程序对状态的改变做出响应之后,选择器将会被重置,并将重复这个过程。

NIO Channel选择器处理的内部细节


Epoll Channel

Epoll Channel是用于 Linux 的本地非阻塞的Channel。

Linux中的epoll API具备一个高度可扩展的I/O事件通知特性,比旧的POSIX select和poll系统调用性能更好,同时现在也是Linux上非阻 塞网络编程的事实标准。

Netty Epoll Channel 的实现方式和 linux-epoll 的特性更加一致,并且以一种更加轻量的方式使用中断。如果应用程序准备在Linux平台允许,可以考虑使用Epoll Channel,高负载下它的性能要优于JDK的NIO实现。


OIO Channel

OIO Channel建立在 java.net 包的阻塞实现之上,不是异步的。

当需要移植使用了阻塞调用的库(例如JDBC),暂时无法直接将逻辑转换为非阻塞,可以考虑先使用OIO Channel重构,然后再移植到纯粹的异步传输上。

OIO Channel的处理逻辑


Local Channel

Local Channel 用于在同一个 JVM 中运行的客户端和服务器程序之间的异步通信。

Local Server Channel相关联的SocketAddress并没有绑定物理网络地址,不接受真正的网络流量。Local Server Channel会被放在一个注册表里,在关闭时注销。


Embedded Channel

Embedded Channel可以为ChannelHandler的实现创建单元测试用例。

Embedded Channel可以将一组ChannelHandle 作为帮助器类嵌入到其他的ChannelHandler内部。

通过这种方式,可以扩展一个ChannelHandler 的功能, 而又不需要修改其内部代码。

ByteBuf

ByteBuf是Netty的数据容器。

因为所有的网络通信都涉及字节序列的移动,所以高效易用的数据结构明显是必不可少的。

ByteBuf解决了JDK ByteBuffer的局限性, 又为网络应用程序的开发者提供了更好的API。

ChannelHandler

ChannelHandler用于处理入站和出站数据,它的方法是由网络事件触发的。

ChannelHandler生命周期

Interface ChannelHandler定义了生命周期操作, 在ChannelHandler被添加到ChannelPipeline中或者被从ChannelPipeline中移除时会调用这些操作。

类型 描述
handlerAdded(ChannelHandlerContext ctx) 当把 ChannelHandler 添加到 ChannelPipeline 中时被调用
handlerRemoved(ChannelHandlerContext ctx) 当从 ChannelPipeline 中移除 ChannelHandler 时被调用
exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 当处理过程中在 ChannelPipeline 中有错误产生时被调用

ChannelInboundHandler

ChannelInboundHandler用于处理入站数据以及各种状态变化。

ChannelInboundHandler Interface定义的生命周期方法,将会在数据被接收时或者与其对应的Channel 状态发生改变时被调用。

当某个ChannelInboundHandler的实现重写channelRead()方法时,要负责显式地释放与池化的ByteBuf实例相关的内存。

ChannelInboundHandler的实现之一SimpleChannelInboundHandler重写了channelRead,会自动释放资源。

ChannelInboundHandler的生命周期方法

类型 描述
channelRegistered 当 Channel 已经注册到它的 EventLoop 并且能够处理 I/O 时被调用
channelUnregistered 当 Channel 从它的 EventLoop 注销并且无法处理任何 I/O 时被调用
channelActive 当 Channel 处于活动状态时被调用;Channel 已经连接/绑定并且已经就绪
channelInactive 当 Channel 离开活动状态并且不再连接它的远程节点时被调用
channelReadComplete 当Channel上的一个读操作完成时被调用。 当所有可读的字节都已经从 Channel 中读取之后,将会调用该回调方法;所以,可能在channelReadComplete()被调用之前看到多次调用 channelRead(…)
channelRead 当从Channel读取数据时被调用 : Invoked when the current Channel has read a message from the peer.
channelWritabilityChanged 当Channel的可写状态发生改变时被调用。用户可以确保写操作不会完成得太快(以避免发生 OutOfMemoryError)或者可以在 Channel 变为再次可写时恢复写入。可以通过调用 Channel 的 isWritable()方法来检测 Channel 的可写性。与可写性相关的阈值可以通过 Channel.config(). setWriteHighWaterMark()和 Channel.config().setWriteLowWaterMark()方法来设置
userEventTriggered 当 ChannelnboundHandler.fireUserEventTriggered()方法被调用时被调用,因为一个 POJO 被传经了 ChannelPipeline

ChannelOutboundHandler

ChannelOutboundHandler用于处理出站数据并且允许拦截所有的操作。

ChannelOutboundHandler的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如,如果到远程节点的写入被暂停了,那么你可以推迟冲 刷操作并在稍后继续。

ChannelOutboundHandler的重要方法

类型 描述
bind(ChannelHandlerContext, SocketAddress,ChannelPromise) 当请求将 Channel 绑定到本地地址时被调用
connect(ChannelHandlerContext, SocketAddress,SocketAddress,ChannelPromise) 当请求将 Channel 连接到远程节点时被调用
disconnect(ChannelHandlerContext, ChannelPromise) 当请求将 Channel 从远程节点断开时被调用
close(ChannelHandlerContext,ChannelPromise) 当请求关闭 Channel 时被调用
deregister(ChannelHandlerContext, ChannelPromise) 当请求将 Channel 从它的 EventLoop 注销时被调用
read(ChannelHandlerContext) 当请求从 Channel 读取更多的数据时被调用,Intercept ChannelHandlerContext#read().
flush(ChannelHandlerContext) 当请求通过 Channel 将入队数据冲刷到远程节点时被调用
write(ChannelHandlerContext,Object, ChannelPromise) 当请求通过 Channel 将数据写到远程节点时被调用

ChannelHandler适配器

ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter分别提供了ChannelInboundHandler和ChannelOutboundHandler的基本实现。

通过扩展抽象类ChannelHandlerAdapter,它们获得了它们共同的接口ChannelHandler的方法。

ChannelHandlerAdapter还提供了实用方法isSharable(),如果其对应的实现被标注为Sharable,那么这个方法将返回 true,表示它可以被添加到多个ChannelPipeline中

在ChannelInboundHandlerAdapter和ChannelOutboundHandlerAdapter中所提供的方法体调用了其相关联的ChannelHandlerContext上的等效方法,从而将事件转发到了ChannelPipeline中的下一个ChannelHandler中。

ChannelHandlerAdapter的层次结构

ChannelHandlerAdapter的层次结构

ChannelPipeline

ChannelPipeline提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出站事件流的API。

可以把ChannelPipeline看作一个拦截流经Channel的入站和出站事件的ChannelHandler实例链,这些ChannelHandler之间的交互组成了一个应用程序数据和事件处理逻辑的核心。

ChannelPipeline保存了与Channel相关联的ChannelHandler。

ChannelPipeline可以根据需要,通过添加或者删除ChannelHandler来动态地修改。

ChannelPipeline着丰富的API用以被调用,以响应入站和出站事件。

A list of {ChannelHandler}s which handles or intercepts inbound events and outbound operations of a Channel.
ChannelPipeline implements an advanced form of the Intercepting Filter pattern to give a user full control over how an event is handled and how the {ChannelHandler}s in a pipeline interact with each other.

ChannelPipeline的创建

每一个新创建的Channe实例会被分配一个新的永久性关联的ChannelPipeline实例,之后既不能附加其他ChannelPipeline实例,也不能分离其当前的。

ChannelHandler安装到ChannelPipeline 中的过程:

  • 一个ChannelInitializer的实现被注册到了ServerBootstrap或者Bootstrap中

  • 当 ChannelInitializer.initChannel()方法被调用时,ChannelInitializer将在 ChannelPipeline 中安装一组自定义的 ChannelHandler

  • ChannelInitializer将它自己从 ChannelPipeline中移除。

和ChannelHandler关系

在ChannelPipeline传播事件时,它会测试ChannelPipeline中下一个ChannelHandler的类型是否和事件的运动方向相匹配。如果不匹配,ChannelPipeline将跳过该ChannelHandler并前进到下一个,直到它找到和该事件所期望的方向相匹配的为止。

ChannelPipeline 和它的 ChannelHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
* For example, let us assume that we created the following pipeline:
* {ChannelPipeline} p = ...;
* p.addLast("1", new InboundHandlerA());
* p.addLast("2", new InboundHandlerB());
* p.addLast("3", new OutboundHandlerA());
* p.addLast("4", new OutboundHandlerB());
* p.addLast("5", new InboundOutboundHandlerX());
* In the given example configuration,
* the handler evaluation order is 1, 2, 3, 4, 5 when an event goes inbound.
* When an event goes outbound, the order is 5, 4, 3, 2, 1.
*
*
* The actual evaluation order of an inbound event will be: 1, 2, and 5.
* The actual evaluation order of a outbound event will be: 5, 4, and 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
How an event flows in a pipeline ?

* I/O Request
* via {Channel} or
* {ChannelHandlerContext}
* |
* +---------------------------------------------------+---------------+
* | ChannelPipeline | |
* | \|/ |
* | +---------------------+ +-----------+----------+ |
* | | Inbound Handler N | | Outbound Handler 1 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* | | \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler N-1 | | Outbound Handler 2 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ . |
* | . . |
* | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
* | [ method call] [method call] |
* | . . |
* | . \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler 2 | | Outbound Handler M-1 | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* | | \|/ |
* | +----------+----------+ +-----------+----------+ |
* | | Inbound Handler 1 | | Outbound Handler M | |
* | +----------+----------+ +-----------+----------+ |
* | /|\ | |
* +---------------+-----------------------------------+---------------+
* | \|/
* +---------------+-----------------------------------+---------------+
* | | | |
* | [ Socket.read() ] [ Socket.write() ] |
* | |
* | Netty Internal I/O Threads (Transport Implementation) |
* +-------------------------------------------------------------------+
*

ChannelPipeline修改

ChannelHandler可以通过添加、删除或者替换其他的ChannelHandler来实时地修改ChannelPipeline的布局。

  • ChannelHandler用于修改ChannelPipeline的方法
名称 描述
addFirst addBefore addAfter addLast 将一个ChannelHandler 添加到ChannelPipeline 中
remove 将一个ChannelHandler 从ChannelPipeline 中移除
replace 将ChannelPipeline中的一个ChannelHandler替换为另一个ChannelHandler
  • ChannelPipeline用于访问ChannelHandler的操作
名称 描述
get 通过类型或者名称返回 ChannelHandler
context 返回和 ChannelHandler 绑定的 ChannelHandlerContext
names 返回 ChannelPipeline 中所有 ChannelHandler 的名称

ChannelHandler 的执行和阻塞

通常 ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的 I/O 处理产生负面的影响。

但有时可能需要与那些使用阻塞 API 的遗留代码进行交互。对于这种情况,ChannelPipeline有一些接受一个EventExecutorGroup 的 add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup,它将被包含在这个EventExecutorGroup 中的某个 EventExecutor 所处理,从而被从该 Channel 本身的 EventLoop 中移除。对于这种用例,Netty提供了一个叫 DefaultEventExecutorGroup 的默认实现。

ChannelPipeline的触发

ChannelPipeline的API提供了调用入站和出站操作的方法。

ChannelPipeline API的入站操作

方法 描述
fireChannelRegistered 调用ChannelPipeline中下一个ChannelInboundHandler的channelRegistered(ChannelHandlerContext)方法
fireChannelUnregistered 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelUnregistered(ChannelHandlerContext)方法
fireChannelActive 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelActive(ChannelHandlerContext)方法
fireChannelInactive 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelInactive(ChannelHandlerContext)方法
fireExceptionCaught 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 exceptionCaught(ChannelHandlerContext, Throwable)方法
fireUserEventTriggered 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 userEventTriggered(ChannelHandlerContext, Object)方法
fireChannelRead 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelRead(ChannelHandlerContext, Object msg)方法
fireChannelReadComplete 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelReadComplete(ChannelHandlerContext)方法
fireChannelWritabilityChanged 调用 ChannelPipeline 中下一个 ChannelInboundHandler 的 channelWritabilityChanged(ChannelHandlerContext)方法

ChannelPipeline API的出站操作

方法 描述
bind 将Channel绑定到一个本地地址,这将调用ChannelPipeline中的下一个ChannelOutboundHandler的 bind(ChannelHandlerContext, SocketAddress, ChannelPromise)方法
connect 将 Channel 连接到一个远程地址,这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 connect(ChannelHandlerContext, SocketAddress, ChannelPromise)方法
disconnect 将Channel 断开连接。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的 disconnect(ChannelHandlerContext, Channel Promise)方法
close 将 Channel 关闭。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 close(ChannelHandlerContext, ChannelPromise)方法
deregister 将 Channel 从它先前所分配的 EventExecutor(即 EventLoop)中注销。这将调 用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 deregister (ChannelHandlerContext, ChannelPromise)方法
flush 冲刷Channel所有挂起的写入。这将调用ChannelPipeline中的下一个ChannelOutboundHandler 的 flush(ChannelHandlerContext)方法
write 将消息写入 Channel。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler的write(ChannelHandlerContext, Object msg, ChannelPromise)方法。注意:这并不会将消息写入底层的 Socket,而只会将它放入队列中。 要将它写入 Socket,需要调用 flush()或者 writeAndFlush()方法
writeAndFlush 这是一个先调用 write()方法再接着调用 flush()方法的便利方法
read 请求从 Channel 中读取更多的数据。这将调用 ChannelPipeline 中的下一个 ChannelOutboundHandler 的 read(ChannelHandlerContext)方法

ChannelHandlerContext

ChannelHandlerContext代表了ChannelHandler和ChannelPipeline之间的关联。

每当有ChannelHandler添加到ChannelPipeline中时,都会创建ChannelHandlerContext。

ChannelHandlerContext的主要功能是管理它所关联的ChannelHandler和其他ChannelHandler(在同一个ChannelPipeline中的)之间的交互。

Channel ChannelPipeline ChannelHandler以及ChannelHandlerContext之间的关系如下图

Channel、ChannelPipeline、ChannelHandler 以及 ChannelHandlerContext 之间的关系




通过Channel或者ChannelPipeline进行的事件传播,事件将沿着整个ChannelPipeline 进行传播

通过Channel或者ChannelPipeline进行的事件传播




通过ChannelHandlerContext触发的操作的事件传播,事件只会传播给位于该ChannelPipeline中的下一个能够处理该事件的 ChannelHandler

通过ChannelHandlerContext触发的操作的事件传播


ChannelHandlerContext的API

方法 描述
alloc 返回和这个实例相关联的Channel所配置的ByteBufAllocator
bind 绑定到给定的SocketAddress,并返回ChannelFuture
channel 返回绑定到这个实例的 Channel
close 关闭Channel,并返回ChannelFuture
connect 连接给定的SocketAddress,并返回ChannelFuture
deregister 从之前分配的EventExecutor注销,并返回ChannelFuture
disconnect 从远程节点断开,并返回ChannelFuture
executor 返回调度事件的EventExecutor
fireChannelActive 触发对下一个ChannelInboundHandler上的channelActive()方法(已连接)的调用
fireChannelInactive 触发对下一个ChannelInboundHandler上的channelInactive()方法(已关闭)的调用
fireChannelRead 触发对下一个ChannelInboundHandler 上的channelRead()方法(已接收的消息)的调用
fireChannelReadComplete 触发对下一个ChannelInboundHandler上的channelReadComplete()方法的调用
fireChannelRegistered 触发对下一个ChannelInboundHandler上的fireChannelRegistered()方法的调用
fireChannelUnregistered 触发对下一个ChannelInboundHandler上的fireChannelUnregistered()方法的调用
fireChannelWritabilityChanged 触发对下一个ChannelInboundHandler上的fireChannelWritabilityChanged()方法的调用
fireExceptionCaught 触发对下一个ChannelInboundHandler上的fireExceptionCaught(Throwable)方法的调用
fireUserEventTriggered 触发对下一个ChannelInboundHandler上的fireUserEventTriggered(Object evt)方法的调用
handler 返回绑定到这个实例的ChannelHandler
isRemoved 如果所关联的ChannelHandler已经被从ChannelPipeline中移除则返回true
name 返回这个实例的唯一名称
pipeline 返回这个实例所关联的ChannelPipeline
read 将数据从Channel读取到第一个入站缓冲区;如果读取成功则触发一个channelRead事件,并(在最后一个消息被读取完成后) 通知ChannelInboundHandler的channelReadComplete (ChannelHandlerContext)方法
write 通过这个实例写入消息并经过ChannelPipeline
writeAndFlush 通过这个实例写入并冲刷消息并经过ChannelPipeline

EventLoop

EventLoop定义了Netty的核心抽象,用于处理连接的生命周期中所发生的事件。

事件循环

基本思想: 运行任务来处理在连接的生命周期内发生的事件是任何网络框架的基本功能, 与之相应的编程上的构造通常被称为事件循环。

在事件循环中执行任务

组件间的联系

Channel、EventLoop、Thread以及EventLoopGroup之间的关系

  • 一个EventLoopGroup包含一个或者多个EventLoop

  • 一个EventLoop在它的生命周期内只和一个Thread绑定

  • 所有由EventLoop处理的I/O事件都将在它专有的Thread上被处理

  • 一个Channel在它的生命周期内只注册于一个EventLoop

  • 一个 EventLoop可能会被分配给一个或多个Channel

在这种设计中,一个给定Channel的I/O操作都是由同一个Thread执行的,实际上消除了对于同步的需要。

Channel、EventLoop、Thread 以及 EventLoopGroup

类层次结构

EventLoop接口是协同设计的一部分,它采用了两个基本的 API:并发和网络编程。

  • io.netty.util.concurrent包构建在 JDK的java.util.concurrent包上进行扩展

  • io.netty.channel包中的类,为了与Channel的事件进行交互。

在这个模型中,一个EventLoop将由一个永远都不会改变的Thread驱动,同时任务可以直接提交给EventLoop以立即执行或者调度执行。

EventLoop的类层次结构

执行逻辑

Netty线程模型的卓越性能取决于对于当前执行的Thread的身份的确定。

如果当前调用线程正是支撑EventLoop的线程,那么所提交的代码块将会被直接执行。否则,EventLoop将把任务放入到内部队列中,以便稍后调度执行。

事件/任务的执行顺序

事件/任务的执行顺序事件和任务是以先进先出的顺序执行的。

这样可以通过保证字节内容总是按正确的顺序被处理,消除潜在的数据损坏的可能性。

EventLoop的执行逻辑

BootStrap

有两种类型的引导:一种用于客户端的Bootstrap,而另一种用于服务器的ServerBootstrap

无论应用程序使用哪种协议或者处理哪种类型的数据, 唯一决定它使用哪种引导类的是它是作为一个客户端还是作为一个服务器。

类别 Bootstrap ServerBootstrap
网络编程中的作用 连接到远程主机和端口 绑定到一个本地端口
EventLoopGroup的数目 1 2

ServerBootstrap需要两个EventLoopGroup,因为服务器需要两组不同的Channel。

第一组将只包含一个 ServerChannel,代表服务器自身的已绑定到某个本地端口的正在监听的套接字。

而第二组将包含所有已创建的用来处理传入客户端连接的Channel

具有两个EventLoopGroup的服务器的引导过程