发布时间:2026/8/27 17:25:21
文件互传中的数据帧设计 前言前面文件互传的各个业务的设计实现分析的差不多了这里回顾分析下文件传输过程中文件数据帧的设计消息帧的设计以及其他的一些open Harmony关于内存对齐的设计。看完这篇就会理解为什么面试官老是揪着int占用几字节问不放了哈哈哈。消息帧设计就比如文件发送完以后软总线会把发送文件列表发送给接收端核对是否全部发完发送文件列表给接收端源码int32_t FileListToBuffer(const char **destFile, uint32_t fileCnt, FileListBuffer *outbufferInfo) { if (destFile NULL || outbufferInfo NULL || fileCnt 0) { TRANS_LOGE(TRANS_FILE, invalid param); return SOFTBUS_INVALID_PARAM; } int32_t errCode SOFTBUS_OK; uint32_t totalLength 0; uint32_t offset 0; for (uint32_t i 0; i fileCnt; i) { size_t fileNameLength strlen(destFile[i]); if (fileNameLength 0 || fileNameLength MAX_FILE_PATH_NAME_LEN) { TRANS_LOGE(TRANS_FILE, bad file name at index%{public} PRIu32, i); return SOFTBUS_INVALID_PARAM; } else { totalLength fileNameLength; } } size_t bufferSize totalLength (sizeof(struct FileListItem) * fileCnt); uint8_t *buffer (uint8_t *)SoftBusCalloc(bufferSize); if (buffer NULL) { TRANS_LOGE(TRANS_FILE, calloc filelist failed); return SOFTBUS_MALLOC_ERR; } for (uint32_t index 0; index fileCnt; index) { uint32_t fileNameSize strlen(destFile[index]); struct FileListItem *fileItem (struct FileListItem *)(buffer offset); fileItem-index htonl(index); fileItem-fileNameLength htonl(fileNameSize); offset sizeof(struct FileListItem); // note: no \0 here if (memcpy_s(fileItem-fileName, bufferSize - offset, destFile[index], fileNameSize) ! EOK) { TRANS_LOGE(TRANS_FILE, copy file name failed!); errCode SOFTBUS_MEM_ERR; break; } offset fileNameSize; } if (errCode ! SOFTBUS_OK) { SoftBusFree(buffer); return errCode; } outbufferInfo-buffer buffer; outbufferInfo-bufferSize offset; return SOFTBUS_OK; }逐行解析int32_t FileListToBuffer(const char **destFile, uint32_t fileCnt, FileListBuffer *outbufferInfo) { // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 参数校验 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ if (destFile NULL || outbufferInfo NULL || fileCnt 0) { return SOFTBUS_INVALID_PARAM; } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 第1轮循环计算所有文件名的总长度 ⭐ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ uint32_t totalLength 0; for (uint32_t i 0; i fileCnt; i) { size_t fileNameLength strlen(destFile[i]); if (fileNameLength 0 || fileNameLength MAX_FILE_PATH_NAME_LEN) { return SOFTBUS_INVALID_PARAM; // 文件名不能为空或超长 } totalLength fileNameLength; // 累加文件名长度 } // 假设 destFile [photo1.jpg, doc.pdf] // totalLength 11 7 18 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 分配缓冲区 ⭐ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ size_t bufferSize totalLength (sizeof(struct FileListItem) * fileCnt); // 18 8*2 34 字节 // ↑文件名 ↑每个Item头部8字节(indexfileNameLength) uint8_t *buffer (uint8_t *)SoftBusCalloc(bufferSize); // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 第2轮循环填充二进制数据 ⭐⭐ // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ uint32_t offset 0; for (uint32_t index 0; index fileCnt; index) { uint32_t fileNameSize strlen(destFile[index]); // 把 bufferoffset 位置强转为 FileListItem 指针 struct FileListItem *fileItem (struct FileListItem *)(buffer offset); // 填充头部转网络字节序 fileItem-index htonl(index); fileItem-fileNameLength htonl(fileNameSize); // offset 跳过 Item 头部8字节这里为什么传入结构体的大小就能跳过头部后面有讲到 offset sizeof(struct FileListItem); // 拷贝文件名到 fileName[0] 的位置不带 \0 memcpy_s(fileItem-fileName, bufferSize - offset, destFile[index], fileNameSize); // ↑ 目标地址 ↑ 剩余空间 ↑ 源数据 ↑ 长度 // offset 跳过文件名 offset fileNameSize; } // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 输出结果 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ outbufferInfo-buffer buffer; outbufferInfo-bufferSize offset; return SOFTBUS_OK; }例子输入destFile [photo1.jpg, doc.pdf]fileCnt 2 第1轮计算总长度 strlen(photo1.jpg) 11 strlen(doc.pdf) 7 totalLength 18 分配bufferSize 18 8*2 34 字节 第2轮填充数据 index0 offset0 fileItem 指向 buffer0 fileItem-index htonl(0) 0x00000000 fileItem-fileNameLength htonl(11) 0x0000000B offset 0 8 8 memcpy(fileItem-fileName, photo1.jpg, 11) offset 8 11 19 index1 offset19 fileItem 指向 buffer19 fileItem-index htonl(1) 0x00000001 fileItem-fileNameLength htonl(7) 0x00000007 offset 19 8 27 memcpy(fileItem-fileName, doc.pdf, 7) offset 27 7 34 输出 buffer → 34字节的二进制数据 bufferSize 34最终二进制布局为什么需要两轮循环第1轮算总长度 → 才知道要分配多少内存 第2轮填充数据 → 往分配好的内存里写 不能合并成一轮 第1个文件时还不知道总长度 → 无法确定分配多少 如果先分配一个估算值 → 可能不够或浪费 两轮循环是最稳妥的方式柔性数组设计发送的帧里面如果帧需要包含的数据长度不确定怎么办这个项目提供了柔性数组的设计解决。方案1固定大小数组浪费空间 struct FileListItem { uint32_t index; uint32_t fileNameLength; char fileName[256]; // 固定256字节 }; → 文件名只有 a.jpg5字节但占了256字节 → 浪费251字节 → 文件名超过256字节就放不下 → 限制 方案2指针两次内存分配 struct FileListItem { uint32_t index; uint32_t fileNameLength; char *fileName; // 指针 }; → 需要单独 malloc 文件名 → 两次内存操作 → 序列化时麻烦要序列化结构体 单独序列化字符串 方案3柔性数组最佳✅ struct FileListItem { uint32_t index; // 4字节 uint32_t fileNameLength; // 4字节 char fileName[0]; // 0字节占位符 }; → 不占空间 → 实际数据紧跟在后面 → 长度由 fileNameLength 决定柔性数组是什么struct FileListItem { uint32_t index; // 4字节 uint32_t fileNameLength; // 4字节 char fileName[0]; // ← 这就是柔性数组 };在这里柔性数组只是一个标识符是一个大小为0的数组不占用结构体空间。普通数组 char name[32]; // 占32字节不管你用不用 → 浪费空间文件名可能只有5字节 → 限制长度超过32就放不下 柔性数组 char name[0]; // 占0字节只是一个标记 → 实际数据紧跟在结构体后面 → 长度由 fileNameLength 告诉你 → 不浪费空间不限制长度项目中的效果// 分配内存结构体(8) 文件名(11) 19字节 uint8_t *buffer SoftBusCalloc(sizeof(FileListItem) strlen(photo1.jpg)); // 内存长这样 // buffer 指向的地址 ┌──────────┬──────────┬─────────────────────┐ │ index0 │ len11 │ p h o t o 1 . j p g │ │ (4字节) │ (4字节) │ (11字节) │ └──────────┴──────────┴─────────────────────┘ 0 8 19 // 使用 FileListItem *item (FileListItem *)buffer; item-index 0; // 前4字节 item-fileNameLength 11; // 第5-8字节 memcpy(item-fileName, photo1.jpg, 11); // 从第9字节开始写 // ↑ fileName 就是 buffer8 的别名为什么使用memcpy而不是strcpy// note: no \0 here代码注释明确说了 memcpy(item-fileName, destFile[index], fileNameSize); // ↑ 没有 \0 结尾节省1字节 strcpy(item-fileName, destFile[index]); // ↑ 会自动加 \0 → 多1字节 → 网络传输浪费带宽1字节对齐这里就涉及到项目对内存对齐的处理了。当一个变量不满默认的4字节或8字节的时候编译器会自动进行补齐但是这样有两个坏处第一帧变长第二两端内容对不上了这是为了保障发送过去的数据和接收到的数据保持一致。发送端1字节对齐 [index(4)][len(4)][fileName...] 第8字节开始就是文件名 接收端默认4字节对齐 [index(4)][len(4)][填充(4)][fileName...] 第12字节才开始文件名 ← 位置对不上 → 两端结构体布局不一致 → 解析错误 1字节对齐后 两端都是 [index(4)][len(4)][fileName...] → 布局一致 → 正确解析 ✅对齐规则详解默认对齐是什么默认对齐值 编译器决定通常等于该平台上最大的基本类型对齐要求 常见平台 x86 / ARM (32位)默认4字节对齐 x86_64 / ARM64 默认8字节对齐 可以用 #pragma pack(N) 或 __attribute__((aligned(N))) 手动指定判断对齐值的公式成员的对齐值 min(成员自身大小, 结构体当前对齐值) 例1默认4字节对齐 struct Example { char a; // 大小1对齐值min(1,4)1 → 偏移0 // 下一个成员偏移011 int b; // 大小4对齐值min(4,4)4 → 偏移必须是4的倍数 // 当前偏移1不是4的倍数 // → 插入3字节填充到偏移4 }; 内存布局 偏移: 0 1 2 3 4 5 6 7 内容: [a] [填充][填充][填充] [ b b b b ] sizeof 8内存对齐在编码中的具体操作#pragma pack(push, 1) // 保存当前设置设为1字节对齐 struct FileListItem { uint32_t index; // 偏移0大小4 uint32_t fileNameLength; // 偏移4大小4紧接index char fileName[0]; // 偏移8大小0 }; // sizeof 8无填充 #pragma pack(pop) // 恢复之前的设置 对比默认对齐假设4字节 struct FileListItem { uint32_t index; // 偏移0 uint32_t fileNameLength; // 偏移4 char fileName[0]; // 偏移8 }; // sizeof 8这个例子没区别 但如果有个 char 成员在中间就有区别了 #pragma pack(push, 1) struct BadExample { char type; // 偏移0 uint32_t index; // 偏移1紧接type无填充 }; // sizeof 5 默认4字节对齐 struct BadExample { char type; // 偏移0 // 偏移1-3填充 uint32_t index; // 偏移4 }; // sizeof 8

相关新闻

2026/8/27 17:25:14

AI编程助手实战:从Codex原理到DeepSeek API集成配置全指南

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 1. 先搞清楚 Codex 到底是什么,以及它现在能解决什么问题 如果你在找 Codex 的安装包、使用教程,或者想知道它…

2026/8/27 17:24:11

基于AD590和uA741的温度测量电路设计与Multisim仿真

🚀 30款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度 在模拟电子技术课程设计中,温度测量与信号转换是一个经典且实用的课题。很多同学在使用运算放大器进行温度传感器信号调理…

2026/8/22 21:14:38

计算机毕业设计之社团纳新管理系统设计与实现

本文论述了社团纳新管理系统的设计和实现,该网站从实际运用的角度出发,运用了计算机网站设计、数据库等相关知识,基于系统管理和Mysql数据库设计来实现的,网站主要包括用户、社团 、社团信息、社团类型、社团申请、系统管理等功能…

2026/8/27 17:23:54

商业园林机器人智能运维系统拆解:边缘计算+车群调度+云端平台

商业园林机器人这轮融资信号,核心不在“割草”这个动作,而在绿地维护从单机作业走向系统化智能运维。李泽湘关注过的商业园林机器人赛道里,新完成数千万融资的项目,选择把主战场放在海外绿地智能运维上,而不是继续在单…

2026/8/27 17:23:54

YOLOv5车牌检测与LPRNet识别实战:基于CCPD数据集实现完整系统

简介:在智能交通与停车管理场景中,车牌识别系统通常由目标检测与字符识别两部分组成。目标检测技术用于在复杂背景中定位车牌区域,而序列识别算法则负责将车牌图像转换为字符文本。YOLOv5作为单阶段检测器的代表,具备高速度与多尺…

2026/8/27 17:23:54

Zrythm 音频插件安装完整指南:从 LV2 效果器到 SFZ 音源

Zrythm 音频插件安装完整指南:从 LV2 效果器到 SFZ 音源 【免费下载链接】zrythm a highly automated and intuitive digital audio workstation - official mirror 项目地址: https://gitcode.com/gh_mirrors/zr/zrythm 你刚把 Zrythm 装进系统,…

2026/8/26 9:13:28

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/27 10:58:22

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/27 7:46:21

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/27 0:01:16

Go语言构建企业级AI服务网关:统一管理英伟达等AI接口调用

1. 项目概述:从零构建一个企业级的AI服务网关 最近在帮一个做内容审核的团队做技术架构升级,他们原来的业务里,每天有几十万张图片和短视频需要过审,最初是接了几个开源的AI模型自己部署,但效果和性能一直不太稳定。后…

2026/8/27 0:01:16

LeetCode Hot100(51-60)算法精解与面试技巧

1. 题目背景与核心价值"hot100(51-60)"这个标题看起来像是某个编程题库或算法练习集中的一组题目编号。在技术社区中,类似命名通常指向LeetCode、牛客网等平台的热门题目集合。作为刷过300题的算法老手,我理解这类题目的核心价值在于&#xff…

2026/8/27 0:01:16

CRC校验实战:从模2除法到HJ212协议排错

1. 为什么一个“校验码”能扛住工业现场90%的数据 corruption? 你有没有遇到过这样的场景:嵌入式设备通过RS-485上传温湿度数据,上位机偶尔收到一帧乱码——温度显示成-273℃,湿度跳到999%,但串口波形看起来完全正常&a…

2026/8/26 19:34:06

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/26 19:17:08

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/26 19:34:05

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…