Netty源码分析系列之TCP粘包、半包问题以及Netty是如何解决的

本文深入探讨TCP的粘包、半包现象及其原因,并详细解析Netty如何通过编解码器来解决这些问题。Netty利用ByteToMessageDecoder抽象类和特定的解码器实现,确保数据在传输过程中的完整性。
摘要由CSDN通过智能技术生成

扫描下方二维码或者微信搜索公众号菜鸟飞呀飞,即可关注微信公众号,阅读更多Spring源码分析Java并发编程文章。

微信公众号

问题

在上一篇文章中分析到了 Netty 服务端是如何进行新连接的接入的,那么当新连接接入后,就可以开始数据的读写操作了。在进行数据读写操作时,对于 TCP 连接而言,netty 就需要解决 TCP 中粘包、半包的问题,这将是本文今天重点分析的内容。在开始阅读本文之前,可以先思考一下以下两个问题。

    1. 什么是 TCP 的粘包、半包问题?UDP 协议存在粘包半包吗?
    1. netty 是如何解决的

什么是粘包拆包

在 TCP/IP 协议模型中,TCP 和 UDP 协议属于传输层协议,这两个协议在数据传输过程中存在很大的差异。

对于 UDP 协议而言,它传输的数据是基于数据报来进行收发的,在 UDP 协议的头中,会有一个 16bit 的字段来表示 UDP 数据报文的长度,在应用层能很好的将不同的数据报文区分开。可以理解为,UDP 协议传输的数据是有边界的,因此它不会存在粘包、半包的问题。

而对于 TCP 协议而言,它传输数据是基于字节流传输的。应用层在传输数据时,实际上会先将数据写入到 TCP 套接字的缓冲区,当缓冲区被写满后,数据才会被写出去,这就可能造成粘包、半包的问题。而且当接收方接收到数据后,实际上接收到的是一个字节流,所谓的流,可以理解为河流一样。既然是流,多个数据包相互之间是没有边界的,而且在 TCP 的协议头中,没有一个单独的字段来表示数据包的长度,这样在接收方的应用层,从字节流中读取到数据后,是没办法将两个数据包区分开的。

粘包、半包示意图

当发送方连续向接收方发送两个完整的数据包时,如果使用 TCP 协议进行传输,就可能存在以下几种情况。下图中 packet1 和 packet2 分别表示发送方发送的两个完整的数据包。

第一种情况,没有发生粘包、半包的现象,即接收方正常接收到两个独立的完整数据包 packet1、packet2,这种情况是属于正常情况。如图 1 所示。

正常包

第二种情况,发生了粘包现象,即发送方将数据包 packet1 写入到自己的 TCP 套接字的缓冲区后,TCP 并没有立即将数据发送出去,因为此时缓冲区可能还没有慢。接着发送方又发送了一个数据包 packet2,仍然是先写入到 TCP 套接字的缓冲区,此时缓冲区满了,然后 TCP 才将缓冲区的数据一起发送出去,这时候接收方接收到的数据看起来只有一个数据包。在 TCP 的协议头中,没有一个单独的字段来表示数据包的长度,这样接收方根本就无法区分出 packet1 和 packet2,这就是所谓的粘包问题。另外,当接收方的 TCP 层接收到数据后,由于应用层没有及时从 TCP 套接字中读取数据,也会造成粘包现象。如图 2 所示。

粘包

第三种情况,发生了半包现象,即发送方依旧是先后发送了两个数据包 packet1 和 packet2,但是 TCP 在传输时,分了几次传输,每次传输的内容中包含的不是 packet1 和 packet2 的完整包,只是 packet1 或者 packet2 的一部分,就相当于把两个数据包的内容拆分了,因此也称之为拆包现象。如图 3 所示。

半包

产生粘包、半包的原因

从上面的示意图中,我们大致可以知道产生粘包、半包的主要原因如下。

  • 粘包原因
      1. 发送方每次写入的数据小于套接字缓冲区大小;
      1. 接收方读取套接字缓冲区的数据不够及时。
  • 半包原因
      1. 发送方写入的数据大于套接字缓冲区的大小;
      1. 发送的数据大于协议的 MSS 或者 MTU,必须拆包。(MSS 是 TCP 层的最大分段大小,TCP 层发送给 IP 层的数据不能超过该值;MTU 是最大传输单元,是物理层提供给上层一次最大传输数据的大小,用来限制 IP 层的数据传输大小)。

但归根结底,产生粘包、半包的根本原因是因为 TCP 是基于字节流来传输数据的,数据包相互之间没有边界,导致接收方无法准确的分辨出每一个单独的数据包。

netty 如何解决粘包拆包问题

作为一个应用层的开发者,我们无法去改变 TCP 基于字节流来传输数据的特性,除非我们自定义一个类似于 TCP 的协议,但是难度太大,设计出来的性能还不一定比现有的 TCP 协议性能好,况且目前 TCP 协议的使用十分广泛。而 netty 作为一款高性能的网络框架,必然就要有对 TCP 协议的支持,既然支持 TCP 协议,那就要解决 TCP 中粘包、半包的问题,否则如果开发人员自己去解决,那就费时费力了。

netty 中通过提供一系列的编解码器来解决 TCP 的粘包、半包问题,顾名思义,编解码器就是通过将从 TCP 套接字中读取的字节流通过一定的规则,将其进行编码或者解码,编码成二进制字节流或者解析出一个个完整的数据包。在 netty 中提供了很多通用的编解码器,对于解码器而言,它们均继承自抽象类ByteToMessageDecoder;对于编码器而言,它们均继承与抽象类MessageToByteEncoder

今天主要先简单分析下抽象类解码器ByteToMessageDecoder类的源码,对于具体的解码器实现将在后面两篇文章中详细分析其原理,对于编码器而言,编码过程与解码过程恰好相反,因此就不再单独赘述,有兴趣的朋友可以自行阅读,欢迎分享。

ByteToMessageDecoder实际上就是一个 ChannelHandler,它的具体实现类需要被添加到 pipeline 中才会起作用。当将解码器添加到 pipeline 中后,当出现 OP_READ 事件时,就会通过 pipeline 传播执行所有 handler 的 channelRead() 方法。在抽象类解码器中,就定义了 channelRead()方法。其源码如下。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
   
    if (msg instanceof ByteBuf) {
   
        // 用来存放解码出来的数据对象,可以将它当做一个集合
        CodecOutputList out = CodecOutputList.newInstance();
        try {
   
            ByteBuf data = (ByteBuf) msg;
            first = cumulation == null;
            if (first) {
   
                //第一次直接赋值
                cumulation = data;
            } else {
   
                //累加数据
                cumulation 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值