粘包和半包问题及解决办法
粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。
半包问题是指数据在传输时,接收端只收到了部分数据,而非完整的数据,就叫做半包。
产生粘包和半包问题原因:
这些问题发生在 TCP/IP 协议中,因为 TCP 是面向连接的传输协议,它是以“流”的形式传输数据的,而“流”数据是没有明确的开始和结尾边界的,所以就会出现粘包问题。
大部分情况下我们都把粘包问题和半包问题看成同一个问题
问题代码演示
- 服务器端用来接收消息
- 客户端用来发送一段固定的消息。
通过输出服务器端接收到的信息来观察粘包问题。服务器端代码实现如下:
package com.nien.test.sticky;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @author ally-coding* @Date: 2023/10/16 23:47* @Project cetc_test* @Description: 粘包服务器端测试*/
public class ServSocket {private static final int BYTE_LENGTH = 20;public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(8888);//获取客户端连接Socket clientSocker = serverSocket.accept();//得到客户端发送的流对象try(InputStream inputStream = clientSocker.getInputStream()){while(true){//循环获取客户端发送的信息byte[] bytes = new byte[BYTE_LENGTH];// 读取客户端发送的信息int count = inputStream.read(bytes, 0, BYTE_LENGTH);if(count>0){System.out.println("接受到客户端的信息是:"+new String(bytes));}count=0;}}}
}
客户端代码:
package com.nien.test.sticky;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;/*** @author ally-coding* @Date: 2023/10/16 23:54* @Project cetc_test* @Description: 粘包客户端端测试*/
public class ClientSocket {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1",8888);final String message = "Hello world!";try(OutputStream outputStream = socket.getOutputStream()){for (int i = 0; i < 10; i++) {outputStream.write(message.getBytes());}}}
}
执行结果如下所示。

可以明显看出,服务器端发生了粘包问题。
解决办法
1.发送方和接收方固定发送数据的大小,当字符长度不够时用空字符弥补,有了固定大小之后就知道每条消息的具体边界了,这样就没有粘包的问题了; 2.在 TCP 协议的基础上封装一层自定义数据协议,在自定义数据协议中,包含数据头(存储数据的大小)和数据的具体内容,这样服务端得到数据之后,通过解析数据头就可以知道数据的具体长度了,也就没有粘包的问题了; 3.以特殊的字符结尾,比如以“\n”结尾,这样我们就知道数据的具体边界了,从而避免了粘包问题(推荐方案)
方法1 固定发送数据的大小
代码实现:
服务端代码:
package com.nien.test.sticky.solver1;import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @author ally-coding* @Date: 2023/10/17 0:40* @Project cetc_test* @Description: 粘包问题解决1-服务端*/
public class Server1 {private static final int BYTE_LENGTH = 1024;public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(9091);//获取客户端连接Socket clientSocker = serverSocket.accept();//得到客户端发送的流对象try(InputStream inputStream = clientSocker.getInputStream()){while(true){//循环获取客户端发送的信息byte[] bytes = new byte[BYTE_LENGTH];// 读取客户端发送的信息int count = inputStream.read(bytes, 0, BYTE_LENGTH);if(count>0){System.out.println("接受到客户端的信息是:"+new String(bytes).trim());}count=0;}}}
}
客户端代码:
package com.nien.test.sticky.solver1;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;/*** @author ally-coding* @Date: 2023/10/17 0:40* @Project cetc_test* @Description: 粘包问题解决1-客户端*/
public class Client1 {private static final int BYTE_LENGTH=1024;public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1",9091);final String messgae = "Hello world!";try(OutputStream outputStream = socket.getOutputStream()){byte[] bytes = new byte[BYTE_LENGTH];int idx = 0;for (byte b : messgae.getBytes()){bytes[idx] = b;idx++;}for (int i = 0; i < 10; i++) {outputStream.write(bytes, 0, BYTE_LENGTH);}}}
}
执行结果如下所示。

虽然这种方式可以解决粘包问题,但这种固定数据大小的传输方式,当数据量比较小时会使用空字符来填充,所以会额外的增加网络传输的负担。
方法2 在 TCP 协议的基础上封装一层自定义数据协议
步骤1 编写一个消息封装类 2编写客户端 3编写服务器
1.编写消息封装类代码:
package com.nien.test.sticky.solver2;import com.sun.org.apache.regexp.internal.RE;import java.io.IOException;
import java.io.InputStream;
import java.text.NumberFormat;/*** @author ally-coding* @Date: 2023/10/17 0:52* @Project cetc_test* @Description: 消息封装类*/
public class SocketPacket {static final int HEAD_SIZE=8;/*** 将协议封装为:协议头 + 协议体* @param content* @return*/public byte[] toBytes(String content){//协议体 byte数据byte[] bodyByte = content.getBytes();int bodyByteLength = bodyByte.length;// 最终封装对象byte[] result = new byte[HEAD_SIZE + bodyByteLength];// 借助 NumberFormat 将 int 转换为 byte[]NumberFormat numberFormat = NumberFormat.getNumberInstance();numberFormat.setMinimumIntegerDigits(HEAD_SIZE);numberFormat.setGroupingUsed(false);//协议头 byte数组byte[] headByte = numberFormat.format(bodyByteLength).getBytes();// 封装协议头System.arraycopy(headByte, 0, result, 0, HEAD_SIZE);// 封装协议体System.arraycopy(bodyByte,0, result, HEAD_SIZE, bodyByteLength);return result;}/*** 获取消息头的内容(也就是消息体的长度)* @param inputStream* @return* @throws IOException*/public int getHeader(InputStream inputStream) throws IOException {int result = 0;byte[] bytes = new byte[HEAD_SIZE];inputStream.read(bytes, 0, HEAD_SIZE);//得到消息体的字节长度result = Integer.valueOf(new String(bytes));return result;}
}
2.编写客户端代码
package com.nien.test.sticky.solver2;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;/*** @author ally-coding* @Date: 2023/10/17 1:30* @Project cetc_test* @Description: 客户端*/
public class Client2 {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1",9093);String[] message = {"Hello world","Hello java"};SocketPacket socketPacket = new SocketPacket();try(OutputStream outputStream = socket.getOutputStream()){for (int i = 0; i < 10; i++) {String msg = message[new Random().nextInt(message.length)];byte[] bytes = socketPacket.toBytes(msg);outputStream.write(bytes, 0, bytes.length);outputStream.flush();}}}
}
3.编写服务端
package com.nien.test.sticky.solver2;import java.io.IOException;import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** @author ally-coding* @Date: 2023/10/17 9:19* @Project cetc_test* @Description: 服务器端 使用线程池来处理每个客户端的业务请求*/
public class Server2 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(9093);// 获取客户端连接Socket clientSocket = serverSocket.accept();// 用线程池处理更多的客户端ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(100,150,100,TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));threadPoolExecutor.submit(()->{//客户端消息处理processMessage(clientSocket);});}private static void processMessage(Socket clientSocket){// Socket 封装对象SocketPacket socketPacket = new SocketPacket();// 获取客户端发送的消息对象try(InputStream inputStream = clientSocket.getInputStream()) {while (true){// 获取消息头(也就是消息体的长度)int bodyLength = socketPacket.getHeader(inputStream);// 消息体 byte 数组byte[] bodyBytes = new byte[bodyLength];// 每次实际读取字节数int readCount = 0;// 消息体赋值下标int bodyIndex = 0;// 循环接收消息头中定义的长度while (bodyIndex<=(bodyLength-1) &&(readCount = inputStream.read(bodyBytes, bodyIndex, bodyLength))!= -1){bodyIndex += readCount;}bodyIndex=0;// 成功接收到客户端的消息并打印System.out.println("接收到客户端的信息:" + new String(bodyBytes));}} catch (IOException e) {System.out.println(e.getMessage());}}
}
运行结果如下所示。

此方法虽然可以解决粘包问题,但消息的设计和代码的实现复杂度比较高,所以也不是理想的解决方案。
方法3 以特殊的字符结尾
代码实现:
服务器代码:
package com.nien.test.sticky.solver3;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*** @author ally-coding* @Date: 2023/10/18 0:40* @Project cetc_test* @Description:*/
public class Server3 {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(9092);// 获取客户端连接Socket clientSocket = serverSocket.accept();// 使用线程池处理更多的客户端ThreadPoolExecutor threadPool = new ThreadPoolExecutor(100,150,100,TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));threadPool.submit(()->{//消息处理processMessage(clientSocket);});}/*** 消息处理* @param clientSocket*/private static void processMessage(Socket clientSocket){// 获取客户端发送的消息流对象try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()))) {while (true){// 按行读取客户端发送的消息String msg = bufferedReader.readLine();if(msg!=null){System.out.println("接收到客户端的信息:" + msg);}}} catch (IOException e) {e.printStackTrace();}}
}
客户端代码:
package com.nien.test.sticky.solver3;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;/*** @author ally-coding* @Date: 2023/10/18 0:40* @Project cetc_test* @Description:*/
public class Client3 {public static void main(String[] args) throws IOException {// 启动 Socket 并尝试连接服务器Socket socket = new Socket("127.0.0.1", 9092);String message = "Hi,Java."; // 发送消息try (BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))) {// 给服务器端发送 10 次消息for (int i = 0; i < 10; i++) {// 注意:结尾的 \n 不能省略,它表示按行写入bufferedWriter.write(message + "\n");// 刷新缓冲区(此步骤不能省略)bufferedWriter.flush();}}}
}
执行结果如下图所示。

该方法最大优点是实现简单,但存在一定的局限性,比如当一条消息中间如果出现了结束符就会造成半包的问题,所以如果是复杂的字符串要对内容进行编码和解码处理,这样才能保证结束符的正确性。
相关文章:
粘包和半包问题及解决办法
粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。 半包问题是指数据在传输时,接收端只收到了部分数据,而非完整的数据,就叫做半包。 产生粘包和半包问题原因: …...
MySQL数据库查询实战操作
前置条件: 创建库:MySQL基本操作之创建数据库-CSDN博客 创建表:MySQL基本操作之创建数据表-CSDN博客 目录 常规查询常用函数union查询一、常规查询 1、查询所有姓名以 "张" 开头的学生: SELECT * FROM student WHERE name LIKE 张%; 这条语句使用 LIKE 运算…...
Elasticsearch 8.9 Master节点处理请求源码
大家看可以看ElasticSearch源码:Rest请求与Master节点处理流程(1) 这个图非常好,下午的讲解代码在各个类和方法之间流转,都体现这个图上 一、Master节点处理请求的逻辑1、节点(数据节点)要和主节点进行通讯࿰…...
Python---练习:while循环案例:猜数字
需求: 计算机从1 ~ 10之间随机生成一个数字,然后提示输入数字,如果我们输入的数字与随机数相等,则提示恭喜你,答对了。如果输入的数字比随机数大,则提示,猜大了。反之,则提示猜小了…...
CRM自动化意味着什么?企业如何从中受益?
客户关系管理(CRM)软件不再仅仅适用于大公司或销售周期长的行业,它越来越成为各种规模企业的重要工具。 在日常工作中,当你陷入流程的所有细节时,可能会产生不必要的工作。因此,如果你想要CRM提供的组织和…...
Python大数据之PySpark
PySpark入门 1、 Spark与PySpark 1、 Spark与PySpark...
网工记背命令(7)----静态路由(负载分担,主备备份)
1.静态路由负载分担 如图所示,属于不同网段的主机通过几台 Switch 相连,要求不配置动态路由协议,使不同网 段的任意两台主机之间能够互通,从拓扑图中可以看出,从 PCA 到 PCC 有两条路径可以过去,分别是 PC…...
error: unable to read askpass response from
报错信息 解决方法: 中文:文件-->设置-->版本控制-->Git-->勾选使用凭证帮助程序 英文:File -> Settings -> Version Control -> Git / Check "User credential Helper" 因为我的webstrom是中文版的&#…...
运行stable-diffusion-xl-refiner-1.0遇到version `GLIBCXX_3.4.29‘ not found的问题
一、问题背景 https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0 在运行示例程序时候遇到GLIBCXX_3.4.29‘ not found diffusers to > 0.18.0 import torch from diffusers import StableDiffusionXLImg2ImgPipeline from diffusers.utils import loa…...
Ubuntu - 安装 Elasticsearch(ES)
注意:以下步骤基于 Elasticsearch 7.x 版本。版本可能会随时间而变化,请查看 Elasticsearch 官方网站以获取最新的版本信息。 添加 Elasticsearch APT 仓库: 打开终端,并使用以下命令添加 Elasticsearch APT 仓库到系统…...
字节码进阶之java Instrumentation原理详解
文章目录 0. 前言1. 基础2. Java Instrumentation API使用示例 3. Java Agent4. 字节码操作库5. 实际应用6. 注意事项和最佳实践 0. 前言 Java Instrumentation是Java API的一部分,它允许开发人员在运行时修改类的字节码。使用此功能,可以实现许多高级操…...
Android 13.0 锁屏页面禁止下拉状态栏
1.概述 在13.0的系统产品定制化中,在默认的锁屏界面的时候原生系统是可以下拉状态栏的,但是定制的产品是需要禁用下拉状态栏的,所以需要在锁屏页面的时候禁用下拉状态栏,需要从两部分查看下拉状态栏流程然后禁用状态栏 接下来就来分析下看这个功能怎么实现 2.锁屏页面禁止…...
Windows10 Docker 安装教程
Docker Desktop是什么? Docker Desktop是适用于Windows的Docker桌面,是Docker设计用于在Windows 10上运行。它是一个本地 Windows 应用程序,为构建、交付和运行dockerized应用程序提供易于使用的开发环境。Docker Desktop for Windows 使用 …...
JWT认证
目录 前言 JWT组成部分 JWT工作原理 在Express中使用JWT 安装JWT相关的包 导入JWT相关的包 定义密钥 登录成功后调用jwt.sign()生成JWT字符串 将JWT字符串还原为JSON对象 捕获解析JWT失败后产生的错误 结尾 前言 Session 认证机制需要配合 Cookie 才能实现。由于 Co…...
【网络安全 --- xss-labs靶场通关(1-10关)】详细的xss-labs靶场通关思路及技巧讲解,让你对xss漏洞的理解更深刻
靶场安装: 靶场安装请参考以下博客,既详细有提供工具: 【网络安全 --- xss-labs靶场】xss-labs靶场安装详细教程,让你巩固对xss漏洞的理解及绕过技巧和方法(提供资源)-CSDN博客【网络安全 --- xss-labs通…...
Mathematics-Vocabulary·数学专业英语词汇
点击查看: Mathematics-Vocabulary数学专业英语词汇点击查看: Mathematics-Vocabulary-Offline数学专业英语词汇离线版本 Chinese-English translation英译汉The study of mathematics in English requires understanding the subject-specific vocabulary and terminology. Ma…...
画程序流程图
一。在线程序流程图。类图和时序图 Integrations | Mermaid 二。VSCODE画UML图和各种种 1.下载plantuml.jarReleases plantuml/plantuml GitHubGenerate diagrams from textual description. Contribute to plantuml/plantuml development by creating an account on GitHu…...
C++ 模板进阶
非类型模板参数 模板参数分为:类型模板参数与非类型模板参数 类型模板参数即:出现在模板参数列表中,跟在class或者typename之后的参数类型名称非类型模板参数即:用一个常量作为类(函数)模板的一个参数,在类(函数)模…...
jenkins 安装与使用、用户权限划分
jenkins 安装与使用 安装插件: 开启该插件功能 验证用户管理 创建web01~02 使用web01登录 用户权限划分 安装 Role-Based Strategy 插件后,系统管理 中多了如图下所示的一个功能,用户权限的划分就是靠他来做的 创建角色 重新访问 创建项目…...
Hadoop3教程(三十三):(生产调优篇)慢磁盘监控与小文件归档
文章目录 (161)慢磁盘监控(162)小文件归档小文件过多的问题如何对小文件进行归档 参考文献 (161)慢磁盘监控 慢磁盘,是指写入数据时特别慢的一类磁盘。这种磁盘并不少见,当机器运行…...
XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...
遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
tomcat入门
1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效,稳定,易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...
消息队列系统设计与实践全解析
文章目录 🚀 消息队列系统设计与实践全解析🔍 一、消息队列选型1.1 业务场景匹配矩阵1.2 吞吐量/延迟/可靠性权衡💡 权衡决策框架 1.3 运维复杂度评估🔧 运维成本降低策略 🏗️ 二、典型架构设计2.1 分布式事务最终一致…...
Redis上篇--知识点总结
Redis上篇–解析 本文大部分知识整理自网上,在正文结束后都会附上参考地址。如果想要深入或者详细学习可以通过文末链接跳转学习。 1. 基本介绍 Redis 是一个开源的、高性能的 内存键值数据库,Redis 的键值对中的 key 就是字符串对象,而 val…...
GB/T 43887-2024 核级柔性石墨板材检测
核级柔性石墨板材是指以可膨胀石墨为原料、未经改性和增强、用于核工业的核级柔性石墨板材。 GB/T 43887-2024核级柔性石墨板材检测检测指标: 测试项目 测试标准 外观 GB/T 43887 尺寸偏差 GB/T 43887 化学成分 GB/T 43887 密度偏差 GB/T 43887 拉伸强度…...
