12.netty中tcp粘包拆包问题及解决方法

【README】

  • 1.本文源代码总结自B站《netty-尚硅谷》;
  • 2.本文介绍了tcp粘包拆包问题;
  • 3.本文po 出了粘包拆包问题解决方案及源代码实现;

【1】tcp粘包拆包问题

refer2 How to deal with the problem of packet sticking and unpacking during TCP transmission? - 编程知识

【1.1】粘包拆包问题描述

  • 假设客户端发送2个连续的数据包到服务器,数据包用packet1,packet2分别表示,则服务器接收到的数据可以分为3种情况

1)情况1: 服务器接收到2个数据包,没有拆包,也没有粘包问题

 2)情况2: 服务器只接收到一个数据包(存在粘包问题

  • 因为tcp不会丢失数据包,因此这一个数据包就封装了2个原生数据包的信息,这种现象叫做粘包
  • 在这种情况,接收者并不知道2个原生包的界限,因此接收者很难处理;

3)情况3: 接收者接收到2个冗余或不完整的数据包(粘包与拆包问题同时发生

  • 接收者接收到2个数据包,但这2个数据包要么不完整,要么掺杂了其他数据包的部分数据
  • 在这种情况下,粘包与拆包同时发生
  • 如果这2个包不被特殊处理,对于接收者来说也很难处理;


【1.2】代码演示粘包拆包问题 

注意:

  • 限于篇幅,本节没有po出全部代码, 能够表达意思即可;

1)业务场景:客户端连续发送10条消息(字符串)到服务器,查看服务器接收情况;

2)客户端发送消息代码:

 3)服务器接收消息代码:

3.1)服务器接收消息的打印效果:

=================================
服务器收到的数据 hello server0服务器累计收到 [1] 个消息包
=================================
服务器收到的数据 hello server1服务器累计收到 [2] 个消息包
=================================
服务器收到的数据 hello server2
hello server3
hello server4
hello server5
hello server6服务器累计收到 [3] 个消息包
=================================
服务器收到的数据 hello server7
hello server8
hello server9服务器累计收到 [4] 个消息包

【效果解说】

  • 客户端发送了10条消息,服务器接收到了 4个数据包,而不是10个数据包
  • 显然,发生了tcp粘包
  • 这10条消息本来是10个数据报文,却被合并(粘)为4个数据包;
  • 问题是: 如何把这4个数据包还原为10个数据包呢 (在高并发情况下,各式各样的数据包会更多)
    • 如果无法还原,则服务器无法正确解析报文并做相应处理;

 【2】 粘包与拆包原因 

1)粘包原因:

  • 发送的数据大小 小于 发送缓冲区,tcp就会把发送的数据多次写入缓冲区,此时发生粘包;
  • 接收数据方的应用层没有及时从 接收缓冲区读取数据,也会发生粘包;

2)拆包原因:

  • 发送的数据大小 大于 tcp发送缓冲区,就会发生拆包;
  • 发送的数据大小 大于 报文最大长度,也会拆包;

【3】粘包拆包解决方法

解决粘包拆包的关键在于 为每一个数据包添加界限标识,常用方法如下:

  • 方法1)发送方为每一个数据包添加报文头部。头部至少包含数据包长度(类似http协议的头部length)。 通过这种方式,接收方通过读取头部的长度知道当前数据包的界限,并在界限处停止读取。
  • 方法2)发送方以固定长度封装数据包。如果不足,则补0填充。
  • 方法3)自定义设置数据包的界限标识,如添加特别标识(如======)。接收方通过标识可以识别不同的数据包;

【4】粘包拆包问题解决的源代码实现

解决方法是:采用方法1,设置每个数据包的长度到报文头部

【4.1】协议数据包封装类

/*** @Description 协议数据包* @author xiao tang* @version 1.0.0* @createTime 2022年09月10日*/
public class ProtocolMessage {private int length;private byte[] content;/*** @description 构造器* @author xiao tang* @date 2022/9/10*/public ProtocolMessage() {}public int getLength() {return length;}public void setLength(int length) {this.length = length;}public byte[] getContent() {return content;}public void setContent(byte[] content) {this.content = content;}
}

【4.2】服务器

1)服务器 :

public class ProtocolNettyServer89 {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ProtocolNettyServerInitializer()); // 自定义一个初始化类// 自动服务器ChannelFuture channelFuture = serverBootstrap.bind(8089).sync();System.out.println("服务器启动成功");// 监听关闭channelFuture.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

2) 服务端初始化器:

public class ProtocolNettyServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();// 添加入站解码器-把字节转为协议报文便于业务逻辑处理pipeline.addLast(new ProtocolMessageDecoder());// 添加出站编码器-把协议报文转为字节便于网络传输pipeline.addLast(new ProtocolMessageEncoder());// 添加业务逻辑handlerpipeline.addLast(new ProtocolNettyServerHandler());}
}

3)处理器:

public class ProtocolNettyServerHandler extends SimpleChannelInboundHandler<ProtocolMessage> {private int count = 0;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) throws Exception {// 接收到数据并处理int length = msg.getLength();String bodyStr = new String(msg.getContent(), StandardCharsets.UTF_8);System.out.println("====================================");System.out.println("服务器接收的消息如下:");System.out.println("报文长度:" + length);System.out.println("报文体内容: " + bodyStr);System.out.println("服务器累计接收到的消息包数量 = " + ++this.count);//  回复客户端byte[] body = ("我是服务器" + count).getBytes(StandardCharsets.UTF_8);int responseLen = body.length;// 构建一个响应协议包ProtocolMessage responseMsg = new ProtocolMessage();responseMsg.setLength(responseLen);responseMsg.setContent(body);ctx.writeAndFlush(responseMsg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

 【4.3】客户端

1)客户端:

public class ProtocolNettyClient89 {public static void main(String[] args) throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ProtocolNettyClientInitializer());  // 自定义一个初始化类// 连接服务器ChannelFuture channelFuture = bootstrap.connect("localhost", 8089).sync();channelFuture.channel().closeFuture().sync();} finally {group.shutdownGracefully();}}
}

2)初始化器:

public class ProtocolNettyClientInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();// 添加出站处理器- 协议报文转字节以便网络传输pipeline.addLast(new ProtocolMessageEncoder());// 添加入站解码器-把字节转为协议报文对象以便业务逻辑处理pipeline.addLast(new ProtocolMessageDecoder());// 添加一个自定义handler,处理业务逻辑pipeline.addLast(new ProtocolNettyClientHandler());}
}

3)处理器:

public class ProtocolNettyClientHandler extends SimpleChannelInboundHandler<ProtocolMessage> {private int count;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ProtocolMessage msg) throws Exception {// 读取服务器响应报文int length = msg.getLength();byte[] body = msg.getContent();System.out.println("=============================");System.out.println("客户端接收的消息如下:");System.out.println("长度 = " + length);System.out.println("报文体 = " + new String(body, StandardCharsets.UTF_8));System.out.println("客户端累计接收的消息包数量 = " + ++count);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 发送10条数据到服务器for (int i = 1; i <= 5; i++) {byte[] body = ("你好服务器,我是客户端张三" + i).getBytes(StandardCharsets.UTF_8);// 创建协议包对象ProtocolMessage message = new ProtocolMessage();message.setContent(body);message.setLength(body.length);// 发送ctx.writeAndFlush(message);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

【4.4】编码器与解码器

1)解码器

/*** @Description 协议报文解码器* @author xiao tang* @version 1.0.0* @createTime 2022年09月10日*/
public class ProtocolMessageDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {System.out.println("ProtocolMessageDecoder.decode() 被调用");//  把字节 转为 协议报文int length = in.readInt();byte[] body = new byte[length];in.readBytes(body);// 封装成 ProtocolMessage,放入out,送入下一个 Handler处理ProtocolMessage protocolMessage = new ProtocolMessage();protocolMessage.setLength(length);protocolMessage.setContent(body);// 添加到outout.add(protocolMessage);}
}

2)编码器 :

/*** @Description 协议消息编码器* @author xiao tang* @version 1.0.0* @createTime 2022年09月10日*/
public class ProtocolMessageEncoder extends MessageToByteEncoder<ProtocolMessage> {@Overrideprotected void encode(ChannelHandlerContext ctx, ProtocolMessage msg, ByteBuf out) throws Exception {System.out.println("ProtocolMessageEncoder.encode() 被调用");out.writeInt(msg.getLength());out.writeBytes(msg.getContent());}
}

【4.5】目录结构:

【4.6】打印效果:

1)客户端发送5条消息到服务器:

2)服务器接收的数据包为 5个,如下(显然没有发生拆包粘包问题):

ProtocolMessageDecoder.decode() 被调用
====================================
服务器接收的消息如下:
报文长度:40
报文体内容: 你好服务器,我是客户端张三1
服务器累计接收到的消息包数量 = 1
ProtocolMessageEncoder.encode() 被调用
ProtocolMessageDecoder.decode() 被调用
====================================
服务器接收的消息如下:
报文长度:40
报文体内容: 你好服务器,我是客户端张三2
服务器累计接收到的消息包数量 = 2
ProtocolMessageEncoder.encode() 被调用
ProtocolMessageDecoder.decode() 被调用
====================================
服务器接收的消息如下:
报文长度:40
报文体内容: 你好服务器,我是客户端张三3
服务器累计接收到的消息包数量 = 3
ProtocolMessageEncoder.encode() 被调用
ProtocolMessageDecoder.decode() 被调用
====================================
服务器接收的消息如下:
报文长度:40
报文体内容: 你好服务器,我是客户端张三4
服务器累计接收到的消息包数量 = 4
ProtocolMessageEncoder.encode() 被调用
ProtocolMessageDecoder.decode() 被调用
====================================
服务器接收的消息如下:
报文长度:40
报文体内容: 你好服务器,我是客户端张三5
服务器累计接收到的消息包数量 = 5
ProtocolMessageEncoder.encode() 被调用

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

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

相关文章

Oracle入门(十四.7)之良好的编程习惯

一、目的良好的编程实践是可以遵循的技术来创建最佳代码。 编程实践涵盖了从编写更易读的代码到创建具有更快性能的代码。软件工程团队通常会遵循风格指南&#xff0c;以便团队中的每个人都使用相同的技术。 这使得读取和修改其他人编写的代码变得更加容易。二、编程实践您已经…

css 浏览器调试中不可见_前端入门必会的初级调试技巧

本文仅仅针对前端初学者&#xff0c;目的是【用20%不到的时间】 学会【前端最常用的部分调试技巧】&#xff0c;如果需要最详细的调试技巧&#xff0c;包括调试性能优化的相关知识&#xff0c;文末会补充最全的文档&#xff08;chrome devtool的官方文档链接&#xff09;初学一…

升级ASP.Net Core项目

升级完类库项目&#xff0c;第二篇&#xff0c;我们来升级ASP.Net Core项目 修改global.json与project.json 这里可以参照&#xff0c;升级.Net Core RC2的那些事&#xff08;一&#xff09; 这里补充一点就是如果你觉得这样修改复杂&#xff0c;你完全可以新建一个项目&#x…

gophp解释器_【干货】Gisp 解释器 Golang 辅助开发工具

Gisp 是一个提供给 golang 使用的 Lisp 类 DSL 解释器。在 Lisp 的基本语法基础上&#xff0c;针对 go 环境稍作了一点语法糖。主要目标是提供一个尽可能便于与 golang 互操作的微型DSL工具。简介Gisp用go语言编写&#xff0c;是一个DSL 解释器&#xff0c;这个 DSL 基本上就是…

Oracle入门(十四.8)之迭代控制:基本循环Loop

一、迭代控制&#xff1a;LOOP语句 循环多次重复一个语句或一系列语句。 PL / SQL提供了以下几种类型的循环&#xff1a;•没有全面条件执行重复操作的基本循环 •FOR循环&#xff0c;基于计数器执行迭代操作•WHILE循环根据条件执行重复操作二、基本循环LOOP语句的最简单形式…

phpst安装memcache扩展_在 Ubuntu/Debian 下安装 PHP7.3 教程

介绍最近的 PHP 7.3.0 已经在 2018 年12月6日 发布 GA&#xff0c;大家已经可以开始第一时间体验新版本了&#xff0c;这里先放出 PHP7.3 安装的教程以便大家升级。适用系统&#xff1a; Ubuntu 18.04 LTS / Ubuntu 16.04 LTS &#xff0f; Ubuntu 14.04 LTS / Debian 9 stretc…

升级.Net Core RC1的类库项目

微软终于发布了.Net Code RC2了&#xff0c;作为一个软粉当然是第一时间升级了。《升级.Net Core RC2的那些事》系列文章主要是记录本人升级RC2的相关步骤以及遇到过的坑。 第一篇先写类库项目&#xff08;Nuget包项目&#xff09;的升级 升级VS工具 这里只提供一个下载地址&am…

Oracle入门(十四.9)之迭代控制:WHILE和FOR循环

一、WHILE循环您可以使用WHILE循环重复一系列语句&#xff0c;直到控制条件不再为TRUE。 条件在每次迭代开始时进行评估。当条件为FALSE或NULL时&#xff0c;循环终止。 如果条件在循环开始时为FALSE或NULL&#xff0c;则不会执行进一步的迭代。 WHILE condition LOOPstatement…

为TFS配置跨平台的生成服务器Xplat (Ubuntu Linux)

1. 概述 从TFS 2015开始&#xff0c;微软开始支持跨平台的构建代理。你可以使用TFS的Xplat代理&#xff0c;方便的在基于IOS, Unix和Linux的服务器上搭建生成代理&#xff0c;实现构建、发布等功能。本文档已Ubuntu为例&#xff0c;指导如何安装和运行Xplat代理。 2. 配置TFS的…

分数优先遵循志愿php源码_分数优先 遵循志愿

本报讯 昨日&#xff0c;广东省考试院发布2019年我省普通高校招生平行志愿投档及录取实施办法。今年我省依旧实行普通高校招生平行志愿投档录取模式&#xff0c;按照“分数优先、遵循志愿”的原则&#xff0c;根据考生高考成绩高低排序和院校志愿先后顺序投档&#xff0c;投出…

Oracle入门(十四.10)之显式游标简介

一、上下文区域和游标Oracle服务器分配一个称为上下文区域的私有内存区域来存储由SQL语句处理的数据。 每个上下文区域&#xff08;因此每个SQL语句&#xff09;都有一个与其关联的游标。您可以将游标视为上下文区域的标签&#xff0c;或者将其作为指向上下文区域的指针。 事实…

1.(转)canal背景与工作原理

【README】 1.canal是一个工具&#xff0c;由阿里开源&#xff0c;用于解析mysql的binlog增量日志&#xff0c;重放日志还原出业务数据&#xff0c;下游可以送入 es&#xff0c;mysql&#xff0c;hbase等&#xff1b; 2.本文以下内容转自&#xff1a;GitHub - alibaba/canal:…

Dapper、Entity Framework 和混合应用

你大概注意到了&#xff0c;自 2008 年以来&#xff0c;我写过许多关于 Entity Framework&#xff08;即 Microsoft 对象关系映射器 (ORM)&#xff09;的文章&#xff0c;ORM 一直是主要的 .NET 数据访问 API。市面上还有许多其他 .NET ORM&#xff0c;但是有一个特殊类别因其强…

html让时间只展示年月日_如何用html写代码,使得在网页上显示当前的时间和日期...

展开全部在网页62616964757a686964616fe59b9ee7ad9431333363363537中动态的显示日期时间&#xff0c;一般都是使用js来实现&#xff0c;很简单&#xff0c;一看就会。网页中动态的显示系统日期时间function startTime(){var todaynew Date();//定义日期对象var yyyy today.get…

Oracle入门(十四.11)之使用显式游标属性

一、游标和记录 此示例中的游标基于SELECT语句&#xff0c;该语句仅检索每个表行的两列。 如果它检索了六列或七&#xff0c;八&#xff0c;二十个呢&#xff1f; DECLAREv_emp_id employees.employee_id%TYPE;v_last_name employees.last_name%TYPE;CURSOR emp_cursor ISSEL…

(转 )centos8安装mysql

【1】下载 mysql rpm包 MySQL :: Download MySQL Yum Repositoryhttps://dev.mysql.com/downloads/repo/yum/ 【2】安装mysql 根据官方文档安装&#xff0c;如下&#xff1a; MySQL :: A Quick Guide to Using the MySQL Yum Repositoryhttps://dev.mysql.com/doc/mysql-yu…

IIS负载均衡-Application Request Route详解第一篇: ARR介绍

说到负载均衡&#xff0c;相信大家已经不再陌生了&#xff0c;本系列主要介绍在IIS中可以采用的负载均衡的软件&#xff1a;微软的Application Request Route模块。 其实Application RequestRoute已经有很多文章介绍过了&#xff0c;但是有很多的文档都是英文的&#xff0c;笔者…

单位矩阵的逆矩阵是它本身吗_矩阵运算、单位矩阵与逆矩阵(二)

逆矩阵什么是逆矩阵&#xff1f;数有倒数&#xff1a;逆矩阵也是相同的概念&#xff0c;但我们写为A-1逆矩阵的定义计算逆矩阵我们怎么知道计算结果是正确的&#xff1f;我们把矩阵和逆矩阵相乘来看看&#xff1a;我们为什么需要逆矩阵&#xff0c;举个例子&#xff1a;一帮人坐…

Oracle入门(十四.12)之游标FOR循环

一、游标FOR循环游标FOR循环处理显式游标中的行。 这是一个快捷方式&#xff0c;因为游标被打开&#xff0c;循环中的每次迭代都会获取一次行&#xff0c;当处理最后一行时会退出循环&#xff0c;并且游标会自动关闭。 当最后一行被提取时&#xff0c;循环本身在迭代结束时自动…

结合Jexus + Kestrel 部署 asp.net core 生产环境

ASP.NET Core 是微软的全新的框架。这一框架的目标 ︰ 跨平台针对云应用优化解除 System.Web 的依赖。 获得下面三个方面的优势&#xff0c;你可以把它认为是一个C# 版本的NodeJS&#xff1a; 1&#xff09; 模块化实现 2&#xff09; 一切都尽可能的-异步 3&#xff09; 依赖关…