码恋 码恋

ALL YOUR SMILES, ALL MY LIFE.

目录
Netty 系列笔记之 TCP 粘包拆包与编解码
/  

Netty 系列笔记之 TCP 粘包拆包与编解码

一、概述

《Netty 系列笔记之开篇》中我们已经对 TCP 粘包、拆包的问题处理有所涉及:

  1. TCP 协议本身设计就是面向流的,提供可靠传输。
  2. 正因为面向流,对于应用层的数据包而言,没有边界区分。这就需要应用层主动处理不同数据包之间的组装。
  3. 发生粘包现象不是 TCP 的缺陷,只是应用层没有主动做数据包的处理。

接下来,让我们详细解析 Netty 如何处理 TCP 的粘包、拆包现象。

二、TCP 粘包和拆包

image.png

数据的发送方发送了 ABC DEF 数据,接收方有可能接收到的是 ABCDEF ,两个数据包粘在一起称为粘包。也有可能接收到的是 AB CD EF ,两个数据包被拆分为三个数据包,称为拆包或半包。

1、现象

具象到下面这张图:

2、产生原因
  • 粘包
    • 要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次发送出去,将会发生粘包。
    • 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

  • 拆包
    • 要发送的数据大于 TCP 发送缓冲区剩余空间大小,将会发生拆包。

    • 待发送数据大于 MSS(最大报文长度)即各层的MTU (最大传输单元),TCP 在传输前将进行拆包。

      根本原因:TCP 是流式协议,数据之间没有边界。

关于最大传输单元

image.png

如上左图,TCP/IP 协议分处 5 层协议的不同层,每层中都有规定最大的传输大小,如 IPV4 最大传输为64 kb ,即为最大传输单元。

3、解决方案

解决粘包和拆包问题的思路是找出数据的边界。

image.png

三、Netty 对封装成帧三种方法的支持

image.png

三个 Decoder 都继承自 ByteToMessageDecoder ,它是一个抽象类,其有一个抽象方法 :

protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;

用于对接收到的数据进行解码。

基于不同的协议,它有非常非常多的实现,以最简单的 FixedLengthFrameDecoder 为例,看如何实现:

  • 重写了 decode() 方法:
@Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }
protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
	// 读取的数据没有达到预定长度
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
	    // 达到长度,读取固定长度的数据返回
            return in.readRetainedSlice(frameLength);
        }
    }

只有几行代码,逻辑非常简单,数据只有达到固定的长度才读取并返回。那么,如果长度不够时,这部分数据存储在那里呢?

这部分逻辑在 ByteToMessageDecoderchannelRead() 方法中,其有一个数据积累器用于存储读到的信息,感兴趣的小伙伴可自行查看。

四、二次解码器

上面的我们说的解码器都继承自 ByteToMessageDecoder ,作用是将存在粘包、拆包问题的用户数据转换为真正的用户数据,其本质上还是字节数组。

而通常来说,我们需要的数据时 Java 对象,所以我们需要对解码后的数据进行二次解码,用到的是 MessageToMessageDecoder ,从命名上面很明显看出他们是做什么的。

数据处理的流程就很清晰了:

ByteToMessageDecoder->MessageToMessageDecoder
Bytebuff -> ByteBuff=>ByteBuff -> Java Object

这样将粘包、拆包问题的处理与具体的协议处理分离开来,耦合性大大降低,可以随时替换不同的协议。

1、常用的编解码方式
  • Java 序列化
  • Marshaling
  • XML
  • JSON
  • MessagePack
  • ProtoBuf
  • ...
2、编解码的三要素
  • 时间:编解码用的时间,要根据数据的大小衡量
  • 空间:编解码用的空间,要根据数据的大小衡量
  • 可读性:编码后是否可读
3、Netty 对常用协议的支持

image.png



❤ 转载请注明本文地址或来源,谢谢合作 ❤


center