Java网络编程(二)经典案例[粘包拆包]

news2025/1/16 8:10:50

粘包拆包

概述

TCP是面向流的协议,TCP在网络上传输的数据就是一连串的数据,完全没有分界线
TCP协议的底层并不了解上层业务的具体定义,它会根据TCP缓冲区的实际情况进行包的划分。
在业务层面认为一个完整的包可能会被TCP拆分成多个小包进行发送,也可能把多个小的包封装成一个大的数据包进行发送,这就是所谓的TCP粘包拆包问题,如上图会存在多种情况

原因分析 

TCP数据流最终发到目的地,必须通过以太网协议封装成一个个的以太网帧发送出去,以太网数据帧大小最小64字节,最大1518字节,除去header部分,其数据payload为46到1500字节。所以如果以太网帧的payload大于MTU(默认1500字节)就需要进行拆包

解决方案

由于TCP协议底层无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,所以该问题只能通过上层的应用层协议设计来解决,常见方案如下:

  • 消息定长,发送方和接收方规定固定大小的消息长度,例如每个报文大小固定为200字节,如果不够,空位补空格
  • 增加特殊字符进行分割,例如FTP协议
  • 自定义协议,将消息分为消息头和消息体,消息头中包含消息总长度,这样服务端就可以知道每个数据包的具体长度了,知道了发送数据包的具体边界后,就可以解决粘包和拆包问题了

Netty解决方案

基于JDK原生的socket或者nio解决粘包拆包问题比较麻烦;作为一款非常强大的网络通信框架,Netty提供了多种编码器用于解决粘包拆包问题,只要掌握这些类库的使用即可解决粘包拆包问题,常见的类库包括:

  • LineBasedFrameDecoder:基于行的解码器,遇到 “\n”、"\r\n"会被作为行分隔符
  • FixedLengthFrameDecoder:基于固定长度的解码器
  • DelimiterBasedFrameDecoder:基于分隔符的帧解码器
  • ByteToMessageDecoder:处理自定义消息协议的解码器
    • ReplayingDecoder:继承ByteToMessageDecoder,网络缓慢并且消息格式复杂时可能慢于ByteToMessageDecoder

自定义消息协议+编码/解码器解决粘包拆包问题实战

Netty中使用自定义协议(本质就是解决服务器端每次读取数据长度的问题) + 编码解码器来解决

客户端

package com.bierce.io.netty.stickingAndunpacking;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.Charset;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class MyStickingUnpackingClientTest {
    private String host;
    private int port;
    public MyStickingUnpackingClientTest(String host, int port){
        this.host = host;
        this.port = port;
    }
    public void run(){
        NioEventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new MyMessageEncoder()); //自定义编码器
                            pipeline.addLast(new MyMessageDecoder()); //自定义解码器
                            pipeline.addLast(new MyStickingUnpackingClientHandler()); //自定义业务处理器
                        }
                    });
            ChannelFuture cf = bootstrap.connect(host, port).sync();
            Channel channel = cf.channel();
            System.out.println("北京时间-" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")) + ": " + "Client"+ channel.localAddress()+ " start Successful!!!");
            cf.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new MyStickingUnpackingClientTest("127.0.0.1", 9999).run();
    }
}
class MyStickingUnpackingClientHandler extends SimpleChannelInboundHandler<MessageProtocal> {
    private int count;
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 5; i++) {
            String msg = "Hello Bierce";
            byte[] data = msg.getBytes(Charset.forName("UTF-8"));
            int length = msg.getBytes(Charset.forName("UTF-8")).length;
            MessageProtocal msgProtocal = new MessageProtocal();
            msgProtocal.setData(data);
            msgProtocal.setLength(length);
            ctx.writeAndFlush(msgProtocal);
        }
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocal msg) throws Exception {
        byte[] data = msg.getData();
        int length = msg.getLength();
        System.out.println("客户端接收到服务端响应信息包括:");
        System.out.println("长度=" + length + ";内容=" + new String(data, Charset.forName("UTF-8"))  + ";消息包数量=" + (++this.count));
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息: " + cause.getMessage());
        ctx.close();
    }
}

服务端

package com.bierce.io.netty.stickingAndunpacking;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.nio.charset.Charset;
import java.util.UUID;
public class MyStickingUnpackingServerTest {
    private int port;
    public MyStickingUnpackingServerTest(int port){
        this.port = port;
    }
    public void run(){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
           ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                            .channel(NioServerSocketChannel.class)
                            .handler(new LoggingHandler(LogLevel.INFO))
                            .childHandler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                protected void initChannel(SocketChannel ch) throws Exception {
                                    ChannelPipeline pipeline = ch.pipeline();
                                    pipeline.addLast(new MyMessageDecoder()); //自定义解码器
                                    pipeline.addLast(new MyMessageEncoder()); //自定义编码器
                                    pipeline.addLast(new MyStickingUnpackingServerHandler());
                                }
                            });
            System.out.println("服务器启动成功!");
            serverBootstrap.bind(port).sync().channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new MyStickingUnpackingServerTest(9999).run();
    }
}
class MyStickingUnpackingServerHandler extends SimpleChannelInboundHandler<MessageProtocal> {
    private int count;
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MessageProtocal msg) throws Exception {
        int length = msg.getLength();
        byte[] data = msg.getData();
        System.out.println();
        System.out.println("服务器接收到的信息包括:");
        System.out.println("长度=" + length + ";内容=" + new String(data, Charset.forName("UTF-8"))  + ";消息包数量=" + (++this.count));
        System.out.println("------------服务端读取成功------------");

        //回复给客户端信息
        String responseContent = UUID.randomUUID().toString();
        int responseLen = responseContent.getBytes("UTF-8").length;
        MessageProtocal msgProtocal = new MessageProtocal();
        msgProtocal.setLength(responseLen);
        msgProtocal.setData(responseContent.getBytes("UTF-8"));
        ctx.writeAndFlush(msgProtocal);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常消息:" + cause.getMessage());
        ctx.close();
    }
}

自定义消息协议类

package com.bierce.io.netty.stickingAndunpacking;
/**
 * 自定义消息服协议
 */
public class MessageProtocal {
    private int length;
    private byte[] data;
    public int getLength() {
        return length;
    }
    public void setLength(int length) {
        this.length = length;
    }
    public byte[] getData() {
        return data;
    }
    public void setData(byte[] data) {
        this.data = data;
    }
}

自定义消息解码编码器类

package com.bierce.io.netty.stickingAndunpacking;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.ReplayingDecoder;
import java.util.List;
public class MyMessageDecoder extends ReplayingDecoder<Void> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        System.out.println("MyMessageDecoder method called");
        int length = in.readInt();
        byte[] data = new byte[length];
        in.readBytes(data);
        MessageProtocal messageProtocal = new MessageProtocal();
        messageProtocal.setLength(length);
        messageProtocal.setData(data);
        out.add(messageProtocal);
    }
}
class MyMessageEncoder extends MessageToByteEncoder<MessageProtocal> {
    @Override
    protected void encode(ChannelHandlerContext ctx, MessageProtocal msg, ByteBuf out) throws Exception {
        System.out.println("MyMessageEncoder method called");
        out.writeInt(msg.getLength());
        out.writeBytes(msg.getData());
    }
}

效果图

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

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

相关文章

Qt(C++)计算一段程序执行经过的时间

一、前言 在许多应用程序和系统中,需要对经过的时间进行计算和记录。例如 可能想要测量某个操作的执行时间,或者记录一个过程中经过的时间以进行性能分析。在这些场景下,准确地计时是非常重要的。 Qt提供了一个功能强大的计时器类QElapsedTimer,可以方便地记录经过的时间…

SSL核心概念 SSL类型级别

SSL&#xff1a;SSL&#xff08;Secure Sockets Layer&#xff09;即安全套接层&#xff0c;及其继任者传输层安全&#xff08;Transport Layer Security&#xff0c;TLS&#xff09;是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。 H…

b站文件太大无法上传怎么办?视频压缩这样做

据了解&#xff0c;B站在网页端和桌面客户端允许上传的视频上线为4G&#xff0c;当视频文件超出这个大小时&#xff0c;我们就要考虑才去方法压缩一下视频大小&#xff0c;不然就会出现无法上传的问题&#xff0c;下面就给大家分享几个视频压缩方法&#xff0c;供大家参考使用。…

sql server删除历史数据

1 函数 datediff函数: DATEDIFF ( datepart , startdate , enddate )datepart的取值可以是year,quarter,Month,dayofyear,Day,Week,Hour,minute,second,millisecond startdate 是从 enddate 减去。如果 startdate 比 enddate 晚&#xff0c;返回负值。 2 例子 删除2023年以…

消息队列的模拟实现(一)

消息队列的模拟实现&#xff08;一&#xff09; 认识消息队列生产者消费者模型两大特征市面上可见的消息队列MQ消息队列的特点&#xff1a; 模拟实现消息队列模型分类提供的核心API消息队列的推拉模式 交换机的类型持久化网络通信额外提供的方法使用一个TCP和信道之间的区别 消…

计算机网络-笔记-第一章-计算机网络概述

目录 一、第一章——计算机网络概述 1、因特网概述 &#xff08;1&#xff09;网络、互联网、因特网 &#xff08;2&#xff09;因特网发展的三个阶段 &#xff08;3&#xff09;因特网服务的提供者&#xff08;ISP&#xff09; &#xff08;4&#xff09;因特网标准化工…

源代码加密、防泄密软件

企业源代码防泄密是指企业采取措施保护其软件或应用程序源代码不被未授权的人员获取、泄露或盗用的一种安全措施。源代码是软件的核心组成部分&#xff0c;其中包含了程序员编写的具体指令和算法&#xff0c;可以被计算机理解和执行。泄漏企业的源代码可能导致严重的后果&#…

DEIF SCM-1测量模块

参数测量&#xff1a; SCM-1测量模块通常用于测量电力系统的各种参数&#xff0c;例如电压、电流、频率、功率因数等。 监测功能&#xff1a; 它能够实时监测电力系统的性能&#xff0c;以确保其在正常运行范围内。 通信接口&#xff1a; DEIF的测量模块通常具有通信接口&…

批量随机改名并自定义长度,让文件夹命名更随心

大家好&#xff01;你是否曾经为批量改名文件夹而苦恼过&#xff1f;现在&#xff0c;我们为你带来了解决方案&#xff01;我们的工具可以帮助你轻松批量给文件夹进行随机改名&#xff0c;并且还可以自定义文件夹名的长度&#xff0c;让你的文件夹命名更加随心和个性化。 首先第…

内存管理:TLSF算法原理分析

1、动态内存分配DSA&#xff1a; 动态内存分配&#xff08;DSA&#xff09;在计算机中十分重要&#xff0c;其主要用于在程序运行时&#xff0c;根据需要分配和释放内存。 (1)、DSA的几个要点分别为&#xff1a; 内存管理方式&#xff1a;动态内存分配与静态内存分配 相对应&…

JMeter性能测试(上)

一、基础简介 界面 打开方式 双击 jmeter.bat双击 ApacheJMeter.jsr命令行输入 java -jar ApacheJMeter.jar 目录 BIN 目录&#xff1a;存放可执行文件和配置文件 docs目录&#xff1a;api文档&#xff0c;用于开发扩展组件 printable-docs目录&#xff1a;用户帮助手册 li…

在线流程图软件哪个好?5款打工人必备的效率神器!

​流程图是可视化工具的一种&#xff0c;被广泛用于呈现和理解复杂的流程和工作流程。本篇文章我们将向你介绍5款优秀的在线流程图软件&#xff0c;助你提升工作效率&#xff0c;它们分别是&#xff1a;boardmix、Lucidchart、draw.io、Creately、Coggle。 在选择在线流程图软…

对numpy以及pandas中axis的理解

用线代的概念来理解轴&#xff0c;也就是dimension 在numpy中&#xff0c;最小的一维数组就可以看做是一个行列式&#xff0c;通常一个行列式写作如下形式 在numpy中就是这样的形式 anp.arange(4) #array([0, 1, 2, 3]) 对一个二维的矩阵&#xff0c;通常可以由两个行列式组…

用AI重构的钉钉,“钱”路在何方?

点击关注 文&#xff5c;郝 鑫&#xff0c;编&#xff5c;刘雨琦 钉钉2023年生态大会&#xff0c;离开了两年的无招&#xff0c;遇到了单飞9天的钉钉。 “做小钉钉、做好钉钉、做酷钉钉”&#xff0c;无招重申了钉钉的方向。 无招提到的三点&#xff0c;再加上“高质量增长”…

【Windows系统】资源管理器右键卡顿案例

问题 最近在使用办公电脑过程中&#xff0c;发现在Windows系统资源管理器中使用右键会出现卡顿现象。这是一台经常使用&#xff0c;工作日上班都会使用&#xff0c;以前没有这个问题。 出现问题的环境&#xff1a;windows版本&#xff1a;win10_x64&#xff08;22H2&#xff…

Camunda 7.x 系列【27】手工任务 业务规则任务

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 2.7.10 本系列Camunda 版本 7.19.0 源码地址: 文章目录 1. 手工任务2. 业务规则任务1. 手工任务 Manual Task手工任务是定义在流程引擎之外的任务,流程引擎不需要了解,也不需要提供系统或用户界面的工作。…

iOS脱壳技术(二):深入探讨dumpdecrypted工具的高级使用方法

前言 应用程序脱壳是指从iOS应用程序中提取其未加密的二进制可执行文件&#xff0c;通常是Mach-O格式。这可以帮助我们深入研究应用程序的底层代码、算法、逻辑以及数据结构。这在逆向工程、性能优化、安全性分析等方面都有着重要的应用。 在上一篇内容中我们已经介绍了Clutc…

Mybatis的动态SQL分页及特殊字符应用

目录 ​编辑 前言&#xff1a; 1.mybatis的分页 1.1分页的应用场景 1.2分页的使用方式 2.mybatis中特殊字符处理 2.1mybatis中特殊字符介绍 2.2mybatis中特殊字符的使用方式 前言&#xff1a; 上篇我已经写了Mybatis动态之灵活使用&#xff0c;接着上篇写mybatis的分页…

Redis下载与安装

文章目录 Redis简介下载&#xff0c;安装和配置&#xff08;cmd&#xff09;图形化工具 Redis 简介 下载&#xff0c;安装和配置&#xff08;cmd&#xff09; 开启redis服务 1.在解压出来的文件夹中打开cmd 2.输入 redis-server.exe redis.windows.conf即可开启服务 可以看到…

Android 之 AlarmManager (闹钟服务)

本节引言&#xff1a; 本节带来的Android中的AlarmManager(闹钟服务)&#xff0c;听名字我们知道可以通过它开发手机闹钟类的APP&#xff0c; 而在文档中的解释是&#xff1a;在特定的时刻为我们广播一个指定的Intent&#xff0c;简单说就是我们自己定一个时间&#xff0c; 然后…