Netty中解决粘包/半包

目录

什么是TCP粘包半包?

TCP 粘包/半包发生的原因

解决粘包半包

channelRead和channelReadComplete区别


什么是TCP粘包半包?

                

       假设客户端分别发送了两个数据包 D1 和 D2 给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下 4 种情况。

1. 服务端分两次读取到了两个独立的数据包,分别是 D1 和 D2,没有粘包和拆包。

2. 服务端一次接收到了两个数据包,D1 和 D2 粘合在一起,被称为 TCP 粘包。

3. 服务端分两次读取到了两个数据包,第一次读取到了完整的 D1 包和 D2 包的部分内容,第二次读取到了 D2 包的剩余内容,这被称为 TCP 拆包。

4. 服务端分两次读取到了两个数据包,第一次读取到了 D1 包的部分内容 D1_1,第二次读取到了 D1 包的剩余内容 D1_2 和 D2 包的整包。

       如果此时服务端 TCP 接收滑窗非常小,而数据包 D1 和 D2 比较大,很有可能会发生第五种可能,即服务端分多次才能将 D1 和 D2 包接收完全,期间发生多次拆包。


TCP 粘包/半包发生的原因

       由于 TCP 协议本身的机制(面向连接的可靠地协议-三次握手机制)客户端与服务器会维持一个连接(Channel),数据在连接不断开的情况下,可以持续不断地将多个数据包发往服务器,但是如果发送的网络数据包太小,那么他本身会启用 Nagle 算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP 的网络延迟要 UDP 的高些)然后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就无法区分哪些数据包是客户端自己分开发送的,这样产生了粘包。服务器在接收到数据库后,放到缓冲区中,如果消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的情况,造成粘包现象。

       UDP:本身作为无连接的不可靠的传输协议(适合频繁发送较小的数据包),他不会对数据包进行合并发送(也就没有 Nagle 算法之说了),他直接是一端发送什么数据,直接就发出去了,既然他不会对数据合并,每一个数据包都是完整的(数据+UDP头+IP头等等发一次数据封装一次)也就没有粘包一说了。

分包产生的原因简单来说:

        应用程序写入数据的字节大小大于套接字发送缓冲区的大小,就是一个数据包被分成了多次接收。


解决粘包半包

解决方案:

1. 增加分割符。

客户端传送数据时带上分隔符,服务端接收数据时,通过分隔符判断数据是否发送完整。

核心代码如下:

 public static final String DELIMITER_SYMBOL = "@~";

 private static class ChannelInitializerImp extends ChannelInitializer<Channel> {

        @Override
        protected void initChannel(Channel ch) throws Exception {
            ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_SYMBOL.getBytes());
            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,
                    delimiter));
            ch.pipeline().addLast(new DelimiterServerHandler());
        }
    }

2. 消息定长,例如每个报文的大小为固定长度 200 字节,如果不够,空位补空格。

   //客户端按照request的长度发送数据,服务端以request的长度接收数据
   public final static String REQUEST = "hello,netty!";

   private static class ChannelInitializerImp extends ChannelInitializer<Channel> {

        @Override
        protected void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(
                    new FixedLengthFrameDecoder(
                            FixedLengthEchoClient.REQUEST.length()));
            ch.pipeline().addLast(new FixedLengthServerHandler());
       }
   }

   //服务端按照response的长度发送数据,服务端以response的长度接收数据
   public static final String RESPONSE = "Welcome to Netty!";
  
   private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
        @Override
        protected void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(
                    new FixedLengthFrameDecoder(
                            FixedLengthEchoServer.RESPONSE.length()));
            ch.pipeline().addLast(new FixedLengthClientHandler());
        }
    }

3. 将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度,使用LengthFieldBasedFrameDecoder。

   //客户端跟服务端handle都需要添加如下代码
   ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));

   //maxFrameLength: 指定消息的最大长度,超过这个长度的消息会被丢弃。
   //lengthFieldOffset: 指定长度字段在消息中的偏移量。
   //lengthFieldLength: 指定长度字段的长度。
   //lengthAdjustment: 在计算消息长度时,需要添加的偏移量,通常为长度字段本身的长度。
   //initialBytesToStrip: 解析出一条完整消息后,需要跳过的字节数,通常设置为长度字段的长度。

channelRead和channelReadComplete区别

两者的区别:
       Netty 是在读到完整的业务请求报文后才调用一次业务 ChannelHandler 的 channelRead
方法,无论这条报文底层经过了几次 SocketChannel 的 read 调用。
       但是 channelReadComplete 方法并不是在业务语义上的读取消息完成后被触发的,而是
在每次从 SocketChannel 成功读到消息后,由系统触发,也就是说如果一个业务消息被 TCP
协议栈发送了 N 次,则服务端的 channelReadComplete 方法就会被调用 N 次。

       简单理解就是,消息会被发送到接收缓冲区,如果一条消息很大需要经过两次发送到接收缓冲区才能发完,当收到一个完整的应用层消息报文,channelRead会被触发一次。而ReadComplete方法每次读取完接收缓冲区的报文,channelReadComplete才会被触发一次。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/377394.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot 笔记 002 整合mybatis做数据库读取

概念 MyBatis 是一款优秀的持久层框架&#xff0c;它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO&#xff08;Plain Old Java Objec…

grafana+prometheus+hiveserver2(jmx_exporter+metrics)

一、hiveserver2开启metrics&#xff0c;并启动jmx_exporter 1、修改hive-site.xml文件开启metrics <property><name>hive.server2.metrics.enabled</name><value>true</value> </property> <property><name>hive.service.m…

Spring Cloud使用ZooKeeper作为注册中心的示例

简单的Spring Cloud应用程序使用ZooKeeper作为注册中心的示例&#xff1a; 1.新建模块&#xff1a; 2.勾选依赖&#xff1a; 3.在pom.xml文件中做出部分修改及添加Spring Cloud Zookeeper 依赖版本&#xff1a; 完整pom文件 <?xml version"1.0" encoding&q…

兼容ARM 32位架构的edgeConnector产品为用户提供新的部署选项

Softing工业将ARM 32位兼容性集成到了edgeConnector产品中&#xff0c;以满足用户对ARM处理器的边缘设备日益增长的使用需求。 &#xff08;兼容ARM 32位架构的edgeConnector产品扩展了其应用部署范围&#xff09; 用户对采用ARM处理器的紧凑型边缘设备的需求正在大幅增长&…

基于高通滤波器的ECG信号滤波及心率统计matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 ECG信号简介 4.2 高通滤波器原理 4.3 心率统计 5.完整工程文件 1.课题概述 通过高通滤波器对ECG信号进行滤波&#xff0c;然后再统计其心率。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a…

IAR报错:Error[Pa045]: function “halUartInit“ has no prototype

在IAR工程.c文件末尾添加一个自己的函数&#xff0c;出现了报错Error[Pa045]: function "halUartInit" has no prototype 意思是没有在开头添加函数声明&#xff0c;即void halUartInit(void); 这个问题我们在keil中不会遇到&#xff0c;这是因为IAR编译器规则的一…

JavaScript 入门

目录 第一个知识点&#xff1a;引入js文件 内部引用: 外部引用: 第二个知识点&#xff1a;javascript的基本语法 定义变量&#xff1a; 条件控制(if - else if - else) 第三个知识点&#xff1a;javascript里的数据类型、运算符&#xff1a; 数字类型 字符串类型 布尔…

Numpy数学计算函数,精简备忘

文章目录 简单的数学函数unwraptrapz 简单的数学函数 下面这张表列出了Numpy中几乎所有常用的 数学函数&#xff0c;这些函数都有着明确的数学概念&#xff0c;其主要的输入输出均符合来自数学公式的直觉&#xff0c;大部分数组操作则支持通过axis来调整作用的坐标轴&#xff…

BeginCTF 2024(新生赛道)WP-P1sc3s007

1. xor 算法不复杂&#xff0c;就是大量的异或&#xff0c;慢慢还原就好 a1 6329079420771558 a2 7679621386735000 a3 flag前16 a4 flag后16 a3和a2逐位异或 a4和a1逐位异或 a3 a1 a4 a2 a3和a2倒序异或 a4 a1 a3 a1 a4 a2a5前…

CTFshow web(命令执行29-36)

?ceval($_GET[shy]);&shypassthru(cat flag.php); #逃逸过滤 ?cinclude%09$_GET[shy]?>&shyphp://filter/readconvert.base64-encode/resourceflag.php #文件包含 ?cinclude%0a$_GET[cmd]?>&cmdphp://filter/readconvert.base64-encode/…

数据结构——顺序表详解

目录 前言 一.线性表 1.概念 二.顺序表 1.概念 2.分类 2.1静态顺序表 2.2动态顺序表 前言 数据结构是计算机存储、组织数据的方式. 一.线性表 1.概念 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列.线性表是一种在实际中广泛使用的…

Windows Server 2019 DHCP服务器搭建

系列文章目录 目录 系列文章目录 文章目录 前言 一、DHCP服务器是什么&#xff1f; 二、配置服务器 1.实验环境搭建 1)实验服务器配置和客户端 2)实验环境 2.服务器配置 ​编辑 文章目录 Windows Server 2003 Web服务器搭建Windows Server 2003 FTP服务器搭建Windows S…

【iOS ARKit】人形遮挡

人形遮挡简介 在 AR系统中&#xff0c;计算机通过对设备摄像头采集的图像进行视觉处理和组织&#xff0c;建立起实景空间&#xff0c;然后将生成的虚拟对象依据几何一致性原理嵌入到实景空间中&#xff0c;形成虚实融合的增强现实环境&#xff0c;再输出到显示系统中呈现给使用…

CoreSight学习笔记

文章目录 1 Components1.1 ROM Table 2 使用场景2.1 Debug Monitor中断2.1.1 参考资料 2.2 Programming the cross halt2.2.1 编程实现2.2.2 参考资料 2.3 CTI中断2.3.1 编程实现2.3.1.1 准备工作2.3.1.2 触发中断2.3.1.3 中断响应 2.3.2 参考资料 1 Components 1.1 ROM Table…

人体生物钟程序设计(C语言)

前几年在本站发布过博文介绍人体生物钟程序的制作方法。后来发现上传后显示的博文有错漏&#xff0c;计算符号脱漏。这会误导读者。今修订整理重新发布&#xff0c;展示一下漂亮的界面设计。 人体生物钟也就是人体生物节律。人体生物节律是自然进化赋予生命的基本特征之一&…

Docker(2)

Docker Docker数据卷挂载常用命令 注意&#xff1a;容器与数据卷的挂载要在创建容器时配置&#xff0c;对于创建好的容器&#xff0c;是不能设置数据卷的。而且创建容器的过程中&#xff0c;数据卷会自动创建。建容器并指定数据卷&#xff0c;注意通过 -v 参数来指定数据卷&am…

【iOS ARKit】人形提取

为解决人形分离和深度估计问题&#xff0c;ARKit 新增加了 Segmentation Buffer&#xff08;人体分隔缓冲区&#xff09;和Estimated Depth Data Buffer&#xff08;深度估计缓冲区&#xff09;两个缓冲区。人体分隔缓冲区作用类似于图形渲染管线中的 Stencil Buffer&#xff0…

洛谷C++简单题小练习day9—[AHOI2017]寻找探监点

day9--[AHOI2017]寻找探监点--2.7 习题概述 题目描述 一个nn 的网格图&#xff08;标号由 1,1 开始&#xff09;上有 m 个探测器&#xff0c;每个探测器有个探测半径 r &#xff0c;问这 nn 个点中有多少个点能被探测到。 输入格式 第一行 3 个整数 n,m,r。 接下来 m 行&…

国内首个openEuler师训营圆满结营! 麒麟信安助力培养国产操作系统高质量师资人才

2024年1月22日&#xff0c;全国首个openEuler师训营圆满结营&#xff01;旨在深化产教融合&#xff0c;加速开源教育走进高校&#xff0c;提高师资队伍openEuler专业能力及实践教学水平。 本次师训营由长沙市大数据产业链、长沙市新一代自主安全计算系统产业链指导&#xff0c…

【Docker】Docker Image(镜像)

文章目录 一、Docker镜像是什么&#xff1f;二、镜像生活案例三、为什么需要镜像四、镜像命令详解docker rmidocker savedocker loaddocker historydocker image prune 五、镜像操作案例六、镜像综合实战实战一、离线迁移镜像实战二、镜像存储的压缩与共享 一、Docker镜像是什么…