鸿蒙网络编程系列36-固定包头可变包体解决TCP粘包问题
1. TCP数据传输粘包简介
在本系列的第6篇文章《鸿蒙网络编程系列6-TCP数据粘包表现及原因分析》中,我们演示了TCP数据粘包的表现,如图所示:
随后解释了粘包背后的可能原因,并给出了解决TCP传输粘包问题的两种思路,第一种是指定数据包结束标志,在本系列第35篇《鸿蒙网络编程系列35-通过数据包结束标志解决TCP粘包问题》中给出了具体的实现,第二种是通过固定包头指定包的长度,本文将通过一个示例演示这种思路的实现。
2. 固定包头可变包体解决TCP粘包问题演示
本示例运行后的界面如图所示:
和上一篇文章类似,输入服务端的地址,这里可以使用本系列第25篇文章《鸿蒙网络编程系列25-TCP回声服务器的实现》中创建的TCP回声服务器,也可以使用其他类似的回声服务器;然后输入服务器端口,最后单击"测试"按钮循环发送0到99的数字字符串到服务端,服务端会回传收到的信息,本示例在收到服务器信息后在日志区域输出,如图所示:
从中可以看出,这次也彻底解决了数据粘包问题,收到的信息和发送时保持一致。
3. 固定包头可变包体解决TCP粘包问题示例编写
下面详细介绍创建该示例的步骤。
步骤1:创建Empty Ability项目。
步骤2:在module.json5配置文件加上对权限的声明:
"requestPermissions": [{"name": "ohos.permission.INTERNET"}]
这里添加了访问互联网的权限。
步骤3:在Index.ets文件里添加如下的代码:
import { socket } from '@kit.NetworkKit';
import { Decimal, util, buffer } from '@kit.ArkTS';
import { BusinessError } from '@kit.BasicServicesKit';@Entry
@Component
struct Index {@State title: string = '固定包头可变包体演示示例';//服务端端口号@State port: number = 9990//服务端IP地址@State serverIp: string = ""//操作日志@State msgHistory: string = ''//最大缓存长度maxBufSize: number = 1024 * 8//接收数据缓冲区receivedDataBuf: buffer.Buffer = buffer.alloc(this.maxBufSize)//缓冲区已使用长度receivedDataLen: number = 0//日志显示区域的滚动容器scroller: Scroller = new Scroller()build() {Row() {Column() {Text(this.title).fontSize(14).fontWeight(FontWeight.Bold).width('100%').textAlign(TextAlign.Center).padding(10)Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {Text("服务端地址:").fontSize(14).width(90)TextInput({ text: this.serverIp }).onChange((value) => {this.serverIp = value}).height(40).width(80).fontSize(14).flexGrow(1)Text(":").fontSize(14)TextInput({ text: this.port.toString() }).onChange((value) => {this.port = parseInt(value)}).height(40).width(70).fontSize(14)Button("测试").onClick(() => {this.test()}).height(40).width(60).fontSize(14)}.width('100%').padding(10)Scroll(this.scroller) {Text(this.msgHistory).textAlign(TextAlign.Start).padding(10).width('100%').backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width('100%').justifyContent(FlexAlign.Start).height('100%')}.height('100%')}//测试async test() {//服务端地址let serverAddress: socket.NetAddress = { address: this.serverIp, port: this.port, family: 1 }//执行TCP通讯的对象let tcpSocket: socket.TCPSocket = socket.constructTCPSocketInstance()//收到消息时的处理tcpSocket.on("message", (value: socket.SocketMessageInfo) => {this.receiveMsgFromServer(value)})await tcpSocket.connect({ address: serverAddress }).then(() => {this.msgHistory += "连接成功\r\n";}).catch((e: BusinessError) => {this.msgHistory += `连接失败 ${e.message} \r\n`;})//循环发送0到99的数字字符串到服务端for (let i = 0; i < 100; i++) {let msg = i.toString()await this.sendMsg2Server(tcpSocket, msg)let sleepTime = Decimal.random().toNumber() + 0.5//休眠sleepTime时间,大概0.5毫秒到1.5毫秒await sleep(sleepTime)}}//发送数据到服务端async sendMsg2Server(tcpSocket: socket.TCPSocket, msg: string) {let textEncoder = new util.TextEncoder();let encodeValue = textEncoder.encodeInto(msg)let sendBuf = buffer.alloc(2 + encodeValue.byteLength)//写入固定包头中的长度信息sendBuf.writeUInt16LE(encodeValue.byteLength)//写入可变包体信息sendBuf.write(msg, 2)await tcpSocket.send({ data: sendBuf.buffer })}//读取服务端发送过来的数据receiveMsgFromServer(value: socket.SocketMessageInfo) {//把接收到的数据复制到缓冲区有效数据尾部let copyCount = buffer.from(value.message).copy(this.receivedDataBuf, this.receivedDataLen)this.receivedDataLen += copyCount//至少写入了3个字节才需要解析if (this.receivedDataLen < 3) {return;}//当前数据包长度let packLen = this.receivedDataBuf.readUInt16LE()let textDecoder = util.TextDecoder.create("utf-8");//当前数据包长度加上固定包体的2字节,如果小于等于缓冲区已使用长度,就可以解析while ((packLen + 2) <= this.receivedDataLen) {//把可变包体中的数据转换为字符串let msgArray = new Uint8Array(this.receivedDataBuf.subarray(2, packLen + 2).buffer);let msg = textDecoder.decodeToString(msgArray)//剩余的未解析数据let leaveBufData = this.receivedDataBuf.subarray(packLen + 2, this.receivedDataLen)//剩余的未解析数据移动到缓冲区头部for (let pos = 0; pos < leaveBufData.length; pos++) {this.receivedDataBuf.writeUInt8(leaveBufData.readUInt8(pos), pos)}//重新设置缓冲区已使用长度this.receivedDataLen = leaveBufData.length//输出接收的数据到日志this.msgHistory += "S:" + msg + "\r\n"//至少写入了3个字节才需要解析,否则跳出循环if (this.receivedDataLen < 3) {break;}//开始查找下一个固定包头中的可变包体长度packLen = this.receivedDataBuf.readUInt16LE()}this.scroller.scrollEdge(Edge.Bottom)}
}//休眠指定的毫秒数
function sleep(time: number): Promise<void> {return new Promise((resolve) => setTimeout(resolve, time));
}
步骤4:编译运行,可以使用模拟器或者真机。
步骤5:按照本文第2部分“数据包结束标志解决TCP粘包问题演示”操作即可。
4. 代码分析
本示例的关键点在于构造数据包的格式,具体数据包的格式是这样的,前两个字节为固定的包长度,使用小端的16位无符号整数表示,后面是包内容。以发送数据包为例,代码如下所示:
async sendMsg2Server(tcpSocket: socket.TCPSocket, msg: string) {let textEncoder = new util.TextEncoder();let encodeValue = textEncoder.encodeInto(msg)let sendBuf = buffer.alloc(2 + encodeValue.byteLength)//写入固定包头中的长度信息sendBuf.writeUInt16LE(encodeValue.byteLength)//写入可变包体信息sendBuf.write(msg, 2)await tcpSocket.send({ data: sendBuf.buffer })}
这里首先把要发送的内容编码为Uint8Array类型,然后为缓冲区分配长度,长度为内容编码后的长度加上2,随后把内容长度作为无符号数写入缓冲区,然后把发送的内容也写入缓冲区,最后使用TCP客户端发送缓冲区到服务端。
接收时,首先把所有收到的数据都复制到接收缓冲区中,然后从缓冲区头部取两个字节作为数据包内容长度,然后判断接收缓冲区中已接收的数据是不是大于等于数据包内容长度加2,如果是,说明接收到了完整的数据包,就可以从中提取内容了,提取完毕把剩下的缓冲区数据移动到缓冲区头部,继续下一次循环,从缓冲区中提取完整数据包的数据,知道已接收的缓冲区小于数据包长度加2为止。相关代码位于方法receiveMsgFromServer中,源码包含了详细的注释,这里就不再赘述了。
(本文作者原创,除非明确授权禁止转载)
本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/PacketHeadWithLen
本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples
相关文章:

鸿蒙网络编程系列36-固定包头可变包体解决TCP粘包问题
1. TCP数据传输粘包简介 在本系列的第6篇文章《鸿蒙网络编程系列6-TCP数据粘包表现及原因分析》中,我们演示了TCP数据粘包的表现,如图所示: 随后解释了粘包背后的可能原因,并给出了解决TCP传输粘包问题的两种思路,第一…...

【华为路由】OSPF多区域配置
网络拓扑 设备接口地址 设备 端口 IP地址 RTA Loopback 0 1.1.1.1/32 G0/0/0 10.1.1.1/24 RTB Loopback 0 2.2.2.2/32 G0/0/0 10.1.1.2/24 G0/0/1 10.1.2.1/24 RTC Loopback 0 3.3.3.3/32 G0/0/0 10.1.2.2/24 G0/0/1 10.1.3.1/24 RTD Loopback 0 4.4.4…...

【C++初阶】一文讲通C++内存管理
文章目录 1. C/C内存分布2. C语言中动态内存管理方式3. C内存管理方式3. 1 new/delete操作内置类型3. 2 new和delete操作自定义类型 4. new与delete的原理4. 1 operator new与operator delete函数4. 2 内置类型4. 3 自定义类型 5. 定位new表达式(placement-new)6. malloc/free和…...
Vue学习笔记(九、简易计算器)
在这个案例中,我们使用v-model分别双向绑定了n1、n2操作数,op操作选项和result计算结果,同时用绑定了等号按钮事件。 由于是双向绑定,当input和select通过外部输入内容时,vm内部的数值也会改变,所以calcula…...

Maven 不同环境灵活构建
需求: 使用 Maven根据不同的构建环境(如开发、测试、生产)来定义不同的配置,实现灵活的构建管理。 需要Demo项目的可以参考:我的demo项目 一、项目分层 一般的初创项目不会有特别多的配置文件,所以使用 spring.profile…...

第三十篇:TCP连接断开过程,从底层说明白,TCP系列五
上一篇《第二十九篇:图解TCP三次握手,看过不会忘,从底层说清楚,TCP系列四》说了TCP的三次握手,接下来我将讲解TCP四次挥手。 既然有连接就有断开,谈到这里,有的同学可能会想,不就是…...
代码随想录算法训练营第七天| 哈希表理论基础 454.四数相加II 383.赎金信 15.三数之和 18.四数之和
454. 四数相加 II 题目 给定四个包含整数的数组 A, B, C, D,计算有多少个元组 (i, j, k, l) 使得 A[i] B[j] C[k] D[l] 0。 解题思路 先计算数组 A 和 B 的所有组合和,并存入哈希表 map 中,键为组合和,值为该和出现的次数…...
搜维尔科技:Manus新品发布Metagloves Pro专业版,专为高精度需求的客户打造,尤其是人形机器人产业与人机工效研究使用
manus新品发布Metagloves Pro专业版,专为高精度需求的客户打造,尤其是人形机器人产业与人机工效研究使用 搜维尔科技:manus新品发布Metagloves Pro专业版,专为高精度需求的客户打造,尤其是人形机器人产业与人机工效研究…...

Spring Boot实现的动态化酒店住宿管理系统
1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及,互联网成为人们查找信息的重要场所,二十一世纪是信息的时代,所以信息的管理显得特别重要。因此,使用计算机来管理酒店客房管理系统的相关信息成为必然。开发…...

数字IC后端实现Innovus |给各种IP子模块添加port buffer和antenna diode万能脚本
我们之前分享过在hierarchical flow后端实现中为了确保顶层flatten时timing signoff和physical signoff看到的情况和模块级看到的情况一致,我们会在模块io port添加io port buffer(主要是timing,antenna一致性)。实际上在芯片级我…...
反向代理服务器---NGINX
1.NGINX NGINX(发音为“engine-x”)是一个开源的高性能HTTP服务器和反向代理服务器。它被广泛用于互联网应用程序的加速、负载均衡和高可用性的配置。NGINX具有低内存消耗、高并发能力和卓越的性能,能够处理大量并发连接和高流量的网络流量。…...
unity3d————场景管理类SceneManager
常用API SceneManager.LoadScene(string sceneName) 加载名为 sceneName 的场景。SceneManager.LoadScene(int sceneBuildIndex) 根据场景在Build设置中的索引加载场景。SceneManager.GetActiveScene() 获取当前活动的场景。SceneManager.GetSceneByName(string name) 根据名称…...

鹅厂面试官:Transformer 为何需要位置编码?
最近这一两周看到不少互联网公司都已经开始秋招发放Offer。 不同以往的是,当前职场环境已不再是那个双向奔赴时代了。求职者在变多,HC 在变少,岗位要求还更高了。 最近,我们又陆续整理了很多大厂的面试题,帮助一些球…...
MySQL数据库学习指南
一、数据库的库操作 1、创建数据库 2、删除数据库 3、查看数据库 4、选择数据库 5、修改数据库 6、数据库备份与恢复 7、数据库的权限管理 二、数据库的表操作 1、创建表 2、删除表 3、修改表 4、查看表的结构 5、查看表的数据 6、创建索引 7、删除索引 8、约束…...
算法刷题-小猫爬山
本题来源165. 小猫爬山 - AcWing题库 翰翰和达达饲养了 NN 只小猫,这天,小猫们要去爬山。 经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。 翰翰和达达只好花…...

Maven项目管理工具-初始+环境配置
1. Maven的概念 1.1. 什么是Maven Maven是跨平台的项目管理工具。主要服务于基于Java平台的项目构建,依赖管理和项目信息管理。 理想的项目构建:高度自动化,跨平台,可重用的组件,标准化的流程 maven能够自动下载依…...

【JavaEE初阶】网络编程TCP协议实现回显服务器以及如何处理多个客户端的响应
前言 🌟🌟本期讲解关于TCP/UDP协议的原理理解~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不多说…...

Android 中的串口开发
一:背景 本文着重讲安卓下的串口。 由于开源的Android在各种智能设备上的使用越来越多,如车载系统等。在我们的认识中,Android OS的物理接口一般只有usb host接口和耳机接口,但其实安卓支持各种各样的工业接口,如HDM…...
TensorRt OP
在TensorRT中,OP(Operations,操作)是指网络中的基本计算单元,类似于数学中的运算符。每个OP执行一个特定的计算任务,例如卷积、矩阵乘法、激活函数等。TensorRT通过识别和优化这些OP来提高深度学习模型的推…...

构建负责任的人工智能:数据伦理与隐私保护
构建负责任的人工智能:数据伦理与隐私保护 目录 🌟 数据伦理的重要性📊 公平性评估:实现无偏差的模型🔒 数据去标识化:保护用户隐私的必要手段🔍 透明性与问责:建立可信的数据处理…...

51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
三维GIS开发cesium智慧地铁教程(5)Cesium相机控制
一、环境搭建 <script src"../cesium1.99/Build/Cesium/Cesium.js"></script> <link rel"stylesheet" href"../cesium1.99/Build/Cesium/Widgets/widgets.css"> 关键配置点: 路径验证:确保相对路径.…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...
数据库——redis
一、Redis 介绍 1. 概述 Redis(Remote Dictionary Server)是一个开源的、高性能的内存键值数据库系统,具有以下核心特点: 内存存储架构:数据主要存储在内存中,提供微秒级的读写响应 多数据结构支持&…...