-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
haiji.yang
committed
Dec 7, 2020
1 parent
24050da
commit 40f5678
Showing
29 changed files
with
1,590 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
## Netty 教程 | ||
> 来自 https://www.w3cschool.cn | ||
这里做了一部分的节选,完整的内容,大家请访问 🌤 ✈ [Netty 教程](https://www.w3cschool.cn/essential_netty_in_action/) | ||
|
||
### Netty 第一个应用 | ||
|
||
- [Netty如何写一个 echo 服务器](book/EchoServer.md) | ||
- [Netty写一个 echo 客户端](book/EchoClient.md) | ||
|
||
### Netty 架构模型的组件总览 | ||
- [Netty 快速入门](book/NettyIntroduction.md) | ||
- [Netty的Channel, Event 和 I/O](book/ChannelEventIo.md) | ||
- [Netty中 Bootstrapping 的作用](book/Bootstrapping.md) | ||
- [认识Netty的ChannelHandler 和 ChannelPipeline](book/ChannelHandlerPipeline.md) | ||
- [近距离观察Netty的ChannelHandler](book/ChannelHandler.md) | ||
|
||
### Netty核心功能介绍 | ||
|
||
#### Netty核心之Transport | ||
- [Netty案例研究:Transport 的迁移](book/Transport.md) | ||
- [基于Netty传输的API](book/NettyApi.md) | ||
- [Netty中包含的 Transport](book/NettyTransport.md) | ||
|
||
#### Netty核心之Buffer | ||
- [Netty中的Buffer API](book/BufferApi.md) | ||
- [Netty字节数据的容器ByteBuf](book/ByteBuf.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
## Netty中 Bootstrapping 的作用 | ||
|
||
**Bootstrapping**是什么?它在Netty中有什么作用呢?Bootstrapping(引导) 是出现在Netty 配置程序的过程中,Bootstrapping在给服务器绑定指定窗口或者要连接客户端的时候会使用到。 | ||
|
||
Bootstrapping 有以下两种类型: | ||
|
||
|
||
|
||
- 一种是用于客户端的Bootstrap | ||
- 一种是用于服务端的ServerBootstrap | ||
|
||
|
||
|
||
不管程序使用哪种协议,创建的是一个客户端还是服务器,“引导”都是必须要使用到的。 | ||
|
||
*面向连接 vs. 无连接* | ||
|
||
*请记住,这个讨论适用于 TCP 协议,它是“面向连接”的。这样协议保证该连接的端点之间的消息的有序输送。无连接协议发送的消息,无法保证顺序和成功性* | ||
|
||
两种 Bootstrapping 之间有一些相似之处,也有一些不同。Bootstrap 和 ServerBootstrap 之间的差异如下: | ||
|
||
**Table 3.1 Comparison of Bootstrap classes** | ||
|
||
| 分类 | Bootstrap | ServerBootstrap | | ||
| ------------------- | -------------------- | --------------- | | ||
| 网络功能 | 连接到远程主机和端口 | 绑定本地端口 | | ||
| EventLoopGroup 数量 | 1 | 2 | | ||
|
||
Bootstrap用来连接远程主机,有1个EventLoopGroup | ||
|
||
ServerBootstrap用来绑定本地端口,有2个EventLoopGroup | ||
|
||
事件组(Groups),传输(transports)和处理程序(handlers)分别在本章后面讲述,我们在这里只讨论两种"引导"的差异(Bootstrap和ServerBootstrap)。第一个差异很明显,“ServerBootstrap”监听在服务器监听一个端口轮询客户端的“Bootstrap”或DatagramChannel是否连接服务器。通常需要调用“Bootstrap”类的connect()方法,但是也可以先调用bind()再调用connect()进行连接,之后使用的Channel包含在bind()返回的ChannelFuture中。 | ||
|
||
一个 ServerBootstrap 可以认为有2个 Channel 集合,第一个集合包含一个单例 ServerChannel,代表持有一个绑定了本地端口的 socket;第二集合包含所有创建的 Channel,处理服务器所接收到的客户端进来的连接。下图形象的描述了这种情况: | ||
|
||
![Figure%203](img/1502159260674064.jpg) | ||
|
||
**Figure 3.2 Server with two EventLoopGroups** | ||
|
||
与 ServerChannel 相关 EventLoopGroup 分配一个 EventLoop 是 负责创建 Channels 用于传入的连接请求。一旦连接接受,第二个EventLoopGroup 分配一个 EventLoop 给它的 Channel。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
## Netty中的Buffer API | ||
|
||
Buffer API主要包括 | ||
|
||
ByteBuf | ||
ByteBufHolder | ||
Netty 根据 reference-counting(引用计数)来确定何时可以释放 ByteBuf 或 ByteBufHolder 和其他相关资源,从而可以利用池和其他技巧来提高性能和降低内存的消耗。这一点上不需要开发人员做任何事情,但是在开发 Netty 应用程序时,尤其是使用 ByteBuf 和 ByteBufHolder 时,你应该尽可能早地释放池资源。 Netty 缓冲 API 提供了几个优势: | ||
|
||
可以自定义缓冲类型 | ||
通过一个内置的复合缓冲类型实现零拷贝 | ||
扩展性好,比如 StringBuilder | ||
不需要调用 flip() 来切换读/写模式 | ||
读取和写入索引分开 | ||
方法链 | ||
引用计数 | ||
Pooling(池) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
## Netty字节级别的操作 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
## Netty字节数据的容器ByteBuf | ||
|
||
既然所有的网络通信都是要基于底层的字节流来传输,那么传输所使用的数据接口就要求是效率高得、使用方便的而且容易使用的,**Netty**的**ByteBuf**更好能够达到这些要求。 | ||
|
||
ByteBuf 是一个已经经过优化的很好使用的数据容器,字节数据可以有效的被添加到 ByteBuf 中或者也可以从 ByteBuf 中直接获取数据。ByteBuf中有两个索引:一个用来读,一个用来写。这两个索引达到了便于操作的目的。我们可以按顺序的读取数据,也可以通过调整读取数据的索引或者直接将读取位置索引作为参数传递给get方法来重复读取数据。 | ||
|
||
### ByteBuf 的工作原理 | ||
|
||
写入数据到 ByteBuf 后,writerIndex(写入索引)增加写入的字节数。读取字节后,readerIndex(读取索引)也增加读取出的字节数。你可以读取字节,直到写入索引和读取索引处在相同的位置。此时ByteBuf不可读,所以下一次读操作将会抛出 IndexOutOfBoundsException,就像读取数组时越位一样。 | ||
|
||
调用 ByteBuf 的以 "read" 或 "write" 开头的任何方法都将自动增加相应的索引。另一方面,"set" 、 "get"操作字节将不会移动索引位置,它们只会在指定的相对位置上操作字节。 | ||
|
||
可以给ByteBuf指定一个最大容量值,这个值限制着ByteBuf的容量。任何尝试将写入超过这个值的数据的行为都将导致抛出异常。ByteBuf 的默认最大容量限制是 Integer.MAX_VALUE。 | ||
|
||
ByteBuf 类似于一个字节数组,最大的区别是读和写的索引可以用来控制对缓冲区数据的访问。下图显示了一个容量为16的空的 ByteBuf 的布局和状态,writerIndex 和 readerIndex 都在索引位置 0 : | ||
|
||
Figure 5.1 A 16-byte ByteBuf with its indices set to 0 | ||
|
||
![img](https://waylau.gitbooks.io/essential-netty-in-action/images/Figure%205.1%20A%2016-byte%20ByteBuf%20with%20its%20indices%20set%20to%200.jpg) | ||
|
||
### ByteBuf 使用模式 | ||
|
||
#### HEAP BUFFER(堆缓冲区) | ||
|
||
最常用的模式是 ByteBuf 将数据存储在 JVM 的堆空间,这是通过将数据存储在数组的实现。堆缓冲区可以快速分配,当不使用时也可以快速释放。它还提供了直接访问数组的方法,通过 ByteBuf.array() 来获取 byte[]数据。 这种方法,正如清单5.1中所示的那样,是非常适合用来处理遗留数据的。 | ||
|
||
Listing 5.1 Backing array | ||
|
||
``` | ||
ByteBuf heapBuf = ...; | ||
if (heapBuf.hasArray()) { //1 | ||
byte[] array = heapBuf.array(); //2 | ||
int offset = heapBuf.arrayOffset() + heapBuf.readerIndex(); //3 | ||
int length = heapBuf.readableBytes();//4 | ||
handleArray(array, offset, length); //5 | ||
} | ||
``` | ||
|
||
1.检查 ByteBuf 是否有支持数组。 | ||
|
||
2.如果有的话,得到引用数组。 | ||
|
||
3.计算第一字节的偏移量。 | ||
|
||
4.获取可读的字节数。 | ||
|
||
5.使用数组,偏移量和长度作为调用方法的参数。 | ||
|
||
注意: | ||
|
||
- 访问非堆缓冲区 ByteBuf 的数组会导致UnsupportedOperationException, 可以使用 ByteBuf.hasArray()来检查是否支持访问数组。 | ||
- 这个用法与 JDK 的 ByteBuffer 类似 | ||
|
||
#### DIRECT BUFFER(直接缓冲区) | ||
|
||
“直接缓冲区”是另一个 ByteBuf 模式。对象的所有内存分配发生在 堆,对不对?好吧,并非总是如此。在 JDK1.4 中被引入 NIO 的ByteBuffer 类允许 JVM 通过本地方法调用分配内存,其目的是 | ||
|
||
- 通过免去中间交换的内存拷贝, 提升IO处理速度; 直接缓冲区的内容可以驻留在垃圾回收扫描的堆区以外。 | ||
- DirectBuffer 在 -XX:MaxDirectMemorySize=xxM大小限制下, 使用 Heap 之外的内存, GC对此”无能为力”,也就意味着规避了在高负载下频繁的GC过程对应用线程的中断影响.(详见http://docs.oracle.com/javase/7/docs/api/java/nio/ByteBuffer.html.) | ||
|
||
这就解释了为什么“直接缓冲区”对于那些通过 socket 实现数据传输的应用来说,是一种非常理想的方式。如果你的数据是存放在堆中分配的缓冲区,那么实际上,在通过 socket 发送数据之前,JVM 需要将先数据复制到直接缓冲区。 | ||
|
||
但是直接缓冲区的缺点是在内存空间的分配和释放上比堆缓冲区更复杂,另外一个缺点是如果要将数据传递给遗留代码处理,因为数据不是在堆上,你可能不得不作出一个副本,如下: | ||
|
||
Listing 5.2 Direct buffer data access | ||
|
||
``` | ||
ByteBuf directBuf = ... | ||
if (!directBuf.hasArray()) { //1 | ||
int length = directBuf.readableBytes();//2 | ||
byte[] array = new byte[length]; //3 | ||
directBuf.getBytes(directBuf.readerIndex(), array); //4 | ||
handleArray(array, 0, length); //5 | ||
} | ||
``` | ||
|
||
1.检查 ByteBuf 是不是由数组支持。如果不是,这是一个直接缓冲区。 | ||
|
||
2.获取可读的字节数 | ||
|
||
3.分配一个新的数组来保存字节 | ||
|
||
4.字节复制到数组 | ||
|
||
5.将数组,偏移量和长度作为参数调用某些处理方法 | ||
|
||
显然,这比使用数组要多做一些工作。因此,如果你事前就知道容器里的数据将作为一个数组被访问,你可能更愿意使用堆内存。 | ||
|
||
#### COMPOSITE BUFFER(复合缓冲区) | ||
|
||
最后一种模式是复合缓冲区,我们可以创建多个不同的 ByteBuf,然后提供一个这些 ByteBuf 组合的视图。复合缓冲区就像一个列表,我们可以动态的添加和删除其中的 ByteBuf,JDK 的 ByteBuffer 没有这样的功能。 | ||
|
||
Netty 提供了 ByteBuf 的子类 CompositeByteBuf 类来处理复合缓冲区,CompositeByteBuf 只是一个视图。 | ||
|
||
*警告* | ||
|
||
*CompositeByteBuf.hasArray() 总是返回 false,因为它可能既包含堆缓冲区,也包含直接缓冲区* | ||
|
||
例如,一条消息由 header 和 body 两部分组成,将 header 和 body 组装成一条消息发送出去,可能 body 相同,只是 header 不同,使用CompositeByteBuf 就不用每次都重新分配一个新的缓冲区。下图显示CompositeByteBuf 组成 header 和 body: | ||
|
||
**Figure 5.2 CompositeByteBuf holding a header and body** | ||
|
||
![Figure%205](https://atts.w3cschool.cn/attachments/image/20170808/1502159465835500.jpg) | ||
|
||
下面代码显示了使用 JDK 的 ByteBuffer 的一个实现。两个 ByteBuffer 的数组创建保存消息的组件,第三个创建用于保存所有数据的副本。 | ||
|
||
Listing 5.3 Composite buffer pattern using ByteBuffer | ||
|
||
``` | ||
// 使用数组保存消息的各个部分 | ||
ByteBuffer[] message = { header, body }; | ||
// 使用副本来合并这两个部分 | ||
ByteBuffer message2 = ByteBuffer.allocate( | ||
header.remaining() + body.remaining()); | ||
message2.put(header); | ||
message2.put(body); | ||
message2.flip(); | ||
``` | ||
|
||
这种做法显然是低效的;分配和复制操作不是最优的方法,操纵数组使代码显得很笨拙。 | ||
|
||
下面看使用 CompositeByteBuf 的改进版本 | ||
|
||
Listing 5.4 Composite buffer pattern using CompositeByteBuf | ||
|
||
``` | ||
CompositeByteBuf messageBuf = ...; | ||
ByteBuf headerBuf = ...; // 可以支持或直接 | ||
ByteBuf bodyBuf = ...; // 可以支持或直接 | ||
messageBuf.addComponents(headerBuf, bodyBuf); | ||
// .... | ||
messageBuf.removeComponent(0); // 移除头 //2 | ||
for (int i = 0; i < messageBuf.numComponents(); i++) { //3 | ||
System.out.println(messageBuf.component(i).toString()); | ||
} | ||
``` | ||
|
||
1.追加 ByteBuf 实例的 CompositeByteBuf | ||
|
||
2.删除 索引1的 ByteBuf | ||
|
||
3.遍历所有 ByteBuf 实例。 | ||
|
||
清单5.4 所示,你可以简单地把 CompositeByteBuf 当作一个可迭代遍历的容器。 CompositeByteBuf 不允许访问其内部可能存在的支持数组,也不允许直接访问数据,这一点类似于直接缓冲区模式,如图5.5所示。 | ||
|
||
Listing 5.5 Access data | ||
|
||
``` | ||
CompositeByteBuf compBuf = ...; | ||
int length = compBuf.readableBytes(); //1 | ||
byte[] array = new byte[length]; //2 | ||
compBuf.getBytes(compBuf.readerIndex(), array); //3 | ||
handleArray(array, 0, length); //4 | ||
``` | ||
|
||
1.得到的可读的字节数。 | ||
|
||
2.分配一个新的数组,数组长度为可读字节长度。 | ||
|
||
3.读取字节到数组 | ||
|
||
4.使用数组,把偏移量和长度作为参数 | ||
|
||
Netty 尝试使用 CompositeByteBuf 优化 socket I/O 操作,消除 原生 JDK 中可能存在的的性能低和内存消耗问题。虽然这是在Netty 的核心代码中进行的优化,并且是不对外暴露的,但是作为开发者还是应该意识到其影响。 | ||
|
||
*CompositeByteBuf API* | ||
|
||
*CompositeByteBuf 提供了大量的附加功能超出了它所继承的 ByteBuf。请参阅的 Netty 的 Javadoc 文档 API。* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
## Netty的Channel, Event 和 I/O | ||
|
||
**Netty**是一个异步事件驱动的NIO框架,Netty的所有IO操作都是异步非阻塞的。Netty 实际上是使用 Threads(多线程)处理 I/O 事件,熟悉多线程编程的读者可能会需要关注同步代码。但是这么做不好,因为同步会影响程序的性能,Netty 的设计保证程序处理事件不会有同步。图 Figure 3.1 展示了,你不需要在 Channel 之间共享 ChannelHandler 实例的原因: | ||
|
||
![Figure%203](img/1502159181753763.jpg) | ||
|
||
**Figure 3.1** | ||
|
||
该图显示,一个 EventLoopGroup 具有一个或多个 EventLoop。想象 EventLoop 作为一个 Thread 给 Channel 执行工作。 (事实上,一个 EventLoop 是势必为它的生命周期一个线程。) | ||
|
||
当创建一个 Channel,Netty 通过 一个单独的 EventLoop 实例来注册该 Channel(并同样是一个单独的 Thread)的通道的使用寿命。这就是为什么你的应用程序不需要同步 Netty 的 I/O操作;所有 Channel 的 I/O 始终用相同的线程来执行。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
## 近距离观察Netty的ChannelHandler | ||
|
||
**ChannelHandler**的类型是有很多的,我们在前面的章节中已经提过了,而且每个ChannelHandler需要做什么都取决与它的超类。为了能够让开发处理逻辑变得简单,Netty提供了一些默认的处理程序来实现形式的“adapter(适配器)”类。pipeline 中每个的 ChannelHandler 主要负责转发事件到链中的下一个处理器。这些适配器类(及其子类)会自动帮你实现,所以你只需要实现该特定的方法和事件。 | ||
|
||
*为什么用适配器?* | ||
|
||
*有几个适配器类,可以减少编写自定义 ChannelHandlers ,因为他们提供对应接口的所有方法的默认实现。(也有类似的适配器,用于创建编码器和解码器,这我们将在稍后讨论。)这些都是创建自定义处理器时,会经常调用的适配器:ChannelHandlerAdapter、ChannelInboundHandlerAdapter、ChannelOutboundHandlerAdapter、ChannelDuplexHandlerAdapter* | ||
|
||
下面解释下三个 ChannelHandler 的子类型:编码器、解码器以及 ChannelInboundHandlerAdapter 的子类SimpleChannelInboundHandler | ||
|
||
### 编码器、解码器 | ||
|
||
当您发送或接收消息时,Netty 数据转换就发生了。入站消息将从字节转为一个Java对象;也就是说,“解码”。如果该消息是出站相反会发生:“编码”,从一个Java对象转为字节。其原因是简单的:网络数据是一系列字节,因此需要从那类型进行转换。 | ||
|
||
不同类型的抽象类用于提供编码器和解码器的,这取决于手头的任务。例如,应用程序可能并不需要马上将消息转为字节。相反,该消息将被转换 一些其他格式。一个编码器将仍然可以使用,但它也将衍生自不同的超类, | ||
|
||
在一般情况下,基类将有一个名字类似 ByteToMessageDecoder 或 MessageToByteEncoder。在一种特殊类型的情况下,你可能会发现类似 ProtobufEncoder 和 ProtobufDecoder,用于支持谷歌的 protocol buffer。 | ||
|
||
严格地说,其他处理器可以做编码器和解码器能做的事。但正如适配器类简化创建通道处理器,所有的编码器/解码器适配器类 都实现自 ChannelInboundHandler 或 ChannelOutboundHandler。 | ||
|
||
对于入站数据,channelRead 方法/事件被覆盖。这种方法在每个消息从入站 Channel 读入时调用。该方法将调用特定解码器的“解码”方法,并将解码后的消息转发到管道中下个的 ChannelInboundHandler。 | ||
|
||
出站消息是类似的。编码器将消息转为字节,转发到下个的 ChannelOutboundHandler。 | ||
|
||
### SimpleChannelHandler | ||
|
||
也许最常见的处理器是接收到解码后的消息并应用一些业务逻辑到这些数据。要创建这样一个 ChannelHandler,你只需要扩展基类SimpleChannelInboundHandler 其中 T 是想要进行处理的类型。这样的处理器,你将覆盖基类的一个或多个方法,将获得被作为输入参数传递所有方法的 ChannelHandlerContext 的引用。 | ||
|
||
在这种类型的处理器方法中的最重要是 channelRead0(ChannelHandlerContext,T)。在这个调用中,T 是将要处理的消息。 你怎么做,完全取决于你,但无论如何你不能阻塞 I/O线程,因为这可能是不利于高性能。 | ||
|
||
*阻塞操作* | ||
|
||
*I/O 线程一定不能完全阻塞,因此禁止任何直接阻塞操作在你的 ChannelHandler, 有一种方法来实现这一要求。你可以指定一个 EventExecutorGroup。当添加 ChannelHandler 到ChannelPipeline。此 EventExecutorGroup 将用于获得EventExecutor,将执行所有的 ChannelHandler 的方法。这EventExecutor 将从 I/O 线程使用不同的线程,从而释放EventLoop。* |
Oops, something went wrong.