鸿蒙网络编程系列56-仓颉版通过数据包结束标志解决TCP粘包问题

1. TCP粘包问题解决思路

在本系列的上一篇文章演示了TCP数据粘包的原因以及可能的解决方法,本文将通过其中的添加数据包结束标志的方法来解决这个问题。我们知道,数据粘包的原因是因为发送的时候没有标明数据包的边界,那么,我们人为在每一个数据包发送的时候都加上这个边界就可以了。这个边界我们称为数据包结束标志,在发送端发送消息的时候,固定在消息尾部附加上这个标志,同样,在接收的时候,分析接收到的消息,从中提取出数据包结束标志,那么,这个标志前面的部分就是完整的消息。本示例将使用仓颉语言在API17的环境下编写,下面是详细的示例演示。

2. 数据包结束标志解决TCP粘包问题演示

本示例运行后的页面如图所示:

输入TCP回声服务器的IP地址和端口,然后单击“测试”按钮,发送0到98的数字字符串到服务端,服务端会回传收到的信息,本示例在收到服务器信息后在日志区域输出,如图所示:

从图中可以看出,本示例彻底解决了数据粘包问题,收到的信息和发送时保持一致。

3. TCP粘包示例编写

下面详细介绍创建该示例的步骤(确保DevEco Studio已安装仓颉插件)。

步骤1:创建[Cangjie]Empty Ability项目。

步骤2:在module.json5配置文件加上对权限的声明:

"requestPermissions": [{"name": "ohos.permission.INTERNET"}]

这里添加了访问互联网的权限。

步骤3:在build-profile.json5配置文件加上仓颉编译架构:

"cangjieOptions": {"path": "./src/main/cangjie/cjpm.toml","abiFilters": ["arm64-v8a", "x86_64"]}

步骤4:在index.cj文件里添加如下的代码:

package ohos_app_cangjie_entryimport ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.HashMap
import std.convert.*
import std.net.*
import std.socket.*
import encoding.base64.toBase64String
import std.sync.sleep
import std.time.Duration
import std.random.*@Entry
@Component
class EntryView {@Statevar title: String = '数据包结束标志演示示例';//连接、通讯历史记录@Statevar msgHistory: String = ''//服务端ip地址@Statevar serverIp: String = "*.*.*.*"//服务端端口@Statevar port: UInt16 = 9990//数据包结束标志var packetEndFlag: String = "\r\n"//最大缓存长度var maxBufSize: Int64 = 1024 * 8//接收数据缓冲区var receivedDataBuf: Array<UInt8> = Array<UInt8>(maxBufSize, item: 0)//缓冲区已使用长度var receivedDataLen: Int64 = 0let scroller: Scroller = Scroller()func build() {Row {Column {Text(title).fontSize(14).fontWeight(FontWeight.Bold).width(100.percent).textAlign(TextAlign.Center).padding(10)Flex(FlexParams(justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center)) {Text("服务端地址:").fontSize(14).width(90)TextInput(text: serverIp).onChange({value => serverIp = value}).width(80).fontSize(11).flexGrow(1)Text(":").fontSize(14)TextInput(text: port.toString()).onChange({value => if (value == "") {port = 0} else {port = UInt16.parse(value)}}).setType(InputType.Number).width(80).fontSize(11)Button("测试").onClick {evt => test()}.width(60).fontSize(14).enabled(serverIp.split(".", removeEmpty: true).size == 4 && port != 0)}.width(100.percent).padding(5)Scroll(scroller) {Text(msgHistory).textAlign(TextAlign.Start).padding(10).width(100.percent).backgroundColor(0xeeeeee)}.align(Alignment.Top).backgroundColor(0xeeeeee).height(300).flexGrow(1).scrollable(ScrollDirection.Vertical).scrollBar(BarState.On).scrollBarWidth(20)}.width(100.percent).height(100.percent)}.height(100.percent)}//从服务器读取消息并输出func readMsgFromServer(tcpClient: TcpSocket) {while (true) {//从socket读取数据var readCount = tcpClient.read(receivedDataBuf[receivedDataLen..])//如果读取的字节数为0,表明对端关闭,直接退出if (readCount == 0) {return}//缓冲区已使用长度加上本次接收的数据长度receivedDataLen += readCount//如果已接收的数据长度小于结束标志的数据长度,直接开始下一轮循环if (receivedDataLen < packetEndFlag.size) {continue}//查找结束标志第一次出现的位置var matchFlagPos = receivedDataBuf[0..receivedDataLen].indexOf(packetEndFlag.toArray())//如果找到了结束标志,就输出内容,一直循环,直到找不到结束标志,然后从外层循环再次读取Socketwhile (let Some(pos) <- matchFlagPos) {//把接收到的数据转换为字符串,不包括结束标志let content = String.fromUtf8(receivedDataBuf[0..pos])//输出接收到消息到日志msgHistory += "S:${content}\r\n"//结束标志后未处理的字节数let undealByteLen = receivedDataLen - pos - packetEndFlag.size//把未处理的字节复制到缓冲区头部receivedDataBuf.copyTo(receivedDataBuf, pos + packetEndFlag.size, 0, undealByteLen)//把未处理的字节数作为缓冲区已使用长度receivedDataLen = undealByteLen//查找下一个结束标志matchFlagPos = receivedDataBuf[0..receivedDataLen].indexOf(packetEndFlag.toArray())}}}//粘包测试func test() {let tcpClient = TcpSocket(serverIp, port)try {tcpClient.connect()msgHistory += "C:连接成功!\r\n"} catch (err: Exception) {msgHistory += "C:连接失败${err.message}!\r\n"return}//启动一个线程读取服务器返回信息spawn {readMsgFromServer(tcpClient)}//启动一个线程循环发送0到99的数字字符串到服务端spawn {try {let m: Random = Random()for (i in 0..99) {sendMsg2Server(tcpClient, i.toString())//随即休眠不超过10毫秒的时间sleep(Duration.millisecond * m.nextInt64(10))}} catch (exp: Exception) {msgHistory += "发送数据到服务器异常:${exp}\r\n"}}}//附加上结束标志后发送数据到服务端func sendMsg2Server(tcpClient: TcpSocket, msg: String) {let senderMsg = msg + packetEndFlagtcpClient.write(senderMsg.toArray())}
}

步骤5:编译运行,可以使用模拟器或者真机。

步骤6:按照本文第2部分“数据包结束标志解决TCP粘包问题演示”操作即可。

4. 代码分析

通过数据包结束标志解决粘包问题的关键点是数据发送和数据接收,相对来说,数据发送比较简单,如函数sendMsg2Server所示,只需要把结束标志附件到消息后面即可。
但是,接收的时候,处理就稍微复杂一些。接收时,会把收到的数据都放到缓冲区receivedDataBuf中,并且记录接收到的数据长度,然后从已接收的数据中查找结束标志,如果找到了结束标志,就以此为界提取标志前的数据为完整消息,然后继续把余下的数据移动到缓冲区头部再进行下一次的查找。如果没有找到结束标志,表示当前接收的数据不完整,还需要接续从套接字读取数据。详细的接收代码在函数readMsgFromServer中,具体执行流程可以参考代码注释。

(本文作者原创,除非明确授权禁止转载)

本文源码地址:
https://gitee.com/zl3624/harmonyos_network_samples/tree/master/code/tcp/PacketEndFlag4Cj

本系列源码地址:
https://gitee.com/zl3624/harmonyos_network_samples

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

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

相关文章

Redis网络通信模块深度解析:单线程Reactor到多线程IO的架构演进

一、核心架构&#xff1a;单线程Reactor模型 Redis网络模块采用经典Reactor模式&#xff0c;核心流程如下&#xff1a; void aeMain(aeEventLoop *eventLoop) {while (!eventLoop->stop) {// 前置钩子&#xff08;集群心跳/数据持久化&#xff09;if (eventLoop->befor…

PILCO: 基于模型的高效策略搜索方法原理解析

PILCO: 基于模型的高效策略搜索方法原理解析 PILCO (Probabilistic Inference for Learning Control) 是一种基于模型的强化学习算法&#xff0c;由Marc Deisenroth和Carl Rasmussen于2011年提出。该算法在数据效率方面表现出色&#xff0c;能够以极少的样本数据实现有效学习。…

大语言模型训练中的自监督学习和其他训练方式

大语言模型训练中的自监督学习和其他训练方式。 自监督学习&#xff08;Self-Supervised Learning&#xff09; 1. 什么是自监督学习&#xff1f; 自监督学习是一种不需要人工标注数据的训练方式&#xff0c;模型从数据本身学习特征和模式。 在语言模型中的具体实现&#x…

[mcp-servers] 工具与资源 | 模型上下文协议MCP | 锚点分类

第三章&#xff1a;工具与资源 欢迎回来&#xff01; 在之前的旅程中&#xff0c;我们认识了客户端&#xff08;第一章&#xff1a;客户端&#xff09;——AI的信使组件 以及MCP服务器&#xff08;第二章&#xff1a;MCP服务器&#xff09;——接收请求并具备执行能力的智能助…

2025年06月27日Github流行趋势

项目名称&#xff1a;edit 项目地址 url&#xff1a;https://github.com/microsoft/edit项目语言&#xff1a;Rust历史 star 数&#xff1a;10807今日 star 数&#xff1a;411项目维护者&#xff1a;lhecker, DHowett, b6k-dev, rhysd, MingcongBai项目简介&#xff1a;我们都编…

Camera Api 2 和 OPEN GL ES 使用(显示滤镜效果)

Camera Api 2 和 OPEN GL ES 使用&#xff08;显示滤镜效果&#xff09; 相机预览和open GL 使用实现滤镜效果 代码 https://github.com/loggerBill/camera 相机预览 1.相机动态权限 <uses-permission android:name"android.permission.CAMERA" /><uses-f…

CAS教务系统单点登录分析

1. 核心 URL 结构 GET /authserver/login?servicehttp%3A%2F%2F192.168.254.188%2Fjsxsd%2Fframework%2FxsMainV.htmlx认证服务器&#xff1a;authserver.XXXX.edu.cn&#xff08;典型 CAS 系统&#xff09;目标服务&#xff1a;http://192.168.254.188/jsxsd/framework/xsMa…

利用云雾自动化在智能无人水面航行器中实现自主碰撞检测和分类

大家觉得有读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 工业信息物理系统 &#xff08;ICPS&#xff09; 技术是推动海上自主化的基础&#xff0c;尤其是对于无人水面航行器 &#xff08;USV&#xff09;。然而&#xff0c;船上计算限制和通信延迟…

AI+物联网:从万物互联到万物智联

AI物联网&#xff1a;从万物互联到万物智联的范式革命 当农田传感器自主决策灌溉时机&#xff0c;当咖啡机根据睡眠数据调节浓度&#xff0c;当城市交通系统在拥堵发生前主动干预——这些场景不再是科幻想象&#xff0c;而是2025年AIoT&#xff08;人工智能物联网&#xff09;…

Python爬虫实战:研究Levenshtein库相关技术

1. 引言 1.1 研究背景与意义 随着电子商务的快速发展,网络上积累了海量的产品数据。这些数据来自不同的电商平台、卖家,存在着产品名称不统一、规格描述差异大等问题,给数据整合、价格比较、竞品分析等应用带来了极大挑战。传统的精确匹配方法无法处理产品名称中的拼写错误…

MySQL 总是差八个小时,如何破?

MySQL 总是差八个小时&#xff0c;如何破&#xff1f;_mysql__江南一点雨-Byzer 白泽 解决 SpringBoot 应用中 MySQL 时区配置引起的时间不一致问题 - 路有所思 - 博客园

iOS 为图片添加水印

(instancetype)waterMarkWithImage:(UIImage *)image andMarkImageName:(NSString *)markName{ UIImage *watermarkImage [UIImage imageNamed:markName]; if (!watermarkImage) { NSLog("水印图片加载失败: %", markName); return image; } // 获取原图尺寸和方向 …

蓝牙工作频段与跳频扩频技术(FHSS)详解:面试高频考点与真题解析

蓝牙技术凭借其低功耗、短距离通信的特性,已成为物联网、智能穿戴等领域的核心技术之一。其核心竞争力在于对 2.4GHz ISM 频段的高效利用与跳频扩频技术(FHSS)的创新应用。本文将系统梳理蓝牙工作频段与 FHSS 的高频考点,并结合历年真题与解析,快速掌握核心知识,轻松应对…

ArkTS与仓颉开发语言:鸿蒙编程的双子星

前言 鸿蒙是多语言生态&#xff0c;ArkTS、仓颉和 C/C充分互补。ArkTS 是动态类型编程语言&#xff0c;主打易学易用、生态丰富、极简开发、持续创新四大特征&#xff1b;仓颉是静态类型编程语言&#xff0c;主打高性能、强安全、跨平台、智能化等特性。为满足不同业务场景诉求…

怎么把本地仓库push 到gitlab 上

1. 首先 我们需要再gitlab 上建立一个group &#xff0c; &#xff08;group 可也设定是public 还是private&#xff09;&#xff0c;public 可以不用用户密码用 https 下载 2. 再gitlab 的group 下 建立一个仓库 &#xff08;pulbic/private) 如何删除 一个仓库&#xff08;…

论文笔记(八十六)V-HOP: Visuo-Haptic 6D Object Pose Tracking

V-HOP: Visuo-Haptic 6D Object Pose Tracking 文章概括摘要1. 引言2.背景A. 问题定义B. 触觉表示基于单元阵列的传感器基于视觉的传感器 3. 方法学A. 手爪表示B. 物体表示C. 网络设计D. 训练范式 IV. 实验A. 多形态数据集B. 位姿跟踪比较C. 模态消融D. 融合策略消融E. 遮挡对性…

[论文阅读] (40)CCS24 PowerPeeler:一种通用的PowerShell脚本动态去混淆方法

《娜璋带你读论文》系列主要是督促自己阅读优秀论文及听取学术讲座&#xff0c;并分享给大家&#xff0c;希望您喜欢。由于作者的英文水平和学术能力不高&#xff0c;需要不断提升&#xff0c;所以还请大家批评指正&#xff0c;非常欢迎大家给我留言评论&#xff0c;学术路上期…

DeepSeek工具对AI编程帮助

一、技术架构&#xff1a;混合专家模型&#xff08;MoE&#xff09;的颠覆性优势 DeepSeek的核心竞争力源于其混合专家模型架构&#xff08;Mixture of Experts&#xff09;&#xff1a; 参数规模&#xff1a;6710亿参数&#xff0c;每个token仅激活37亿参数&#xff0c;实现超…

链表题解——两数相加【LeetCode】

方法一&#xff1a;递归 写法一&#xff1a;创建新节点 算法思路解析 该实现采用 递归方式 逐位处理两个链表&#xff0c;并考虑进位 carry&#xff1a; ✨ 步骤拆解 递归终止条件&#xff1a;当 l1, l2 都为空且没有进位&#xff08;carry 0&#xff09;&#xff0c;说明…

AutoGen框架的ReAct推理模式的多跳测试

问题&#xff1a;特斯拉公司 CEO 的出生地是哪个国家&#xff1f; 答案&#xff1a;南非。 推理过程&#xff1a; 第一跳&#xff1a;确定特斯拉&#xff08;Tesla, Inc.&#xff09;的 CEO。特斯拉的 CEO 是埃隆马斯克&#xff08;Elon Musk&#xff09;。 第二跳&#xff1a;…