netty源码分析之-处理器详解(9)

Netty处理器重要的概念:

  • Netty的处理器可以分为两类:入站处理器与出站处理器
  • 入站处理器的顶层是ChannelInboundHandler,出站处理器的顶层是ChannelOutboundHandler
  • 数据处理时常用的各种编码器本质上都是处理器
  • 编解码器:无论我们向网络写入的数据是什么类型(int、char、String、二进制等),数据在网络中传递时,其都是以字节流的形式呈现的;将数据由原本的形式转换为字节流的操作称为编码(encode),将数据由字节转换为它原本的格式或是其他格式的操作称为解码(decode),编解码统一成为codec
  • 编码:本质上是一种出站处理器,因此,编码一定是一种ChannelOutboundHandler
  • 解码:本质上是一种入站处理器,因此,解码一定是一种ChannelInboundHandler
  • 在Netty中,编码器通常以XXXEncoder命名,解码器通常以XXXDdcoder命名

MessageToByteEncoder

对于编码器的顶层抽象类MessageToByteEncoder,像流式处理一样将一个消息转换成一个ByteBuf,encode是需要实现的重要方法:

public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter{}
public class IntegerEncoder extends MessageToByteEncoder<Integer> {
           @Override
           public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out)
                   throws Exception {
               out.writeInt(msg);
           }
       }

ByteToMessageDecoder

对于解码器的顶层抽象类ByteToMessageDecoder,像流式处理一样将一个ByteBuf转换成需要的其他对象类型,decode是需要实现的重要方法:

public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter{}
public class SquareDecoder extends ByteToMessageDecoder {
           @Override
           public void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
                   throws Exception {
               out.add(in.readBytes(in.readableBytes()));
           }
       }

在这里由于tcp传输数据的粘包与拆包,接受到的数据不一定是完整的,因此我们需要判断是否能够处理进而转换成我们需要的类型,需要类似以下的判断,实际上后续有ReplayingDecoder能够更加方便的来处理。

//整形举例
if (in.readableBytes() >= 8) {
      out.add(in.readLong());
}

MessageToMessageDecoder

将消息从一个类型转换成另一个类型,我们可以在ChannelInitailizer中添加多个编码器或者解码器来同时处理网络中接受到到消息(或者写到网络),类似的编解码器都是类似的功能

public class StringToIntegerDecoder extends
               MessageToMessageDecoder<String> {

            @Override
           public void decode(ChannelHandlerContext ctx, String message,
                              List<Object> out) throws Exception {
               out.add(message.length());
           }
       }

ReplayingDecoder

ReplayingDecoder是ByteToMessageDecoder的一个特殊变种,能够在阻塞I/O的范式中实现非阻塞解码。与ByteToMessageDecoder最大的不同是,ReplayingDecoder允许你直接实现decode或者decodeLast就好像所需要的字节数据已经接受到,而不用去监测是否有足够的字节数据能够用来解码
例如,对于ByteToMessageDecoder解码可能会有如下操作:

public class IntegerHeaderFrameDecoder extends ByteToMessageDecoder {

      @Override
     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf, List<Object> out) throws Exception {

       if (buf.readableBytes() < 4) {
          return;
       }

       buf.markReaderIndex();
       int length = buf.readInt();   //header

       if (buf.readableBytes() < length) {  //body
          buf.resetReaderIndex();
          return;
       }

       out.add(buf.readBytes(length));
     }
   }

如果第一次读取的header,能够在接下来的读取中全部接受则进行解码处理,否则返回到标记处,也就是无法解码,等待接受更多的数据

而对于ReplayingDecoder的解码,可能会更简洁:

public class IntegerHeaderFrameDecoder
        extends ReplayingDecoder<Void> {

     protected void decode(ChannelHandlerContext ctx,
                             ByteBuf buf) throws Exception {

       out.add(buf.readBytes(buf.readInt()));
     }
   }

ReplayingDecoder对ByteBuf进行了特殊的实现,当没有足够的数据来解码成特定类型的时候会抛错,在上述方法IntegerHeaderFrameDecoder实现中,当接受到4个或者更多字节的时候能够正常返回整型header,否则将抛错,如果ReplayingDecoder捕获到了异常,将会重新设置readerIndex到初始位置,然后重复调用decode来接受更多的字节数据。
ReplayingDecoder如此简洁也会有一些代价,如:

  • 一些ByteBuf的操作可能被禁止
  • 如果网络很差或者消息格式很复杂,那么会导致性能比较差。因为当接受到的字节数据不够会重复的调用多次decode来获取更多的消息;而不会像ByteToMessageDecoder一样直接返回
  • 可能需要注意decode方法会背调用多次为了解码一条消息
public class MyDecoder extends ReplayingDecoder<Void> {

     private final Queue<Integer> values = new LinkedList<Integer>();

      @Override
     public void decode(.., ByteBuf buf, List<Object> out) throws Exception {

       // A message contains 2 integers.
       values.offer(buf.readInt());
       values.offer(buf.readInt());

       // This assertion will fail intermittently since values.offer()
       // can be called more than two times!
       assert values.size() == 2;
       out.add(values.poll() + values.poll());
     }
   }

当调用第二次values.offer(buf.readInt());的时候可能没有足够多的字节数据,则会重复调用decode,但是此时queue中可能不止2条数据。

正确的做法应该是以下方式:

public class MyDecoder extends ReplayingDecoder<Void> {

     private final Queue<Integer> values = new LinkedList<Integer>();

      @Override
     public void decode(.., ByteBuf buf, List<Object> out) throws Exception {

       // Revert the state of the variable that might have been changed
       // since the last partial decode.
       values.clear();

       // A message contains 2 integers.
       values.offer(buf.readInt());
       values.offer(buf.readInt());

       // Now we know this assertion will never fail.
       assert values.size() == 2;
       out.add(values.poll() + values.poll());
     }
   }

当数据不够需要重新调用decode的时候,做一次清除queue的操作来保证队列正确的数据

但是我们可以通过调用checkpoint()方法来更新buffer的初始位置,让readerIndex能够重新回到该起始位置。从而避免多次重复的调用decode来对同一个消息多次解码

下面的这些类处理更加复杂的用例:

  • io.netty.handler.codec.LineBasedFrameDecoder— 这个类在 Netty 内部也有使
    用,它使用了行尾控制字符(\n 或者\r\n)来解析消息数据;
  • io.netty.handler.codec.http.HttpObjectDecoder— 一个 HTTP 数据的解码器。

自定义协议编解码来解决粘包与拆包:
https://github.com/huiGod/netty_lecture/tree/master/src/main/java/com/shengsiyuan/netty/handler3


关于Netty编解码器的重要结论:

  • 无论是编码器还是解码器,其所接受的消息类型必须要与待处理的参数类型一直,否则该编码器或解码器并不会被执行
  • 在解码器进行数据解码时,一定要记得判断缓冲(ByteBuf)中的数据是否足够,否则将会产生一些问题

同样对于编码器和解码器来说,引用计数也需要特别注意,一旦消息被编码或者解码,它就会被ReferenceCountUtil.release(message)调用自动释放,如果需要保留引用以便稍后使用,那么可以调用referenceCountUtil.retain(message)方法来增加该引用计数,从而防止该消息被释放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty5.0 架构剖析和源码解读 作者:李林锋 版权所有 email neu_lilinfeng@ © Netty5.0 架构剖析和源码解读1 1. 概述2 1.1. JAVA 的IO演进2 1.1.1. 传统BIO通信的弊端2 1.1.2. Linux 的网络IO模型简介4 1.1.3. IO复用技术介绍7 1.1.4. JAVA的异步IO8 1.1.5. 业界主流的NIO框架介绍10 2.NIO入门10 2.1. NIO服务端10 2.2. NIO客户端13 3.Netty源码分析16 3.1. 服务端创建16 3.1.1. 服务端启动辅助类ServerBootstrap16 3.1.2. NioServerSocketChannel 的注册21 3.1.3. 新的客户端接入25 3.2. 客户端创建28 3.2.1. 客户端连接辅助类Bootstrap28 3.2.2. 服务端返回ACK应答,客户端连接成功32 3.3. 读操作33 3.3.1. 异步读取消息33 3.4. 写操作39 3.4.1. 异步消息发送39 3.4.2. Flush操作42 4.Netty架构50 4.1. 逻辑架构50 5. 附录51 5.1. 作者简介51 5.2. 使用声明51 1. 概述 1.1.JAVA 的IO演进 1.1.1. 传统BIO通信的弊端 在JDK 1.4推出JAVANIO1.0之前,基于JAVA 的所有Socket通信都采用 BIO 了同步阻塞模式( ),这种一请求一应答的通信模型简化了上层的应用开发, 但是在可靠性和性能方面存在巨大的弊端。所以,在很长一段时间,大型的应 C C++ 用服务器都采用 或者 开发。当并发访问量增大、响应时间延迟变大后, 采用JAVABIO作为服务端的软件只有通过硬件不断的扩容来满足访问量的激 增,它大大增加了企业的成本,随着集群的膨胀,系统的可维护性也面临巨大 的挑战,解决这个问题已经刻不容缓。 首先,我们通过下面这幅图来看下采用BIO 的服务端通信模型:采用BIO 通信模型的 1connect NewThread1 WebBrowse 2connect 2handle(Req) WebBrowse 3connect Acceptor NewThread2 WebBrowse WebBrowse 4connect NewThread3 3sendResponsetopeer NewThread4 图1.1.1-1 BIO通信模型图 服务端,通常由一个独立的Accepto 线程负责监听客户端的连接,接收到客户 端连接之后为客户端连接创建一个新的线程处理请求消息
FastThreadLocal 是 Netty 中的一个优化版 ThreadLocal 实现。与 JDK 自带的 ThreadLocal 相比,FastThreadLocal 在性能上有所提升。 FastThreadLocal 的性能优势主要体现在以下几个方面: 1. 线程安全性:FastThreadLocal 使用了一种高效的方式来保证线程安全,避免了使用锁的开销,使得在高并发场景下性能更好。 2. 内存占用:FastThreadLocal 的内部数据结构更加紧凑,占用的内存更少,减少了对堆内存的占用,提高了内存的利用效率。 3. 访问速度:FastThreadLocal 在访问时,使用了直接索引的方式,避免了哈希表查找的开销,使得访问速度更快。 在 Netty 源码中,FastThreadLocal 主要被用于优化线程的局部变量存储,提高线程之间的数据隔离性和访问效率。通过使用 FastThreadLocal,Netty 在高性能的网络通信中能够更好地管理线程的局部变量,提供更高的性能和并发能力。 引用中提到的代码片段展示了 Netty 中的 InternalThreadLocalMap 的获取方式。如果当前线程是 FastThreadLocalThread 类型的线程,那么就直接调用 fastGet 方法来获取 InternalThreadLocalMap 实例;否则,调用 slowGet 方法来获取。 fastGet 方法中,会先尝试获取线程的 threadLocalMap 属性,如果不存在则创建一个新的 InternalThreadLocalMap,并设置为线程的 threadLocalMap 属性。最后返回获取到的 threadLocalMap。 slowGet 方法中,通过调用 UnpaddedInternalThreadLocalMap.slowThreadLocalMap 的 get 方法来获取 InternalThreadLocalMap 实例。如果获取到的实例为 null,则创建一个新的 InternalThreadLocalMap,并将其设置到 slowThreadLocalMap 中。最后返回获取到的 InternalThreadLocalMap。 综上所述,FastThreadLocal 是 Netty 中为了优化线程局部变量存储而设计的一种高性能的 ThreadLocal 实现。它通过减少锁的开销、优化内存占用和加快访问速度来提升性能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [FastThreadLocal源码分析](https://blog.csdn.net/lvlei19911108/article/details/118021402)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [Netty 高性能之道 FastThreadLocal 源码分析(快且安全)](https://blog.csdn.net/weixin_33871366/article/details/94653953)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值