Netty对处理粘包和半包的支持

news2024/9/21 11:08:31

Netty基本介绍,参考 Netty与网络编程

1.1 什么是粘包拆包

例如:发送 ABC, DEF两个报文

  • 收到ABCDEF一个报文,发生了粘包

  • 收到AB,C,DEF三个报文,ABC发生了拆包

  • 收到AB,CD,EF三个报文,即发生了拆包又发生了粘包

1.2 看一个粘包半包样例

  • 客户端每次把消息“ABC,DEF,GHI,JKL,MNO\n" 发生一百次给服务端
  • 服务端将每次收到的消息输出,并记录收到的次数,然后将消息返回客户端

我们看下面服务端输出的结果:

  • 服务端一共接收到两次消息,说明消息被合并了,发生了粘包
  • 第一次输出的消息最后一行只有"ABC,",这说明发生了拆包,一个完整的消息被拆分了才会出现这种情况

输出结果:

Server Accept[ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,] and the counter is:1
Server Accept[DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
ABC,DEF,GHI,JKL,MNO
] and the counter is:2

代码:

EchoClient.java

public class EchoClient {

    private final int port;
    private final String host;

    public EchoClient(int port, String host) {
        this.port = port;
        this.host = host;
    }

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
        try {
            final Bootstrap b = new Bootstrap();;/*客户端启动必须*/
            b.group(group)/*将线程组传入*/
                    .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/
                    .remoteAddress(new InetSocketAddress(host,port))/*配置要连接服务器的ip地址和端口*/
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture f = b.connect().sync();
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient(9999,"127.0.0.1").start();
    }
}

EchoClientHandler.java

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    private AtomicInteger counter = new AtomicInteger(0);

    /*** 客户端读取到网络数据后的处理*/
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8)
                +"] and the counter is:"+counter.incrementAndGet());
    }

    /*** 客户端被通知channel活跃后,做事*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ByteBuf msg = null;
        String request = "ABC,DEF,GHI,JKL,MNO"
                + System.getProperty("line.separator");
        //发送100次
        for(int i=0;i<100;i++){
            msg = Unpooled.buffer(request.length());
            msg.writeBytes(request.getBytes());
            ctx.writeAndFlush(msg);
        }
    }

    /*** 发生异常后的处理*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

EchoServer.java

public class EchoServer  {

    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        EchoServer echoServer = new EchoServer(9999);
        System.out.println("服务器即将启动");
        echoServer.start();
        System.out.println("服务器关闭");
    }

    public void start() throws InterruptedException {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
        try {
            ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/
            b.group(group)/*将线程组传入*/
                .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                .localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/
                /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                所以下面这段代码的作用就是为这个子channel增加handle*/
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(serverHandler);/*添加到该子channel的pipeline的尾部*/
                    }
                });
            ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
            System.out.println("服务器启动完成,等待客户端的连接和数据.....");
            f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
        } finally {
            group.shutdownGracefully().sync();/*优雅关闭线程组*/
        }
    }
}

EchoServerHandler.java

@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {

    private AtomicInteger counter = new AtomicInteger(0);

    /*** 服务端读取到网络数据后的处理*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        String request = in.toString(CharsetUtil.UTF_8);
        System.out.println("Server Accept["+request
                +"] and the counter is:"+counter.incrementAndGet());
        String resp = "Hello,"+request+". Welcome to Netty World!"
                + System.getProperty("line.separator");
        ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
    }

    /*** 发生异常后的处理*/
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

1.3 解决方法

TCP粘包/半包发生的原因:TCP是流式协议,消息无边界,消息发送的节点为了加快转发速度,会等到缓冲区满再发送,那么他可能会对消息拆包或者合并包满足缓冲区再发送

解决问题的方法也很简单:找出消息边界,确定了消息的边界,我们就能找到完整的消息

方式确定消息边界优点缺点推荐
TCP连接改成短连接每个链接发送一个消息简单效率低下不推荐
固定长度按长度取消息简单空间浪费(消息太短需要填充)不推荐
分隔符分隔符之间就是一个消息空间不浪费分隔符需要转义推荐
消息头和消息体根据消息头确定一个消息精确且不用转义实现相对复杂推荐

下面我们依次看一下上面几种方法如何实现:

1.4 短连接(不推荐)

每一次发送都创建一个新的连接,就可以避免粘包和拆包问题,但是这种方式效率低下,TCP三次握手四次挥手非常消耗性能,不推荐。

1.5 固定长度方式实现

客户端(服务端同样处理)

实现ChannelInitializer类,添加下面两行代码new FixedLengthFrameDecoder(FixedLengthEchoServer.RESPONSE.length())表示固定长度消息

在这里插入图片描述

将其作为handler交给Bootstrap来处理消息

在这里插入图片描述

1.6 分割符

1.6.1 换行符作分隔符(客户端(服务端同样处理)):

实现ChannelInitializer类,添加下面两行代码new LineBasedFrameDecoder(1024)表示固定长度消息,

同样需要将ChannelInitializerImp交给Bootstrap来处理消息
在这里插入图片描述

1.6.2 自定义分隔符:

实现ChannelInitializer类,添加下面两行代码new DelimiterBasedFrameDecoder(1024, delimiter)表示固定长度消息,

同样需要将ChannelInitializerImp交给Bootstrap来处理消息
在这里插入图片描述

1.7 消息头和消息体

自己重新定义一种消息头和消息体,根据消息头来确定消息边界,就可以知道是否发生粘包和拆包

1.8 总结:

Netty对粘包和拆包的处理进行了封装,开发者使用很方便,比较推荐的实现方式是1.6 和1.7。

  • 如果消息中没有什么特殊字符,可以采用1.6的方式,特殊字符当成分割符,实现简单方便。
  • 如果消息中不确定有哪些特殊字符,可以采用1.7的方式,但是实现复杂一点,相当于自己定义一个应用层协议。

备注:各种解决方案的代码后续分享会出来

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

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

相关文章

SQL server学习01-SQL server环境配置

目录 一&#xff0c;手动下载及安装 microsoft .net framework 3.5 1&#xff0c;下载 2&#xff0c;安装 二&#xff0c;安装SQL server2014 1&#xff0c;下载 2&#xff0c;安装 3&#xff0c;启动SQL server服务 三&#xff0c;下载及安装Microsoft SQL Server…

2024华为杯研赛E题保姆级教程思路分析

E题题目&#xff1a;高速公路应急车道紧急启用模型 今年的E题设计到图像/视频处理&#xff0c;实际上&#xff0c;E题的难度相对来说较低&#xff0c;大家不用畏惧视频的处理&#xff0c;被这个吓到。实际上&#xff0c;这个不难&#xff0c;解决了视频的处理问题&#xff0c;…

茶思屋直播|TinyEngine+AI:聚焦主航道,在实践中探索低代码技术黑土地

低代码引擎使能开发者定制低代码平台。它是低代码平台的底座&#xff0c;提供可视化搭建页面等基础能力&#xff0c;既可以通过线上搭配组合&#xff0c;也可以通过cli创建个人工程进行二次开发&#xff0c;实时定制出自己的低代码平台。适用于多场景的低代码平台开发&#xff…

周末愉快!——周复盘

加班的晚上有一个美梦&#xff01; 周末愉快简单复盘结尾 精华&#xff1a; 在这个信息爆炸的时代&#xff0c;我们的大脑每天都被无数的数据和刺激充斥&#xff0c;以至于我们常常感到应接不暇。然而&#xff0c;正如古人所言&#xff1a;“不飞则已&#xff0c;一飞冲天”&am…

物联网关组态应用案例

产品简介 拓扑未来物联网关是高集成度的物联网采集及通信装置&#xff0c;支持通过RS485串口以太网口进行数据采集&#xff0c;支持数据缓存、协议解析、边缘计算&#xff0c;Ethernet/4G/WIFI数据传输和接入云端平台。支持采集PLC、传感器、仪器仪表和各种控制器&#xff0c;…

MySQL的索引——提高查找算法的数据结构 B+树

我们MYSQL服务器是在内存中的&#xff0c;所以所有的操作也是内存级的&#xff0c;索引也是如此 我们要提高算法的效率&#xff1a;首先要用一个好的数据存储结构储存数据&#xff0c;然后结构决定算法 所以——索引的本质就是一种提高算法效率组织的数据结构 缩印的主要价值体…

【华为杯】2024数学建模研赛题目

2024数学建模研赛题目已经发布 各个赛题题目如下&#xff1a; A题 B题 C题 D题 E题 F题 赛题完整版在文末&#xff0c;点击下方名片。

【操作系统】01.冯·诺伊曼体系结构

上面这张图就是我们经常能在各种教材中看到的冯诺伊曼体系结构。我们常见的计算机&#xff0c;如笔记本。我们不常见的计算机&#xff0c;如服务器&#xff0c;大部分都遵守冯诺依曼体系。 一、认识设备 输入设备&#xff1a; 键盘、鼠标、网卡、磁盘、摄像头…… 输出设备&a…

Java8 中一个极其强悍的新接口,很多人没用过

在开发过程中经常会使用​​if...else...​​​进行判断抛出异常、分支处理等操作。这些​​if...else...​​​充斥在代码中严重影响了代码代码的美观&#xff0c;这时我们可以利用Java 8的Function接口来消灭​​if...else...​​。 if (...){throw new RuntimeException(&qu…

深入Android UI开发:从自定义View到高级布局技巧的全面学习资料

在Android开发的世界中&#xff0c;UI设计和实现是吸引用户的关键。本文将为您介绍一套全面的Android UI开发学习资料&#xff0c;包括详细的学习大纲、PDF文档、源代码以及配套视频教程&#xff0c;旨在帮助您从自定义View到高级布局技巧&#xff0c;全面提升您的UI开发技能。…

深度学习-从零基础快速入门到项目实践,这本书上市了!!!

此书地址&#xff1a; 《【2024新书】深度学习 从零基础快速入门到项目实践 文青山 跟我一起学人工智能 机器学习算法原理代码实现教程 深度学习项目分析 深度学习 从零基础快速入门到项目实践》【摘要 书评 试读】- 京东图书 除深度学习外我还写了一本软件测试书。我大概是国…

Godot游戏如何提升触感体验

在游戏世界中&#xff0c;触感体验至关重要&#xff0c;既能极大提升玩家沉浸感&#xff0c;让其深度融入游戏&#xff0c;在操作角色或与环境互动时&#xff0c;通过触感反馈获得身临其境的真实感&#xff08;比如动作游戏中角色攻击或受击时的振动反馈&#xff0c;能使玩家更…

【OSS安全最佳实践】降低因账号密码泄露带来的未授权访问风险

如果因个人或者企业账号密码泄露引发了未经授权的访问&#xff0c;可能会出现非法用户对OSS资源进行违法操作&#xff0c;或者合法用户以未授权的方式对OSS资源进行各类操作&#xff0c;这将给数据安全带来极大的威胁。为此&#xff0c;OSS提供了在实施数据安全保护时需要考虑的…

6. Python 输出长方形,直角三角形,等腰三角形

使用Python输出长方形&#xff0c;直角三角形&#xff0c;等腰三角形 这里主要使用python语言里的循环知识&#xff0c;具体说是Python语言里的循环嵌套&#xff0c; 注意&#xff0c;在实际使用中&#xff0c;循环嵌套一般最多到达3层&#xff0c;嵌套太多会影响到程序执行。…

JavaWeb - 5 - 前端工程化

一.前后端分离开发 前后端混合开发 缺点&#xff1a;沟通成本高&#xff0c;分工不明确&#xff0c;不便管理&#xff0c;不便维护拓展 前后端分离开发 当前最为主流的开发模式&#xff1a;前后端分离 前后端分离开发中很重要的是API接口文档&#xff08;如&#xff1a;YApi&…

蓝队技能-应急响应篇Web内存马查杀JVM分析Class提取诊断反编译日志定性

知识点&#xff1a; 1、应急响应-Web内存马-定性&排查 2、应急响应-Web内存马-分析&日志 注&#xff1a;传统WEB类型的内存马只要网站重启后就清除了。 演示案例-蓝队技能-JAVA Web内存马-JVM分析&日志URL&内存查杀 0、环境搭建 参考地址&#xff1a;http…

C++函数重载完成日期类相关计算

本文内容如下&#xff1a; 1.创建类以及函数的声明2.日期加减天数1.月份天数2.函数实现 3.日期比较大小4.日期减日期1.日期的前置和后置加加2.日期减日期的实现 5.内置类型的cout和cin本文代码如下&#xff1a; 要完成日期类的相关计算要创建自定义的类型&#xff0c;然后用函数…

Java制作拼图小游戏——基础编程实战(详细代码注释与流程讲解)

目录 前言 涉及知识点 准备工具 Java开发环境 图片资源 最终效果 ——需求分析 登录界面 功能描述 需求分析 功能需求 游戏主界面 功能描述 需求分析 功能需求 游戏菜单 游戏胜利界面 框架搭建 总结 编码 编码顺序 搭建App实现程序的入口 完成User用户类和…

计算总体方差statistics.pvariance()

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 计算总体方差 statistics.pvariance() [太阳]选择题 根据给定的Python代码&#xff0c;运行结果为&#xff1f; import statistics data [1, 2, 3, 4, 5] print(f"【显示】data{…

【C++掌中宝】深入解析C++命名空间:有效管理代码的利器

文章目录 前言1. namespace 的价值2. namespace 的定义3. 命名空间的本质4. 嵌套的命名空间5. 命名空间的使用6. using 指令7. 补充结语 前言 假设这样一种情况&#xff0c;当一个班上有两个名叫 Zara 的学生时&#xff0c;为了明确区分它们&#xff0c;我们在使用名字之外&am…