粘包和半包问题及解决办法
粘包问题是指数据在传输时,在一条消息中读取到了另一条消息的部分数据,这种现象就叫做粘包。
半包问题是指数据在传输时,接收端只收到了部分数据,而非完整的数据,就叫做半包。
产生粘包和半包问题原因:
这些问题发生在 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)慢磁盘监控 慢磁盘,是指写入数据时特别慢的一类磁盘。这种磁盘并不少见,当机器运行…...

手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件
在选煤厂、化工厂、钢铁厂等过程生产型企业,其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进,需提前预防假检、错检、漏检,推动智慧生产运维系统数据的流动和现场赋能应用。同时,…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
Java毕业设计:WML信息查询与后端信息发布系统开发
JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发,实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构,服务器端使用Java Servlet处理请求,数据库采用MySQL存储信息࿰…...

20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...