粘包与拆包

news/2024/7/27 7:38:56/文章来源:https://blog.csdn.net/zhengzhaoyang122/article/details/136435475

优质博文:IT-BLOG-CN

一、粘包出现的原因

服务端与客户端没有约定好要使用的数据结构。Socket Client实际是将数据包发送到一个缓存buffer中,通过buffer刷到数据链路层。因服务端接收数据包时,不能断定数据包1何时结束,就有可能出现数据包2的部分数据结合数据包1发送出去,导致服务器读取数据包1时包含了数据包2的数据。这种现象称为粘包。
在这里插入图片描述

二、案例展示

【1】服务端代码如下,具体注释说明

package com.server;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;/*** Netty5服务端* @author zhengzx**/
public class ServerSocket {public static void main(String[] args) {//创建服务类ServerBootstrap serverBootstrap = new ServerBootstrap();//boss和workerNioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();try {//设置线程池serverBootstrap.group(boss,worker);//设置socket工厂,Channel 是对 Java 底层 Socket 连接的抽象serverBootstrap.channel(NioServerSocketChannel.class);//设置管道工厂serverBootstrap.childHandler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) throws Exception {//设置后台转换器(二进制转换字符串)ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringEncoder());ch.pipeline().addLast(new ServerSocketHandler());}});//设置TCP参数serverBootstrap.option(ChannelOption.SO_BACKLOG, 2048);//连接缓冲池大小serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);//维持连接的活跃,清除死连接serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);//关闭超时连接ChannelFuture future = serverBootstrap.bind(10010);//绑定端口System.out.println("服务端启动");//等待服务端关闭future.channel().closeFuture().sync();} catch (Exception e) {e.printStackTrace();} finally {//释放资源boss.shutdownGracefully();worker.shutdownGracefully();}}
}

【2】ServerSocketHandler处理类展示:

package com.server;import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;public class ServerSocketHandler extends SimpleChannelInboundHandler<String>{@Overrideprotected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println(msg);}}

【3】客户端发送请求代码展示:

package com.client;import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;public class Client {public static void main(String[] args) throws UnknownHostException, IOException {//创建连接Socket socket = new Socket("127.0.0.1", 10010);//循环发送请求for(int i=0;i<1000;i++){socket.getOutputStream().write("hello".getBytes());}    //关闭连接socket.close();}
}

【4】打印结果。(正常情况应为一行一个hello打印)

三、分包

数据包数据被分开一部分发送出去,服务端一次读取数据时可能读取到完整数据包的一部分,剩余部分被第二次读取。具体情况如下图展示:

四、解决办法

方案一:定义一个稳定的结构:

【1】包头+length+数据包: 客户端代码展示:包头用来防止socket攻击,length用来获取数据包的长度。

package com.server;import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;import org.omg.CORBA.PRIVATE_MEMBER;
import org.omg.CORBA.PUBLIC_MEMBER;/*** @category 通过长度+数据包的方式解决粘包分包问题* @author zhengzx**/
public class Client {//定义包头public static int BAO = 24323455;public static void main(String[] args) throws UnknownHostException, IOException {//创建连接Socket socket = new Socket("127.0.0.1", 10010);//客户端发送的消息String msg = "hello";//获取消息的字节码byte[] bytes = msg.getBytes();//初始化buffer的长度:4+4表示包头长度+存放数据长度的整数的长度ByteBuffer buffer = ByteBuffer.allocate(8+bytes.length);//将长度和数据存入buffer中buffer.putInt(BAO);buffer.putInt(bytes.length);buffer.put(bytes);//获取缓冲区中的数据byte[] array = buffer.array();//循环发送请求for(int i=0;i<1000;i++){socket.getOutputStream().write(array);}    //关闭连接socket.close();}
}

【2】服务端: 需要注意的是,添加了MyDecoder类,此类具体下面介绍

package com.server;import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
import org.jboss.netty.handler.codec.string.StringDecoder;
import org.jboss.netty.handler.codec.string.StringEncoder;public class Server {public static void main(String[] args) {//服务类ServerBootstrap bootstrap = new ServerBootstrap();//boss线程监听端口,worker线程负责数据读写ExecutorService boss = Executors.newCachedThreadPool();ExecutorService worker = Executors.newCachedThreadPool();//设置niosocket工厂bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));//设置管道的工厂bootstrap.setPipelineFactory(new ChannelPipelineFactory() {@Overridepublic ChannelPipeline getPipeline() throws Exception {ChannelPipeline pipeline = Channels.pipeline();pipeline.addLast("decoder", new MyDecoder());pipeline.addLast("handler1", new HelloHandler());return pipeline;}});bootstrap.bind(new InetSocketAddress(10101));System.out.println("start!!!");}}

【3】MyDecode类: 需要继承FrameDecoder类。此类中用ChannelBuffer缓存没有读取的数据包,等接收到第二次发送的数据包时,会将此数据包与缓存的数据包进行拼接处理。当return一个String时,FarmedDecoder通过判断返回类型,调用相应的sendUpStream(event)向下传递数据。源码展示:

public static void fireMessageReceived(ChannelHandlerContext ctx, Object message, SocketAddress remoteAddress) {ctx.sendUpstream(new UpstreamMessageEvent(ctx.getChannel(), message, remoteAddress));}
}

当返回null时,会进行break,不处理数据包中的数据,源码展示:

while (cumulation.readable()) {int oldReaderIndex = cumulation.readerIndex();Object frame = decode(context, channel, cumulation);if (frame == null) {if (oldReaderIndex == cumulation.readerIndex()) {// Seems like more data is required.// Let us wait for the next notification.break;} else {// Previous data has been discarded.// Probably it is reading on.continue;}}
}

我们自己写的MyDecoder类,代码展示:(包含socket攻击的校验)

package com.server;import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;public class MyDecoder extends FrameDecoder{@Overrideprotected Object decode(ChannelHandlerContext arg0, Channel arg1, ChannelBuffer buffer) throws Exception {//buffer.readableBytes获取缓冲区中的数据 需要 大于基本长度if(buffer.readableBytes() > 4) {//防止socket攻击,当缓冲区数据大于2048时,清除数据。if(buffer.readableBytes() > 2048) {buffer.skipBytes(buffer.readableBytes());}//循环获取包头,确定数据包的开始位置while(true) {buffer.markReaderIndex();if(buffer.readInt() == Client.BAO) {break;}//只读取一个字节buffer.resetReaderIndex();buffer.readByte();if(buffer.readableBytes() < 4) {return null;}}//做标记buffer.markReaderIndex();//获取数据包的发送过来时的长度int readInt = buffer.readInt();//判断buffer中剩余的数据包长度是否大于单个数据包的长度(readInt)if(buffer.readableBytes() < readInt) {//返回到上次做标记的地方,因为此次数据读取的不是一个完整的数据包。buffer.resetReaderIndex();//缓存当前数据,等待剩下数据包到来return null;}//定义一个数据包的长度byte[] bt = new byte[readInt];//读取数据buffer.readBytes(bt);//往下传递对象return new String(bt);}//缓存当前数据包,等待第二次数据的到来return null;}
}

【4】服务端: 处理请求的handler

package com.server;import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;public class HelloHandler extends SimpleChannelHandler {private int count = 1;@Overridepublic void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {System.out.println(e.getMessage() + "  " +count);count++;}
}

【5】结果展示(按顺序打印):
结果展示

方案二: 在消息的尾部加一些特殊字符,那么在读取数据的时候,只要读到这个特殊字符,就认为已经可以截取一个完整的数据包了,这种情况在一定的业务情况下实用。

方案三:LengthFieldBasedFrameDecoderLengthFieldPrepender
LengthFieldBasedFrameDecoderLengthFieldPrepender需要配合起来使用,这两者一个是解码,一个是编码的关系。它们处理粘拆包的主要思想是在生成的数据包中添加一个长度字段,用于记录当前数据包的长度。LengthFieldBasedFrameDecoder会按照参数指定的包长度偏移量数据对接收到的数据进行解码,从而得到目标消息体数据;而LengthFieldPrepender则会在响应的数据前面添加指定的字节数据,这个字节数据中保存了当前消息体的整体字节数据长度。

关于 LengthFieldBasedFrameDecoder,这里需要对其构造函数参数进行介绍:

public LengthFieldBasedFrameDecoder(int maxFrameLength,  //指定了每个包所能传递的最大数据包大小;int lengthFieldOffset,  //指定了长度字段在字节码中的偏移量;int lengthFieldLength,  //指定了长度字段所占用的字节长度;int lengthAdjustment,  //对一些不仅包含有消息头和消息体的数据进行消息头的长度的调整,这样就可以只得到消息体的数据,这里的 lengthAdjustment 指定的就是消息头的长度;int initialBytesToStrip)   //对于长度字段在消息头中间的情况,可以通过 initialBytesToStrip 忽略掉消息头以及长度字段占用的字节。

我们以json序列化为例对LengthFieldBasedFrameDecoderLengthFieldPrepender的使用方式进行说明。如下是EchoServer的源码:

public class EchoServer {public void bind(int port) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {// 这里将LengthFieldBasedFrameDecoder添加到pipeline的首位,因为其需要对接收到的数据// 进行长度字段解码,这里也会对数据进行粘包和拆包处理ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));// LengthFieldPrepender是一个编码器,主要是在响应字节数据前面添加字节长度字段ch.pipeline().addLast(new LengthFieldPrepender(2));// 对经过粘包和拆包处理之后的数据进行json反序列化,从而得到User对象ch.pipeline().addLast(new JsonDecoder());// 对响应数据进行编码,主要是将User对象序列化为jsonch.pipeline().addLast(new JsonEncoder());// 处理客户端的请求的数据,并且进行响应ch.pipeline().addLast(new EchoServerHandler());}});ChannelFuture future = bootstrap.bind(port).sync();future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws InterruptedException {new EchoServer().bind(8080);}
}

EchoServer主要是在pipeline中添加了两个编码器和两个解码一器,编码器主要是负责将响应的User对象序列化为json对象,然后在其字节数组前面添加一个长度字段的字节数组;解码一器主要是对接收到的数据进行长度字段的解码,然后将其反序列化为一个User对象。下面是JsonDecoder的源码:

public class JsonDecoder extends MessageToMessageDecoder<ByteBuf> {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {byte[] bytes = new byte[buf.readableBytes()];buf.readBytes(bytes);User user = JSON.parseObject(new String(bytes, CharsetUtil.UTF_8), User.class);out.add(user);}
}

JsonDecoder首先从接收到的数据流中读取字节数组,然后将其反序列化为一个User对象。下面我们看看JsonEncoder的源码:

public class JsonEncoder extends MessageToByteEncoder<User> {@Overrideprotected void encode(ChannelHandlerContext ctx, User user, ByteBuf buf)throws Exception {String json = JSON.toJSONString(user);ctx.writeAndFlush(Unpooled.wrappedBuffer(json.getBytes()));}
}

JsonEncoder将响应得到的User对象转换为一个json对象,然后写入响应中。对于EchoServerHandler,其主要作用就是接收客户端数据,并且进行响应,如下是其源码:

public class EchoServerHandler extends SimpleChannelInboundHandler<User> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, User user) throws Exception {System.out.println("receive from client: " + user);ctx.write(user);}
}

对于客户端,其主要逻辑与服务端的基本类似,这里主要展示其pipeline的添加方式,以及最后发送请求,并且对服务器响应进行处理的过程:

@Override
protected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 2, 0, 2));ch.pipeline().addLast(new LengthFieldPrepender(2));ch.pipeline().addLast(new JsonDecoder());ch.pipeline().addLast(new JsonEncoder());ch.pipeline().addLast(new EchoClientHandler());
}

这里客户端首先会在连接上服务器时,往服务器发送一个User对象数据,然后在接收到服务器响应之后,会打印服务器响应的数据。

public class EchoClientHandler extends SimpleChannelInboundHandler<User> {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.write(getUser());}private User getUser() {User user = new User();user.setAge(27);user.setName("zhangxufeng");return user;}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, User user) throws Exception {System.out.println("receive message from server: " + user);}
}

方案四:自定义粘包与拆包器:
对于一些更加复杂的协议,可能有一些定制化的需求。通过继承LengthFieldBasedFrameDecoderLengthFieldPrepender来实现粘包和拆包的处理。

如果用户确实需要不通过继承的方式实现自己的粘包和拆包处理器,这里可以通过实现MessageToByteEncoderByteToMessageDecoder来实现。这里MessageToByteEncoder的作用是将响应数据编码为一个ByteBuf对象,而ByteToMessageDecoder则是将接收到的ByteBuf数据转换为某个对象数据。通过实现这两个抽象类,用户就可以达到实现自定义粘包和拆包处理的目的。如下是这两个类及其抽象方法的声明:

public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
}public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter {protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;
}

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

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

相关文章

吴恩达deeplearning.ai:机器学习的开发过程与优化方法

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai专栏 我想在接下来分析下开发机器学习系统的过程&#xff0c;这样当你自己动手时&#xff0c;能够做出更加正确的判断。 机器学习开发的迭代 Iterative loop of ML development 决定模型架构 第…

酷开科技以酷开系统为媒介,打造欢乐生活场景

家人相聚在一起的时光总是那么美好&#xff0c;在欢聚的日子里&#xff0c;我们也总是希望能够让时间变得慢一点&#xff0c;再慢一点&#xff0c;但是随着春节假期的结束&#xff0c;很多人已经开始了新一年的忙碌&#xff0c;大家纷纷回到工作、学习岗位&#xff0c;回归之前…

GBU1510-ASEMI逆变器整流桥GBU1510

编辑&#xff1a;ll GBU1510-ASEMI逆变器整流桥GBU1510 型号&#xff1a;GBU1510 品牌&#xff1a;ASEMI 封装&#xff1a;GBU-4 最大重复峰值反向电压&#xff1a;1000V 最大正向平均整流电流(Vdss)&#xff1a;15A 功率(Pd)&#xff1a;大功率 芯片个数&#xff1a;4…

Java后端核心——Servlet

目录 一.概述 二.基础实现 1.导入坐标 2.定义实现类 3.注解 4.访问Servlet 三.执行流程 四.生命周期 1.加载和实例化 2.初始化 3.请求处理 4.服务终止 五.方法 1.init 2.service 3.destroy 4.getServletInfo 5.getServletConfig 六.体系结构 七.urlPatter…

基于YOLOv8深度学习的智能道路裂缝检测与分析系统【python源码+Pyqt5界面+数据集+训练代码】深度学习实战、目标检测、目标分割

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Rollup Summer:一览 Rollup 生态全景图

作者&#xff1a;Stanley&#xff0c;Kernel Ventures 编译&#xff1a;JIN&#xff0c;Techub News 短短几天内&#xff0c;ZKFair 的总锁定价值&#xff08;TVL&#xff09;已达到 1.2 亿美元&#xff0c;目前稳定在 8000 万美元&#xff0c;使其成为增长最快的 Rollup 之一…

测试常用MySQL相关

1、通过命令行连接数据库 [rootlocalhost ~]# mysql -u root -p Enter password: 输入以上命令&#xff0c;回车后输入密码&#xff0c;回车&#xff0c;出现 mysql> 命令提示窗口则表示登录成功&#xff0c;可以在mysql>下输入任何sql语句。 2、退出mysql mysql>…

【Python】新手入门:什么是变量?如何在Python中声明变量?变量有哪些使用方式?

【Python】新手入门&#xff1a;什么是变量&#xff1f;如何在Python中声明变量&#xff1f;变量有哪些使用方式&#xff1f; &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【…

C# OpenCvSharp DNN FreeYOLO 密集行人检测

目录 效果 模型信息 项目 代码 下载 C# OpenCvSharp DNN FreeYOLO 密集行人检测 效果 模型信息 Inputs ------------------------- name&#xff1a;input tensor&#xff1a;Float[1, 3, 192, 320] --------------------------------------------------------------- …

#stm32外设总结电容触摸按键

BS8116A-3 IRQ 外部中断请求 NMOS输出内部上拉 SCL SDA IIC通信接口 VDD 供电电压2.2-5.5V Ct电容: 0~25 pF 电容越大灵敏度越低 1、 软件使用流程 初始化 将IIC的两个引脚初始化为复用开漏模式 按键引脚设置上拉输入 下降沿触发外部中断 void KEY_Init(void) {//uint8_t …

打造经典游戏:HTML5与CSS3实现俄罗斯方块

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

uniapp实现单选框卡片选择器,支持微信小程序、H5等多端

采用uniapp-vue3实现的一款单选框卡片选择器&#xff0c;纯CSS实现样式和交互&#xff0c;提供丝滑的动画选中效果&#xff0c;支持不同主题配置&#xff0c;适配多端 可到插件市场下载尝试&#xff1a; https://ext.dcloud.net.cn/plugin?id16901 使用示例 示例代码 <te…

【Spring Boot 源码学习】BootstrapContext的实际使用场景

《Spring Boot 源码学习系列》 BootstrapContext的实际使用场景 一、引言二、往期内容三、主要内容3.1 BootstrapContext3.2 BootstrapRegistry 初始化器实现3.3 BootstrapContext 的实际使用场景3.3.1 早期启动时3.3.2 环境配置准备完成时3.3.3 应用上下文准备完成后关闭 Boot…

使用Opencv库直接进行人脸检测

import cv2abs_path cv2.__file__ xml_path abs_path.rsplit("/",1)[0] "/data/haarcascade_frontalface_default.xml"# 加载人脸检测器 face_cascade cv2.CascadeClassifier(xml_path)# 加载图像 img cv2.imread(/media/datasets/face/liuyigei_duo.…

uniapp列表进入动画

app列表入场动画 - DCloud 插件市场 列表入场动画https://ext.dcloud.net.cn/plugin?id16957

WordPress 从入门到精通【设置 WordPress】

前言&#xff1a;为方便演示&#xff0c;前几张图使用 Playground 环境截取 如果你还不会部署WordPress&#xff0c;请看下面的链接并使用雨云可视化构建一个WordPress站点&#xff1a; 超简单EP面板搭建WordPress网站教程 - 风屿岛 10 (biliwind.com) 进入仪表盘 在搭建完…

禁止使用搜索引擎,你了解吗?

员工A&#xff1a;“我今天想搜索的时候&#xff0c;用不了浏览器了&#xff0c;你能用么&#xff1f;” 员工B&#xff1a;“不知道啊我试一下啊” “也不行” 员工C&#xff1a;“为什么啊&#xff1f;” 针对上述对话&#xff0c;我们不禁思考&#xff1a; 公司为什么禁…

机器学习-可解释性机器学习:随机森林与fastshap的可视化模型解析

一、引言 机器学习在当今社会扮演着日益重要的角色&#xff0c;但黑盒模型的不可解释性限制了其应用范围。因此&#xff0c;可解释性机器学习成为研究热点&#xff0c;有助于提高模型的可信度和可接受性。本文旨在探讨随机森林和fastshap作为可视化模型解析工具的应用&#xff…

Mysql的Cardinality值

什么是Cardinality值&#xff1f; Cardinality值是Mysql做索引优化时一个非常关键的值&#xff0c;优化器会根据这个值来判断是否使用这个索引&#xff0c;它表示索引中唯一值的数目估计值&#xff0c;该值应该尽可能接近1&#xff0c;如果非常小&#xff0c;则用户需要考虑是否…

机器学习中的线性代数

基础知识的的复习: 线性代数——深度学习花书第二章 - 知乎 矩阵分解 特征值分解。 PCA(Principal Component Analysis)分解,作用:降维、压缩。 SVD(Singular Value Decomposition)分解,也叫奇异值分解。 矩阵分解的主要应用是:降维、聚类分析、数据预处理、低维度特征学…