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

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

1. TCP数据传输粘包简介

在本系列的第6篇文章《鸿蒙网络编程系列6-TCP数据粘包表现及原因分析》中,我们演示了TCP数据粘包的表现,如图所示:

随后解释了粘包背后的可能原因,并给出了解决TCP传输粘包问题的两种思路,其中一种就是指定数据包结束标志,本节将通过一个示例演示这种思路的实现。

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

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

输入服务端的地址,这里可以使用本系列第25篇文章《鸿蒙网络编程系列25-TCP回声服务器的实现》中创建的TCP回声服务器,也可以使用其他类似的回声服务器;然后输入服务器端口,最后单击"测试"按钮循环发送0到99的数字字符串到服务端,服务端会回传收到的信息,本示例在收到服务器信息后在日志区域输出,如图所示:

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

3. 数据包结束标志解决TCP粘包问题示例编写

下面详细介绍创建该示例的步骤。
步骤1:创建Empty Ability项目。

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

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

这里添加了读取和设置Wifi配置的权限。

步骤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 = ''//数据包结束标志packetEndFlag: string = "\r\n"//最大缓存长度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 senderMsg = msg + this.packetEndFlagawait tcpSocket.send({ data: senderMsg })}//读取服务端发送过来的数据receiveMsgFromServer(value: socket.SocketMessageInfo) {//把接收到的数据复制到缓冲区有效数据尾部let copyCount = buffer.from(value.message).copy(this.receivedDataBuf, this.receivedDataLen)//缓冲区已使用长度加上本次接收的数据长度this.receivedDataLen += copyCount//缓冲区数据中数据包结束标志的位置let endFlagPos = this.receivedDataBuf.subarray(0, this.receivedDataLen).indexOf(this.packetEndFlag)let textDecoder = util.TextDecoder.create("utf-8");while (endFlagPos > -1) {//把数据包结束标志前面的数据转换为字符串let msgArray = new Uint8Array(this.receivedDataBuf.subarray(0, endFlagPos).buffer);let msg = textDecoder.decodeToString(msgArray)//剩余的未解析数据let leaveBufData = this.receivedDataBuf.subarray(endFlagPos + 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"//开始查找下一个数据包结束标志endFlagPos = this.receivedDataBuf.subarray(0, this.receivedDataLen).indexOf(this.packetEndFlag)}this.scroller.scrollEdge(Edge.Bottom)}
}//休眠指定的毫秒数
function sleep(time: number): Promise<void> {return new Promise((resolve) => setTimeout(resolve, time));
}

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

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

4. 代码分析

本示例的关键点在于设置数据包的结束标志,虽然TCP数据本身是没有边界的,但是通过人为添加额外的数据包分界标志,就可以把发送者的意图通过分界标志表示出来。为简单起见,本示例把结束标志设置为回车换行符,读者也可以使用其他的标志,需要注意的是,在发送信息的时候,信息本身不能包含结束标志符号,针对本示例而言,发送信息本身不能包含回车换行符序列,否则会出现意想不到的结果。

发送时添加结束标志的代码如下所示:

    //发送的原始数据后加上数据包结束标志let senderMsg = msg + this.packetEndFlagawait tcpSocket.send({ data: senderMsg })

接收时,首选把所有收到的数据都复制到接收缓冲区中,然后遍历缓冲区查找结束标志,找到后就把结束标志以前的部分作为完整数据包提取出来使用,剩余的部分复制到缓冲区首部进行下一次遍历,直到找不到结束标志为止,相关代码位于方法receiveMsgFromServer中,源码包含了详细的注释,这里就不再赘述了。

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

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

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


http://www.mrgr.cn/news/57594.html

相关文章:

  • 解决后端给前端的返回数据过大的问题(压缩)
  • Qt 文本文件读写与保存
  • 单细胞 | 转录因子足迹分析
  • Laravel|Lumen项目配置信息config原理
  • Redis遇到Hash冲突怎么办?
  • 某政府所属酒店流程规范化管理咨询项目
  • 养殖场大型全自动饲料颗粒加工机械设备
  • 力扣49.字母异位词分组
  • 【深度学习代码调试5】标准化数据集:TensorFlow Datasets (TFDS)自动化数据加载与预处理
  • ComfyUI零基础入门搭建教程
  • 手机空号过滤接口-在线手机空号检测-手机空号过滤API
  • 机器学习——元学习(Meta-learning)
  • 912.排序数组(堆排序)
  • 极狐GitLab 17.5 发布 20+ 与 DevSecOps 相关的功能【二】
  • MyBatis Builder
  • 微信小程序 - “本地资源图片无法通过WXSS 获取,可以使用网络图片,或者 base64,或者使用标签” 解决
  • ABB防爆伺服电机HX系列 危险工业环境中的安全卫士
  • 基于Python+SQL Server2008实现(GUI)快递管理系统
  • vue2结合echarts实现数据排名列表——前端柱状进度条排行榜
  • android 生成json 文件
  • 双十一护眼台灯测评推荐:实测书客、柏曼和明基护眼台灯怎么样
  • Lora算法原理及应用
  • qt QLineEdit详解
  • 大模型微调实战:基于 LLaMAFactory 通过 LoRA 微调修改模型自我认知
  • 分组密码填充模式
  • 自闭症学校:儿童成长的崭新希望