Netty学习知识点笔记

1.Channel、 EventLoop 和 ChannelFuture的关系

在这里插入图片描述

  • EventLoop 定义了 Netty 的核心抽象, 用于处理连接的生命周期中所发生的事件
  • 一个 EventLoopGroup 包含一个或者多个 EventLoop
  • 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定
  • 所有由 EventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理
  • 一个 Channel 在它的生命周期内只注册于一个 EventLoop
  • 一个 EventLoop 可能会被分配给一个或多个 Channel
  • 一个给定 Channel 的 I/O 操作都是由相同的 Thread 执行的, 实际
    上消除了对于同步的需要
  • Netty 中所有的 I/O 操作都是异步的。因为一个操作可能不会
    立即返回,所以我们需要一种用于在之后的某个时间点确定其结果的方法。为此, Netty 提供了
    ChannelFuture 接口,其 addListener()方法注册了一个 ChannelFutureListener,以
    便在某个操作完成时(无论是否成功)得到通知

2. ChannelHandler的执行顺序

在这里插入图片描述

  • 由ChannelPipeline控的添加顺序控制
   bootstrap.handler(new ChannelInitializer<SocketChannel>() {//ChannelInitializer是用于配置通道
                @Override
                public void initChannel(SocketChannel ch) {
                    ch.pipeline().addLast(new SDataEncoder());//出站
                    ch.pipeline().addLast(new SDataDecoder());//入站
                    ch.pipeline().addLast(new SIdleStateHandler(0, 0, SIdleTime));//in
                    ch.pipeline().addLast(clientDataHandler);//in
                }
            });

如这里代码,入站的顺序是SDataDecoder(解密)–》clientDataHandler(客户端数据处理)

public class SDataDecoder extends ByteToMessageDecoder {
   
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
    
       //在入栈解码器中,需要将入的in数据,转换成我们想要的数据对象outObject
       //然后把这个数据添加到out数组里面
       out.add(outObject)
       //完成后,调用,让流程走向下个channelhandler
        ctx.fireChannelReadComplete();
            
     }
  }

//SDataDecoder 的下个channelhandler,如下面这个,就需要指定为解码后的数据类型,

public class ClientDataHandlerextends SimpleChannelInboundHandler<outObject> {
//在这个方法中,就能处理我们想要服务器返回的入站的数据了
 @Override
    protected void channelRead0(ChannelHandlerContext ctx, SMessage s) {
}

在这里插入图片描述

在这里插入图片描述

3. 粘包、拼包原因

由服务器发送的消息可能会被分块接收,也可能分块发送。 也就是说,如果服务器发送了 5 字节, 那么不
能保证这 5 字节会被一次性接收。 即使是对于这么少量的数据, channelRead0()方法也可能
会被调用两次,第一次使用一个持有 3 字节的 ByteBuf(Netty 的字节容器),第二次使用一个
持有 2 字节的 ByteBuf。作为一个面向流的协议, TCP 保证了字节数组将会按照服务器发送它们的顺序被接收。所以只要按照特定的长度或者分隔符处理,就能拼接到完整的数据包

设计时,不同的消息除了划分不同的消息类型,还需要针对每个消息一个唯一的消息id,不然同类型消息,只按照消息类型接收,可能会出现发送一次请求,同时受到多条回复(其他线程也发送了类似的消息)

4. ChannelHandler 的典型用途包括

  • 将数据从一种格式转换为另一种格式;
  • 提供异常的通知;
  • 提供 Channel 变为活动的或者非活动的通知;
  • 提供当 Channel 注册到 EventLoop 或者从 EventLoop 注销时的通知;
  • 提供有关用户自定义事件的通知。

拦截过滤器 ChannelPipeline 实现了一种常见的设计模式—拦截过滤器(Intercepting Filter)。 UNIX 管道是另外一个熟悉的例子: 多个命令被链接在一起,其中一个命令的输出端将连
接到命令行中下一个命令的输入端。

5. chanel 的是线程安全的

Netty 的 Channel 实现是线程安全的,因此你可以存储一个到 Channel 的引用,并且每当
你需要向远程节点写数据时,多个线程都使用这个 Channel写数据,都是没问题的

6. EventLoop的是线程管理

Netty线程模型的卓越性能取决于对于当前执行的Thread的身份的确定 ,确定它是否是分配给当前Channel以及它的EventLoop的那一个线程(因为当前不同的线程会抢用cpu)。如果(当前)调用线程正是支撑 EventLoop 的线程, 那么所提交的代码块将会被(直接)执行。否则,EventLoop 将调度该任务以便稍后执行,并将它放入到内部队列中。当 EventLoop下次处理它的事件时, 它会执行队列中的那些任务/事件。这也就解释了任何的 Thread 是如何与 Channel 直接交互而无需在 ChannelHandler 中进行额外同步的

在这里插入图片描述

7. EventLoop/线程的分配

1. 异步传输
在这里插入图片描述
一旦一个 Channel 被分配给一个 EventLoop, 它将在它的整个生命周期中都使用这个EventLoop(以及相关联的 Thread)。请牢记这一点,因为它可以使你从担忧你的 ChannelHandler 实现中的线程安全和同步问题中解脱出来。

2. 阻塞传输
在这里插入图片描述

但是, 正如同之前一样, 得到的保证是每个 Channel 的 I/O 事件都将只会被一个 Thread(用于支撑该 Channel 的 EventLoop 的那个 Thread) 处理。

8. 引导

引导客户端:
在这里插入图片描述
代码示例:
在这里插入图片描述
在这里插入图片描述
引导服务器:

在这里插入图片描述
代码示例:
在这里插入图片描述

9. 使用 Netty 的 ChannelOption 和属性

在这里插入图片描述
在这里插入图片描述

10. 关闭

需要关闭 EventLoopGroup, 它将处理任何挂起的事件和任务,并且随后
释放所有活动的线程。这就是调用 EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个 Future,这个 Future 将在关闭完成时接收到通知。需要注意的是,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的 Future 注册一个监听器以在关闭完成时获得通知

11. 解码器

  • 抽象类 ByteToMessageDecoder

在这里插入图片描述
虽然 ByteToMessageDecoder 使得可以很简单地实现这种模式,但是你可能会发现,在调用 readInt()方法前不得不验证所输入的 ByteBuf 是否具有足够的数据有点繁琐。ReplayingDecoder,它是一个特殊的解码器,以少量的开销消除了这个步骤。

  • 抽象类 ReplayingDecoder
    在这里插入图片描述和之前一样,从ByteBuf中提取的int将会被添加到List中。如果没有足够的字节可用,这个readInt()方法的实现将会抛出一个Error,其将在基类中被捕获并处理。当有更多的数据可供读取时,该decode()方法将会被再次调用
  • 抽象类 MessageToMessageDecoder
    在这里插入图片描述
  • 在这里插入图片描述

12. 编码器

抽象类 MessageToByteEncoder
在这里插入图片描述
抽象类 MessageToMessageEncoder
在这里插入图片描述

13.空闲的连接和超时

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

14.解码基于分隔符的协议和基于长度的协议

基于分隔符的(delimited) 消息协议使用定义的字符来标记的消息或者消息段(通常被称为帧)的开头或者结尾。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
如果你正在使用除了行尾符之外的分隔符分隔的帧,那么你可以以类似的方式使用 DelimiterBasedFrameDecoder,只需要将特定的分隔符序列指定到其构造函数即可
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
基于长度的协议通过将它的长度编码到帧的头部来定义帧,而不是使用特殊的分隔符来标记
它的结束。
在这里插入图片描述
在这里插入图片描述
你将经常会遇到被编码到消息头部的帧大小不是固定值的协议。为了处理这种变长帧,你可以使用 LengthFieldBasedFrameDecoder, 它将从头部字段确定帧长,然后从数据流中提取指定的字节数
在这里插入图片描述

发布了175 篇原创文章 · 获赞 113 · 访问量 29万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章