TCP 粘包/拆包 — Netty

云快充对接workerman实现协议版本1.6 云快充所有帧类型 阅读详情

使用 TCP 进行通讯,粘包和拆包是常见的问题。在数据传输过程中,由于 TCP 是面向字节流的协议,没有明确的数据范围的一串数据,可能会导致数据包的合并或拆分。

概念

TCP 是面向字节流的,无论哪个端发送数据,在接收的时候由于 TCP 不清楚上层的业务逻辑,由于 Maximum Segment Size(最大报文段大小)、Maximum Transmission Unit(最大传输单位)、Buffer Size(缓冲区大小),以及 Nagle 算法等指标的影响,也就产生了所谓的粘包和拆包的现象。

粘包和拆包的含义

粘包:发出方的多个数据包被合并成一个包发送,导致接收方无法区分每个数据包的数据范围。
在这里插入图片描述

拆包:发出方的一个数据包被拆分成多个包发送,导致接收方无法获取完整的数据。

在这里插入图片描述

MSS、MTU 、Buffer、Nagle

Maximum Segment Size(MSS,TCP 协议层上允许的最大数据段大小,指定了 TCP 数据段中的数据部分的最大字节数。) 是基于 Maximum Transmission Unit(MTU,MTU 是网络协议层上能够传输的最大数据包大小,单位是字节) 来设置的。

通常情况下,MSS 的最大值会小于或等于 MTU 减去 TCP 和 IP 头部的大小:通常是 1500 - 20 (IP) - 20 (TCP) = 1460 字节。Jumbo Frame 环境下是 9000 - 20 (IPv4) - 20 (TCP) = 8960 字节

TCP 为提高性能,发送端会将需要发送的数据发送到 Buffer 缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制,来接收数据。缓冲区大小是可调整的。

Nagle 算法是一种用于减少小数据包数量的优化策略。目的是通过将多个小的数据包合并成一个大数据包来提高网络性能,减少网络中数据包的数量,从而降低带宽的浪费。不过有的应用对实时性要求较高,Nagle 算法可能不适用(可能会导致延迟)。为了避免这种情况,可以通过关闭 Nagle 算法来强制立即发送数据包。基本原理是:

  • 当应用程序发送一个小于 MSS(最大段大小)的数据包时,Nagle 算法会等待一个确认(ACK)包的到来,然后再发送下一个数据包。这是为了避免频繁发送小的数据包。

  • 如果发送的数据包已经积累了足够的数据(达到 MSS),或者如果已经收到了之前的数据包的 ACK,Nagle 算法会将这些数据包发送出去。

// 关闭 Nagle 算法
channelHandlerContext.getChannel().socket().setTcpNoDelay(true); 

解决方案(Netty)

使用固定长度数据包

最简单的解决方案之一。每个消息的长度是固定的,那么接收方就能清楚地知道每个包的大小,避免了粘包和拆包问题。实现简单,也不需要没有复杂的解码逻辑,容易理解。由于发送方总是发送固定长度的包,接收方根据固定长度来读取。只是消息长度变化较大,会导致浪费或效率较低,不适用于变长数据。

@Slf4j
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
		pipeline.addLast(new FixedLengthFrameDecoder(1024));  // 假设每个消息的固定长度为1024字节
		pipeline.addLast(new MyHandler());
    }
}

使用消息分隔符

可以在消息之间添加特定的分隔符(例如换行符、空格、特定字符等),接收方通过分隔符来区分不同的消息。可以让发送方和接收方一起约定一个或多个分隔符(如换行符 \n),收到分隔符时解析完整的消息。实现简单且灵活,适用于文本协议(如 FTP、SMTP 等),不需要额外的长度字段。但是分隔符可能会被数据本身使用,导致冲突(例如如果数据中包含分隔符,则无法正确分割消息)。而且对于二进制数据也不适用。

@Slf4j
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
		pipeline.addLast(new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter())); // 使用换行符作为分隔符
		pipeline.addLast(new StringDecoder());
		pipeline.addLast(new StringEncoder());
		pipeline.addLast(new MyHandler());
    }
}

使用消息长度字段

这是比较常见的解决方案,在每个消息的开始部分添加一个长度字段(通常是 2 字节、4 字节等),表示接下来的报文大小。接收方首先读取长度字段,然后根据该长度来读取消息体。这种最适用于二进制和文本数据。而且比分隔符方法更灵活,不容易发生冲突。只是增加了额外的开销,因为每个消息都需要一个长度字段。

假设每条消息的前 2 个字节表示消息体的长度,然后接收方读取指定长度的消息体。

@Slf4j
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(
        1024,    // 最大帧大小
        0,       // 长度字段的位置
        4,       // 长度字段的大小(例如4字节的整数)
        0,       // 跳过长度字段后的字节数
        4));      // 长度字段的大小(假设是4字节)
    	pipeline.addLast(new MyHandler());
    }
}

自定义协议(云快充协议)

一般大多数情况下,使用消息长度字段就足够解决大部分的拆包粘包问题了。但是实际开发过程中,公司的数据传输都会自定义一种自己协议,来指定如何打包和解析消息。这通常涉及在消息中使用长度字段、校验和、结束标志等元素。这样提供更大的灵活性,符合特定需求的协议。也可支持复杂的消息结构。只是实现复杂,需要定义清晰的协议格式,如果协议设计不当,大部分情况会导致解析错误,而且修改起来超级麻烦。这里以云快充协议为例:
在这里插入图片描述

@Slf4j
@Component
public class SocketInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        // 超时
        pipeline.addLast("idleStateHandler", new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
        pipeline.addLast(new YKCMessageDecoder()); // 解码
        pipeline.addLast(new YKCMessageEncoder()); // 编码
        pipeline.addLast(new YKCDataHandler()); // 编码
    }

}

@Slf4j
@Component
public class MyFrameDecoder extends ByteToMessageDecoder {

	public static final int PROTOCOL_HEAD_BASE_LENGTH = 2; // 基础数据长度
    public static final byte PROTOCOL_HEAD = 0x68; // 协议标志
    
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        // 最基础长度(协议头和消息体长度)都不够,
        if (byteBuf.readableBytes() < PROTOCOL_HEAD_BASE_LENGTH) {
            return;
        }
        // 标记当前 readerIndex 位置,如果读到后面发现只有一半,使用 resetReaderIndex 重置回来
        byteBuf.markReaderIndex();
        byte head = byteBuf.readByte();
        if (head == PROTOCOL_HEAD) {
            int bodyLen = 0xFF & byteBuf.readByte(); // 转成无符号的int,Byte.toUnsignedInt(byteBuf.readByte()) 写法可能更能提高代码可读性
            if (byteBuf.readableBytes() <= bodyLen + 2) { // 剩余长度是 bodyLen + 2(帧校验域)
                byteBuf.resetReaderIndex(); // 数据长度与协议里总长度不符
                return;
            }
            // 长度足够,重置读取起始位置
            byte[] body = new byte[bodyLen]; // 刚好前面2个字节(头和长度)抵去后面2个字节(帧校验域)
            byteBuf.readBytes(body);
            byte[] crcData = YKCCrcUtil.calculateCrc(body);
            byte lowByte = byteBuf.readByte();
            byte hiByte = byteBuf.readByte();
            if (hiByte == crcData[0] && lowByte == crcData[1]) {
                list.add(body);
            } else {
                log.error("crc校验失败.数据错误");
            }
        }else {
            channelHandlerContext.channel().close(); // 不是云快充协议,直接关闭
        }
    }
}

@Slf4j
@Component
public class YKCMessageEncoder extends MessageToByteEncoder<String[]> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, String[] bytes, ByteBuf out) throws Exception {
        log.info("发送数据给客户端:{}", (Object) bytes);
        byte[] res = ConvertUtil.hexArrToByteArr(bytes);
        out.writeBytes(res);
    }
}

总结

TCP 传输过程中,由于 MMS、MTU、Buffer、以及 Nagle 造成的粘包/拆包是很正常的,一般可使用 Netty 提供的一些内置解码器,例如:DelimiterBasedFrameDecoder、LengthFieldBasedFrameDecoder、FixedLengthFrameDecoder 来解决,也可使用自定义的解码器来处理复杂的传输协议。

使用过程中,通过合理配置缓冲区大小、优化发送和接收逻辑、以及使用合适的协议(如长度字段或分隔符),可以有效提高 TCP 通信的效率和稳定性。

另外,Nagle 算法会对 TCP 的粘包和拆包影响取决于应用层的数据发送模式。如果需要精细控制 TCP 包的发送行为,可以考虑根据需求调整是否启用 Nagle 算法。

Netty解决TCP粘包/拆包导致的半包读写问题 一.TCP粘包/拆包问题说明   TCP是个“流”协议,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包拆分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 二.利用LinedBasedFrameDecoder解决TCP粘包问题    为了 阅读详情

相关推荐

Netty权威指南(四)TCP粘包/拆包问题

TCP粘包/拆包问题解决之道一、介绍1.1 TCP粘包/拆包问题说明1.2 TCP粘包/拆包发生的原因1.3 粘包问题的解决策略二、未考虑TCP粘包导致的功能异常案例2.1 TimeServerHandler的改造2.2 TimeClientHandler的改造2.3 运行三、利用LineBasedFrameDecoder解决TCP粘包问题3.1 支持TCP粘包的TimeServer3.2 支持TCP粘包的TimeClient3.3 运行3.4 LineBasedFrameDecoder和StringDec

DreamsArchitects的博客 393

解锁云快充协议开发:开启智能充电新时代、基于Java语言 Netty通讯框架的云块充协议1.5_云快充协议1.6_云快充协议1.4_云快充协议

云快充协议+云快充1.5协议+云快充1.6+云快充协议开源代码+云快充底层协议+云快充桩直连+桩直连协议+充电桩协议+云快充源码。

weixin_42648890的博客 2581

云快充平台与设备通信协议1.6

云快充平台与设备通信协议1.6

使用netty对接云快充协议1.5,1.6

云快充1.5,1.6对接协议 Java

小纯洁的博客 1358

云快充平台协议V1.5.pdf

云快充平台协议V1.5.pdf

MCU云快充协议C语言实现库

适合MCU,云快充协议C语言实现库

Netty——TCP 粘包/拆包问题

本文讲解了 TCP 的粘包/拆包问题与成因,并讲解了在 Netty 中如何解决。

qq_61350148的博客 1492

云快充充电桩1.6【智能充电管理】云快充平台V1.6协议:双枪并充与二维码交互系统通信规范

内容概要:本文档为《云快充平台协议V1.6》,详细规定了充电桩与云快充平台之间数据交互的通信流程、报文格式及业务逻辑。协议基于TCP/IP通信,参考国网104规约和GB/T 27930标准,涵盖充电桩登录认证、心跳机制、实时数据上报、充电启停控制、交易记录上传、计费模型管理、远程运维、地锁控制及双枪并充等功能。重点包括通信结构、帧格式定义、各类数据交互流程(如APP/刷卡充电、离线模式)、并充模式支持以及二维码下发等扩展功能,并提供了CRC校验等技术实现细节。; 适合人群:从事充电桩设备开发、物联网通信协议对接、新能源汽车充电平台集成的技术人员,以及负责充电桩与平台联调测试的工程师。; 使用场景及目标:①实现充电桩与云快充平台的标准化通信对接;②支持多种充电启动方式(APP、刷卡、VIN码)及远程控制;③确保交易数据一致性与计费准确性;④实现远程运维(重启、升级)与地锁联动控制;⑤支持双枪并充业务场景下的多枪协同管理。; 阅读建议:本文档为技术对接手册,建议开发者结合实际设备开发环境,重点关注报文结构、字段定义与时序流程,严格按照协议要求

Netty学习(二)- TCP粘包/拆包

一、TCP 粘包/拆包介绍 1、什么是粘包、拆包 首先只有TCP数据传输才会存在是粘包、拆包现象。 假设客户端分别发送两个数据包D1和D2给服务器,由于TCP是面向流的协议,TCP把客户端传过来的数据看成是一连串的无结构的字节流,且服务端一次读取到的数据是不确定的,所以可能会出现下面几种情况。 (1)服务端分两次接收到D1和D2数据包,没有发生粘包/拆包。 (2)服务端一次接收了两个数据包,D1...

noaman_wgs的博客 796

西门子PLC与汇川SV660P伺服速度模式配置实战指南

本文详细介绍了西门子S7-1200 PLC与汇川SV660P伺服驱动器在速度模式下的配置实战指南。从硬件接线、伺服参数配置到TIA Portal软件组态和PLC程序编写,提供了全面的操作步骤和实用技巧,帮助工程师快速解决伺服系统调试中的常见问题,确保系统稳定运行。

weixin_30316097的博客 498

微信小程序后台定位与实时位置更新的实战指南

本文提供了微信小程序实现后台定位与实时位置更新的完整实战指南。针对小程序退到后台后位置更新中断的痛点,详细解析了基于`startLocationUpdateBackground`API的后台定位解决方案。内容涵盖从隐私协议配置、项目权限声明,到授权、启动、监听、停止的核心代码流程,并分享了真机调试、性能优化及权限引导等关键避坑经验,帮助开发者构建稳定可靠的后台位置服务。

weixin_29008055的博客 466

【Netty权威指南】04-TCP粘包/拆包问题的解决之道

1、TCP粘包/拆包 TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水它们是连成一片的,其间并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 1.1、TCP粘包/拆包问题...

通往神秘的道路的专栏 833

大模型推理常见采样策略:Top-k, Top-p, Temperature, Beam Search

大模型推理采样策略,举例通俗易懂汇总介绍

qq_43243579的博客 2万+

【Netty】TCP粘包/拆包解决方案

TCP作为传输层(4层)协议,并不知道你的应用层(7层)数据报文的含义,因此使用TCP传输数据时,可能会出现「粘包/拆包」,需要引用层通过特定的协议去解决。 1. 什么是TCP粘包/拆包? 「粘包/拆包」在Socket编程中经常会出现,使用TCP协议传输数据时,如果对端连续发送多个小的数据包,TCP会将这些小的数据包打包,合并成一个TCP报文发送出去,这就是「粘包」。如果对端发送一个超大的数据包,TCP会根据缓冲区的情况,将这个超大数据包拆分成多个小的TCP报文发送出去,这就是「拆包」。 ​ 拿HTTP服.

程序员小潘 1809

MaPLe: Multi-modal Prompt Learning

具体而言,我们在语言分支中附加可学习的上下文标记,并通过耦合函数显式地将视觉 Prompt 置于语言 Prompt 上,以建立它们之间的交互。使用 Prompt 来适应CLIP (语言或视觉) 的单个分支中的表示是次优的,因为它不允许在下游任务上动态调整两个表示空间的灵活性。由此,提出了一种分支感知的多模态 Prompt,通过在两种模态之间共享提示来协调 CLIP 的视觉和语言分支。在 Prompt Tuning 中,必须采取多模态方法获得同时适应 CLIP 的视觉和语言分支,以实现上下文优化的完整性。

weixin_48907099的博客 1051

SAP物料分类账实战:从配置到月结的关键步骤(基于S/4HANA1909)

本文详细解析了在SAP S/4HANA 1909系统中,物料分类账从基础配置到月结操作的全流程。作为成本核算的核心,物料分类账通过多级价格确定实现实际成本核算,关键步骤包括激活物料分类账、配置自动过账科目(OBYC)、运行CKMLCP进行差异分摊与结算。文章旨在帮助财务与成本会计人员确保成本差异的准确归集与分摊,顺利完成月结。

weixin_28314575的博客 193

Netty解决半包(TCP粘包/拆包导致)读写问题

摘要 使用Netty进行异步IO编程,同事问我粘包/拆包问题如何处理,所以抽空分析一下TCP粘包/拆包问题的产生;Netty提供了多种编解码器用于处理半包问题,熟练掌握了类库的应用,TCP粘包问题变得很容易。 TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限没有分割的一串数据。TCP会根据缓冲区的实际情况进行包划分,一个完整的包可能会拆分成多个包进行发送,也用可能把多

LANGZI7758521的专栏 1万+

OpenGL绘制简单场景,实现旋转缩放平移和灯光效果

本项目实现了用OpenGL绘制一个简单场景,包含正方体、球体和网格,实现了物体的旋转、缩放、平移和灯光效果。附有项目完整代码,有详细注释。适合初学者熟悉opengl使用。

张竞成的博客 1万+

Netty 学习笔记(六)、Netty 如何解决 TCP 粘包/拆包问题

概述 在早先博客我通过 NIO 实现时间服务器时曾提到:TCP 可能造成粘包/拆包问题,但由于之前的示例相对简单,因此没有做预防处理。本篇博客我就来简单聊一下 TCP 粘包/拆包 问题以及 Netty 如何解决该问题。 什么是 TCP 粘包/拆包问题 TCP 是一种流传输协议,所谓流传输就是指数据包之间没有界限,所有数据都是连在一起的。顶层应用层通过 TCP 发送数据时,TCP 并不了解该数据具体含义,它只会根据当前缓冲区的实际情况进行数据包的划分。这样就可能导致一个完整的业务数据包被拆分为多个TCP包发

meiyongdesan的博客 597

BF8815A

了解ADBMS1818芯片的人会发现,这款BF8815B和1818有着很多相似的东西,当你了解其中一款AFE之后,另一款你的上手也会特别快,下面文章是我针对1818梳理的一些重要知识点,此篇文章主要讲解BF8815B的一些差异点。

m0_61973119的博客 561

【知识分享】高速印刷电路板(PCB)寄生参数计算指南

对于高速PCB设计而言,寄生参数一直是令人讨厌的问题,它们会悄悄潜入PCB并在电路中造成严重的破坏,具体来说,它们是渗透到高速电路中的隐藏杂散电容器和电感器,包括由封装引线和长走线形成的电感器、PCB焊盘对金属平面形成的电容器、走线之间形成的电容器等等。

一只豌豆象的博客 1779

TCP粘包/拆包问题之Netty解决方案

TCP以流的方式进行数据传输,上层的应用协议为了对消息进行区分,往往采用如下四种方式: 消息长度固定,累计读取到长度总和为定长LEN的报文后,就认为读取到了一个完整的消息;将计数器置位,重新开始读取下一个数据报; 将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛; 将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的结束分隔符; 通过在消息头中定义...

. 449

锂电池充电器电路设计 里面有电路

锂电池充电器电路设计 里面有电路 3.7V升压到5V电路设计。

Netty之TCP粘包/拆包问题

什么是TCP的粘包/拆包呢? 在tcp编程当中,不管是服务端还是客户端,当我们读取或者发送消息时,都需要考虑到tcp底层的粘包/拆包机制,咱们来看一张图 此图来源于《Netty权威指南 第二版》作者李林锋,本次博客也是读了这本书,学习了在此记录一下,很感谢作者。 言归正传,在此图当中,我们可以看见client 端分别发送两个数据包D1和D2到server端。由于服务端读取的字节数是不确定...

zhoujie的博客 194

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

Netty学习——TCP粘包/拆包的问题

TCP粘包/拆包的问题 TCP粘包的概念         TCP是一个流协议,是通过输入输出流来输入输出数据的,因而底层是不知道业务逻辑。TCP的装包是根据TCP缓冲区的实际情况进行包的划分的,也就是TCP底层如果发现缓冲区的数据已经足够,才会把数据发送出来。这样,我们写到缓冲区中的数据其实是没有任何逻辑的,比如说可能我们写到缓冲区中的数据是1个半的消息(这个时候叫粘包,也就是多个业务逻辑包粘

Benson的博客 269

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

Netty解决TCP粘包/拆包的问题

什么是TCP粘包/拆包   首先要明确, 粘包问题中的 “包”, 是指应用层的数据包.在TCP的协议头中, 没有如同UDP一样的 “报文长度” 字段,但是有一个序号字段.   站在传输层的角度, TCP是一个一个报文传过来的. 按照序号排好序放在缓冲区中.   站在应用层的角度, 看到的只是一串连续的字节数据.那么应用程序看到了这一连串的字节数据, 就不知道从哪个部分开始到哪个部分是一个完整的应用...

波波烤鸭的博客 3753

Netty:TCP粘包/拆包问题解决之道

1.TCP粘包/拆包 TCP是一个“流”协议,流,就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。 2.粘包问题的解决策略 由于底层TCP无法理解上层的业务数据,所以底层无法保证数据包不被拆分和重组,这个问题只能通过上层的应用协议栈设计来解决,根据业界主流的解决方案,归纳如下: 1.消息定长,例

chenbin1991smile的博客 405
上一篇: Docker 一站式部署 Spring Cloud Alibaba 多种不同服务
下一篇: Netty 基础
RedRay0
博客等级 码龄2年 120粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值