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> {@Overrideprotected 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> {@Overrideprotected 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> {@Overrideprotected 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才会被触发一次。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于微信小程序的书籍阅读系统,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Redis核心技术与实战【学习笔记】 - 27.限制Redis Cluster规模的因素(通信开销)

简述 Redis Cluster 能保存的数据量以及支撑的吞吐量&#xff0c;跟集群实例规模相关。 Redis 官方给出了 Redis Cluster 的规模上线&#xff0c;就是一个集群运行 1000 个实例。 其实&#xff0c;限定 Redis Cluster 集群规模的一个关键因素就是&#xff0c;实例间的通信开销…

使用SD-WAN进行企业网络升级的必要性

随着企业业务的不断扩大&#xff0c;企业的网络建设成为业务成功的基础。对于中小企业而言&#xff0c;提高生产力和竞争力&#xff0c;电脑化和信息化已成为不可逆转的趋势。各种关键业务应用如办公室自动化、数据库、ERP、CRM、物流供应链等的增加&#xff0c;对网络的速度、…

JAVA生产使用登录校验模式

背景 目前我们的服务在用户登录时&#xff0c;会先通过登录接口进行密码校验。一旦验证成功&#xff0c;后端会利用UUID生成一个独特的令牌&#xff08;token&#xff09;&#xff0c;并将其存储在Redis缓存中。同时&#xff0c;前端也会将该令牌保存在本地。在后续的接口请求…

基于51 单片机的交通灯系统 源码+仿真+ppt

主要内容&#xff1a; 1&#xff09;南北方向的绿灯、东西方向的红灯同时亮40秒。 2&#xff09;南北方向的绿灯灭、黄灯亮5秒&#xff0c;同时东西方向的红灯继续亮。 3&#xff09;南北方向的黄灯灭、左转绿灯亮&#xff0c;持续20秒&#xff0c;同时东西方向的红灯继续…

【Git版本控制 03】远程操作

目录 一、克隆远程仓库 二、推送远程仓库 三、拉取远程仓库 四、忽略特殊文件 五、命令配置别名 一、克隆远程仓库 Git是分布式版本控制系统&#xff0c;同⼀个Git仓库&#xff0c;可以分布到不同的机器上。怎么分布呢&#xff1f; 找⼀台电脑充当服务器的⻆⾊&#xff…

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…

FPGA_工程_按键控制的基于Rom数码管显示

一 信号 框图&#xff1a; 其中 key_filter seg_595_dynamic均为已有模块&#xff0c;直接例化即可使用&#xff0c;rom_8*256模块&#xff0c;调用rom ip实现。Rom_ctrl模块需要重新编写。 波形图&#xff1a; 二 代码 module key_fliter #(parameter CNT_MAX 24d9_999_99…

LEETCDE 220. 存在重复元素 III

class Solution { public:long long size;bool containsNearbyAlmostDuplicate(vector<int>& nums, int indexDiff, int valueDiff) {//桶排序unordered_map<long,long> m;sizevalueDiff1;for(int i0;i<nums.size();i){//控制数值long long idxgetID(nums[i…

实践动物姿态估计,基于最新YOLOv8全系列【n/s/m/l/x】参数模型开发构建公共场景下行人人员姿态估计分析识别系统

姿态估计&#xff08;PoseEstimation&#xff09;在我们前面的相关项目中涉及到的并不多&#xff0c;CV数据场景下主要还是以目标检测、图像识别和分割居多&#xff0c;最近正好项目中在使用YOLO系列最新的模型开发项目&#xff0c;就想着抽时间基于YOLOv8也开发构建实现姿态估…

【Linux】gdb调试与make/makefile工具

目录 导读 1. make/Makefile 1.1 引入 1.2 概念 1.3 语法规则 1.4 示例 2. Linux调试器-gdb 2.1 引入 2.2 概念 2.3 使用 导读 我们在上次讲了Linux编辑器gcc\g的使用&#xff0c;今天我们就来进一步的学习如何调试&#xff0c;以及makefile这个强大的工具。 1. mak…

STM32F1 - 工程模板_标准外设库

Project 1> 程序框架2> 文件结构3>标准外设库 - 头文件包含关系4> 移植中出现的错误4.1> 编译器版本选择4.2> 工程宏定义 N> 资料链接 1> 程序框架 根据硬件分层&#xff0c;把软件分层 2> 文件结构 为方便管理&#xff0c;将启动文件startup_stm…