当前位置: 首页 > news >正文

tcp字节传输(java)-自定义包头和数据识别

1、背景

tcp传输的时候会自动拆包,因此服务端接收的数据段可能跟客户端发送过来的数据段长度不一致,比如客户端一次发送10000个字节。但是服务端接收了两次才接收完整(例如第一次接收6000字节,第二次接收4000字节)。但是服务端每次必须要接收完所有的字节才能进行处理,而且客户端每次发的数据长度都不一致。
于是经过协商,客户端每次发送数据段时,在数据段前加10个字节(后面统一称数据包头),前6个字节为数据包起始标识符,后4个字节为此次发送数据段的长度。

2、难点

因为tcp会拆包,所以数据段前的10个字节可能会出现在任何位置,也可能会出现在两次tcp传输过程中。另外如果包头前6个字节不是指定的标识,要向后顺延,直到找出包头。

3、思路

1)使用两个ByteBuffer对象,一个记录数据段前的10个字节,该对象仅创建一次。另一个ByteBuffer对象存储去除包头后的完整的数据段信息,该对象在每次接收新的包头时,都会根据包头的后4个字节重新创建(因为jvm的自动垃圾回收,所以这里不用担心内存溢出问题)。

2)接收完整的数据段后,如果还有多余数据则使用迭代方式处理。

4、java代码实现

1、这里只列出了核心代码,相关逻辑需要自己补全2、创建tcp服务端代码
try (ServerSocket ss = new ServerSocket(port)) {while (true) {Socket socket = ss.accept();new SocketHandler(socket, eqpmtId, port, save).start();}
} catch (Exception e) {log.error("TCP服务端创建异常,端口为{},异常为\n", this.port, e);
}3、tcp服务端详细处理代码
@Slf4j
class SocketHandler extends Thread {private Socket socket;private String eqpmtId;private Integer port;private boolean save;public SocketHandler(Socket socket, String eqpmtId, Integer port, boolean save) {this.socket = socket;this.eqpmtId = eqpmtId;this.port = port;this.save = save;}@Overridepublic void run() {log.info("与{},{}建立消息socket通信", eqpmtId, port);try (InputStream inputStream = socket.getInputStream();FileOutputStream os = new FileOutputStream(new File("D:\\tmp-data\\" + System.currentTimeMillis() + ".h264"));) {byte[] buffer = new byte[64 * 1024];int len = 0;ByteBuffer dataBuffer = null;ByteBuffer headBuffer = ByteBuffer.allocate(10);while (socket.isConnected() && !socket.isClosed()) {if ((len = inputStream.read(buffer)) != -1) {log.info("收到数据包len={}", len);try {dataBuffer = getDataBuffer(buffer, 0, len, headBuffer, dataBuffer);} catch (Exception e) {log.error("接收数据异常,重新开始接收...\n",e);headBuffer.clear();dataBuffer.clear();}} else {log.info("没有数据,休眠1秒,否则cpu会飙升");Thread.sleep(1000);}}} catch (Exception e) {log.error("socket传输异常,异常为\n", this.port, e);}log.info("关闭与},{}消息socket通信", eqpmtId, port);}private ByteBuffer getDataBuffer(byte[] buffer, int start, int end, ByteBuffer headBuffer, ByteBuffer dataBuffer) {int offset = start;int tmpLen = 0;//先找到包头if (headBuffer.position() < headBuffer.capacity()) {//当前数组长小于包头长度有,整个数组放入头缓存后返回int len = end - offset;if (len < headBuffer.capacity() - headBuffer.position()) {headBuffer.put(buffer, offset, len);return dataBuffer;}tmpLen = headBuffer.capacity() - headBuffer.position();headBuffer.put(buffer, offset, headBuffer.capacity() - headBuffer.position());offset = offset + tmpLen;//包头缓存填充满了,判断包头是否正确if (!isHead(headBuffer.array())) {//包头不正确,则不断向后移位直到找到包头log.info("包头有问题,向后移动一位继续校验");int headLastIndex = headBuffer.capacity() - 1;for (; offset < end; offset++) {for (int i = 0; i < headLastIndex; i++) headBuffer.put(i, headBuffer.get(i + 1));headBuffer.put(headLastIndex, buffer[offset]);if (isHead(headBuffer.array())) break;}//移位结束确认是找到了包头还是当前数组已经遍历完if (!isHead(headBuffer.array())) {headBuffer.position(headLastIndex);return dataBuffer;}}//包头正确后,解析获取数据包有多长,并创建对应的缓存对象int dataLen = dataLen(headBuffer.array());log.info("包头设定长度为{}", dataLen);dataBuffer = ByteBuffer.allocate(dataLen);}if (offset == end) return dataBuffer;//如果可以填充满数据缓存对象,则发送数据包,并清理缓存if (end - offset >= dataBuffer.capacity() - dataBuffer.position()) {tmpLen = dataBuffer.capacity() - dataBuffer.position();dataBuffer.put(buffer, offset, dataBuffer.capacity() - dataBuffer.position());offset = offset + tmpLen;/** 收到完整数据包,进行处理,注意这里的函数要替换成自己的处理逻辑 **/sendData(dataBuffer, null);dataBuffer.clear();headBuffer.clear();if (offset == end) return dataBuffer;//迭代处理剩下的数据return getDataBuffer(buffer, offset, end, headBuffer, dataBuffer);}//如果不能填充慢数据缓存对象,则整个数据放入后返回dataBuffer.put(buffer, offset, end - offset);return dataBuffer;}//判断是否为包头public boolean isHead(byte[] buffer) {if (buffer == null || buffer.length < 10) return false;int b1 = buffer[0];int b2 = buffer[1];int b3 = buffer[2];int b4 = buffer[3];int b5 = buffer[4];int b6 = buffer[5];String s = "" + b1 + b2 + b3 + b4 + b5 + b6;if ("001001".equals(s)) return true;return false;}//判断数据包的长度(ByteUtil用的hutool工具包里的类,也可以自己实现)public int dataLen(byte[] buffer) {return ByteUtil.bytesToInt(new byte[]{buffer[6], buffer[7], buffer[8], buffer[9]});}}

相关文章:

tcp字节传输(java)-自定义包头和数据识别

1、背景 tcp传输的时候会自动拆包&#xff0c;因此服务端接收的数据段可能跟客户端发送过来的数据段长度不一致&#xff0c;比如客户端一次发送10000个字节。但是服务端接收了两次才接收完整&#xff08;例如第一次接收6000字节&#xff0c;第二次接收4000字节&#xff09;。但…...

pyspark 系统找不到指定的路径; \Java\jdk1.8.0_172\bin\java

使用用具PyCharm 2023.2.1 1&#xff1a;pyspark 系统找不到指定的路径&#xff0c; Java not found and JAVA_HOME environment variable is not set. Install Java and set JAVA_HOME to point to the Java installation directory. 解决方法&#xff1a;配置正确环境变量…...

UE4 Physics Constraint Actor 实现钟摆效果

放入场景&#xff0c;然后将一个球体放入场景 选择小球 将小球改为Movable 选择模拟物理&#xff0c;并将小球移除平衡点 就实现了...

UE4/UE5 动画控制

工程下载​ ​​​​​​​​​​​​​https://mbd.pub/o/bread/ZJ2cm5pu 蓝图控制sequence播放/倒播动画&#xff1a; 设置开启鼠标指针&#xff0c;开启鼠标事件 在场景中进行过场动画制作 设置控制事件...

Springboot整合shiro

导入依赖 <!-- 引入springboot的web项目的依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> ​ <!-- shiro --><depende…...

阻塞/非阻塞、同步/异步(网络IO)

1.阻塞/非阻塞、同步/异步(网络IO) 【思考】典型的一次 IO 的两个阶段是什么&#xff1f; 数据就绪 和 数据读写 数据就绪 &#xff1a;根据系统 IO 操作的就绪状态 阻塞 非阻塞 数据读写 &#xff1a;根据应用程序和内核的交互方式 同步 异步 陈硕&#xff1a;在处理 IO …...

为什么大家会觉得考PMP没用?

一是在于PMP这套知识体系&#xff0c;是一套底层的项目管理逻辑框架&#xff0c;整体是比较抽象的。大家在学习工作之后&#xff0c;会有人告诉你很多职场的一些做事的规则&#xff0c;比如说对于沟通&#xff0c;有人就会告诉如何跟客户沟通跟同事相处等等&#xff0c;这其实就…...

AVR128单片机 USART通信控制发光二极管显示

一、系统方案 二、硬件设计 原理图如下&#xff1a; 三、单片机软件设计 1、首先是系统初始化 void port_init(void) { PORTA 0xFF; DDRA 0x00;//输入 PORTB 0xFF;//低电平 DDRB 0x00;//输入 PORTC 0xFF;//低电平 DDRC 0xFF;//输出 PORTE 0xFF; DDRE 0xfE;//输出 PO…...

为什么5G 要分离 CU 和DU?(4G分离RRU 和BBU)

在 Blog 一文中&#xff0c;5G--BBU RRU 如何演化到 CU DU&#xff1f;_5g rru_qq_38480311的博客-CSDN博客 解释了4G的RRU BBU 以及 5G CU DU AAU&#xff0c;主要是讲了它们分别是什么。但是没有讲清楚 为什么&#xff0c;此篇主要回答why。 4G 为什么分离基站为 RRU 和 BBU…...

Python中的数据输入

获取键盘输入 input语句 使用input()可以从键盘获取输入&#xff0c;使用一个变量来接收 print("你是谁&#xff1f;") name input() print(f"我知道了&#xff0c;你是{name}")# print("你是谁&#xff1f;") name input("你是谁&…...

cms系统稳定性压力测试出现TPS抖动和毛刺的性能bug【杭州多测师_王sir】

一、并发线程数100&#xff0c;分10个阶梯&#xff0c;60秒加载时间&#xff0c;运行1小时进行压测&#xff0c;到10分钟就出现如下 二、通过jstat -gcutil 16689 1000进行监控...

【UE】材质描边、外发光、轮廓线

原教学视频链接&#xff1a; ue4 材质描边、外发光、轮廓线_哔哩哔哩_bilibili 步骤 1. 首先新建一个材质&#xff0c;这里命名为“Mat_outLine” 在此基础上创建一个材质实例 2. 在视口中添加一个后期处理体积 设置后期处理体积为无限范围 点击添加一个数组 选择“资产引用”…...

百模大战,打响AI应用生态的新赛点

点击关注 文&#xff5c;郝鑫 黄小艺&#xff0c;编&#xff5c;刘雨琦 “宇宙中心”五道口&#xff0c;又泛起了昔日的光芒。 十字路口一角的华清嘉园里&#xff0c;各种互联网大佬们&#xff0c;王兴、程一笑、张一鸣等人的创业传说似乎还有余音&#xff0c;后脚搬进来的AI…...

【C++二叉树】进阶OJ题

【C二叉树】进阶OJ题 目录 【C二叉树】进阶OJ题1.二叉树的层序遍历II示例代码解题思路 2.二叉搜索树与双向链表示例代码解题思路 3.从前序与中序遍历序列构造二叉树示例代码解题思路 4.从中序与后序遍历序列构造二叉树示例代码解题思路 5.二叉树的前序遍历&#xff08;非递归迭…...

C++——vector:resize与reserve的区别,验证写入4GB大数据时相比原生操作的效率提升

resize和reserve的区别 reserve&#xff1a;预留空间&#xff0c;但不实例化元素对象。所以在没有添加新的对象之前&#xff0c;不能引用容器内的元素。而要通过调用push_back或者insert。 resize&#xff1a;改变容器元素的数量&#xff0c;且会实例化对象&#xff08;指定或…...

基础配置xml

# 配置端口 server.port8081# 文件上传配置 # 是否支持文件上传 spring.servlet.multipart.enabledtrue # 是否支持文件写入磁盘 spring.servlet.multipart.file-size-threshold0 # 上传文件的临时目录 spring.servlet.multipart.locationd:/opt/tmp # 最大支持上传文件大小 sp…...

win环境安装SuperMap iserver和配置许可

SuperMap iServer是我国北京超图公司研发的基于跨平台GIS内核的云GIS应用服务器产品&#xff0c;通过服务的方式&#xff0c;面向网络客户端提供与专业GIS桌面产品相同功能的GIS服务&#xff0c;能够管理、发布多源服务&#xff0c;包括REST服务、OGC服务等。 SuperMap iserve…...

【Apollo学习笔记】——规划模块TASK之PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER(一)

文章目录 TASK系列解析文章前言PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER功能介绍PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER相关配置PIECEWISE_JERK_NONLINEAR_SPEED_OPTIMIZER流程确定优化变量定义目标函数定义约束ProcessSetUpStatesAndBoundsOptimizeByQPCheckSpeedLimitF…...

pytest parametrize多参数接口请求及展示中文响应数据

编写登陆接口 app.py from flask import Flask, request, jsonify, Responseapp Flask(__name__)app.route(/login, methods[POST]) def login():username request.form.get(username)password request.form.get(password)# 在这里编写你的登录验证逻辑if username admin…...

电视连续剧 ffmpeg 批量去掉片头片尾

思路&#xff1a; 一、用python获取每集的总时长 二、把每集的时间&#xff0c;拼接成想要的ffmpeg的剪切命令命令。 1、用python获取每集的总时长 1&#xff0c;安装moviepy库&#xff0c;直接安装太慢&#xff0c;换成国内的源 pip install moviepy -i http://mirrors.aliyu…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

内存分配函数malloc kmalloc vmalloc

内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能

下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能&#xff0c;包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序

一、开发准备 ​​环境搭建​​&#xff1a; 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 ​​项目创建​​&#xff1a; File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

Java面试专项一-准备篇

一、企业简历筛选规则 一般企业的简历筛选流程&#xff1a;首先由HR先筛选一部分简历后&#xff0c;在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如&#xff1a;Boss直聘&#xff08;招聘方平台&#xff09; 直接按照条件进行筛选 例如&#xff1a…...