单片机数据拼包

对于数据包拼包方式常规方式有

  • 数组
  • 指针
  • 结构体

下文将此三种方式分别列举此数据包的实现。

然后对比优缺点。

本文举例数据包协议

包头 长度Length 消息类型 消息序列号Seq 负载数据 校验
2字节 1字节 1字节 1字节 N字节 2字节
名称 描述 其他
包头 固定 0X0A,0X0A 对于以太网数据包可以不设立此段。串口一般需要使用,对解包有利,这里不赘述。
长度 Length 数据包长度,(除去包头和自身)
消息类型 - 低7bit是消息类型,最高bit标记是否是回复消息
消息序列号Seq 消息编号,用于回复消息与请求消息的匹配
负载数据 消息类型对应的负载数据 负载数据长度 = Length - 4
校验 前面所有字节的校验值

代码中使用类型如下定义

// https://github.com/NewLifeX/microCLib.git  Core 目录 Type.h 内定义。
typedef char sbyte;
typedef unsigned char byte;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef long long int int64;
typedef unsigned long long int uint64;

基本定义

/// <summary>消息类型</summary>
typedef enum
{
/// <summary></summary>
Ping = 0x01,
/// <summary>注册</summary>
Reg = 0x02,
/// <summary>登录</summary>
Login = 0x03,
}MsgType_e; // 数据包头
static byte PktHead[] = {0x0A,0x0A}; // 函数原型
/// <summary>创建消息</summary>
/// <param name="seq">消息序列号Seq</param>
/// <param name="payload">负载数据内容指针</param>
/// <param name="payloadlen">负载数据长度</param>
/// <param name="data">消息输出缓冲区</param>
/// <param name="len">缓冲区长度</param>
/// <returns>返回消息真实长度</returns>
int Buil(byte seq, byte* payload, int payloadlen, byte* data, int len); // 下列代码,会根据事项方式在函数名加尾缀 ByXXX

数组

int BuilByteArray(byte seq, byte* payload, int payloadlen, byte* data, int len)
{
if (data == NULL)return -1;
// 判断缓冲区长度是否足够
if (len < payloadlen + 4 + 3)return -1; // 用于记录长度/写入位置
int idx = 0;
// 写数据包头
// memcpy(&data[idx], PktHead, sizeof(PktHead)); // idx=0 可以直接写data
memcpy(data, PktHead, sizeof(PktHead));
idx += sizeof(PktHead);
// 长度
data[idx++] = payloadlen + 4;
// 类型
data[idx++] = (byte)Reg;
// 序列号
data[idx++] = seq;
// 负载
memcpy(&data[idx], payload, payloadlen);
idx += payloadlen; // 计算crc
ushort crc = CaclcCRC16(data, idx); // 写入crc
memcpy(&data[idx], (byte*)&crc, sizeof(crc));
idx += sizeof(crc); return idx;
}
  • 常规操作,在各种c项目中最为常见。
  • 容易出错的点在 idx 维护。
  • 基本无难度。
  • 阅读难度很高,如果不写好备注。基本头秃。

指针

int BuilByPoint(MsgType_e type, byte seq, byte* payload, int payloadlen, byte* data, int len)
{
if (data == NULL)return -1;
// 判断缓冲区长度是否足够
if (len < payloadlen + 4 + 3)return -1; byte* p = data; // 写数据包头
// memcpy(&data[idx], PktHead, sizeof(PktHead)); // idx=0 可以直接写data
memcpy(p, PktHead, sizeof(PktHead));
p += sizeof(PktHead);
// 长度
*p++ = payloadlen + 4;
// 类型
*p++ = (byte)type;
// 序列号
*p++ = seq;
// 负载
memcpy(p, payload, payloadlen);
p += payloadlen; // 计算crc
ushort crc = CaclcCRC16(data, p - data); // 写入crc
memcpy(p, (byte*)&crc, sizeof(crc));
p += sizeof(crc); return p - data;
}
  • 基本就是数组方式的翻版。
  • 在执行效率上优于数组方式。
  • 指针对于 c 来说一直都是难点。
  • 容易写出错。
  • 阅读难度非常高,如果不写好备注。基本头秃。

结构体

// 压栈编译器配置
#pragma pack(push)
// 告诉编译按照1字节对齐排布内存。
#pragma pack(1) /// <summary>固定位置的数据部分</summary>
typedef struct
{
/// <summary>包头</summary>
ushort PktHead;
/// <summary>长度</summary>
byte Length;
/// <summary>消息类型,enum长度不确定,所以写个基础类型</summary>
byte Type;
/// <summary>消息序列号</summary>
byte Seq;
}MsgBase_t;
// 恢复编译器配置(弹栈)
#pragma pack(pop) int BuilByStruct(MsgType_e type, byte seq, byte* payload, int payloadlen, byte* data, int len)
{
if (data == NULL)return -1;
// 判断缓冲区长度是否足够
if (len < payloadlen + 4 + 3)return -1; // 直接写入能描述的部分。
MsgBase_t* mb = (MsgBase_t*)data;
memcpy((byte*)&(mb->PktHead), PktHead, sizeof(PktHead));
mb->Length = payloadlen + 4;
mb->Type = (byte)type;
mb->Seq = seq; int idx = sizeof(MsgBase_t);
// 负载
memcpy(&data[idx], payload, payloadlen);
idx += payloadlen; // 计算crc
ushort crc = CaclcCRC16(data, idx); // 写入crc
memcpy(&data[idx], (byte*)&crc, sizeof(crc));
idx += sizeof(crc); return idx;
}
  • 很少出现在各种开源软件中。
  • 需要掌握一个高级知识点,涉及编译器和 cpu 特征。

    cpu位宽、非对齐访问以及对应的编译器知识。
  • 对于固定长度的指令来说,非常方便。
  • cpu执行效率非常高,跟数组方式的速度一致。
  • 写好结构体,数值填充顺序就跟协议内容无关了。
  • 很好理解,阅读无压力。
  • 对于读非固定格式数据来说,0灵活度。只能抽取相同部分做部分处理。非常头秃。

    (本文主体是写数据,详细讨论)

数据流

// https://github.com/NewLifeX/microCLib.git
#include "Stream.h" int BuildByStream(MsgType_e type, byte seq, byte* payload, int payloadlen, byte* data, int len)
{
if (data == NULL)return -1;
// 判断缓冲区长度是否足够
if (len < payloadlen + 4 + 3)return -1; // 初始化流
Stream_t st;
StreamInit(&st, data, len);
// 包头
StreamWriteBytes(&st, PktHead, sizeof(PktHead));
// 长度
StreamWriteByte(&st, payloadlen + 4);
// 类型
StreamWriteByte(&st, (byte)type);
// 序列号
StreamWriteByte(&st, seq);
// 负载
StreamWriteBytes(&st, payload, payloadlen);
// 计算crc
ushort crc = CaclcCRC16(st.MemStart, st.Position);
// 写入crc
StreamWriteBytes(&st, (byte*)&crc, sizeof(crc)); return st.Position;
}
  • 上位机处理常规方式。算是面对对象编程的范畴了。
  • 阅读难度很小。
  • Stream 内部已做边界判断,基本不会出现bug。
  • 缺点,效率低。每个操作都是函数调用,此处产生大量消耗。

Stream 还定义了一些带扩容的方法。可以在外部不传入缓冲的情况下完成数据包构建。

由于内部使用了堆,所以需要手动释放内存。

自带扩容的方式,属于另一种使用方式了,这里不做对比。

对比总结

以下评判为个人经验判断,欢迎讨论。

执行速度:指针>结构体>数组>流

技术难度:指针>结构体>数组>流

写错可能性:指针>数组>结构体>流

易读性:结构体>流>数组>指针

c语言数据拼包的更多相关文章

  1. 使用R语言的RTCGA包获取TCGA数据--转载

    转载生信技能树 https://mp.weixin.qq.com/s/JB_329LCWqo5dY6MLawfEA TCGA数据源 - R包RTCGA的简单介绍 - 首先安装及加载包 - 指定任意基因 ...

  2. R语言数据预处理

    R语言数据预处理 一.日期时间.字符串的处理 日期 Date: 日期类,年与日 POSIXct: 日期时间类,精确到秒,用数字表示 POSIXlt: 日期时间类,精确到秒,用列表表示 Sys.date ...

  3. Android利用Fiddler进行网络数据抓包

    最新最准确内容建议直接访问原文:Android利用Fiddler进行网络数据抓包 主要介绍Android及IPhone手机上如何进行网络数据抓包,比如我们想抓某个应用(微博.微信.墨迹天气)的网络通信 ...

  4. 第一篇:R语言数据可视化概述(基于ggplot2)

    前言 ggplot2是R语言最为强大的作图软件包,强于其自成一派的数据可视化理念.当熟悉了ggplot2的基本套路后,数据可视化工作将变得非常轻松而有条理. 本文主要对ggplot2的可视化理念及开发 ...

  5. Android利用Fiddler进行网络数据抓包,手机抓包工具汇总

    Fiddler抓包工具 Fiddler抓包工具很好用的,它可以干嘛用呢,举个简单例子,当你浏览网页时,网页中有段视频非常好,但网站又不提供下载,用迅雷下载你又找不到下载地址,这个时候,Fiddler抓 ...

  6. R语言︱H2o深度学习的一些R语言实践——H2o包

    每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- R语言H2o包的几个应用案例 笔者寄语:受启发 ...

  7. Go语言基础之包

    Go语言基础之包 在工程化的Go语言开发项目中,Go语言的源码复用是建立在包(package)基础之上的.本文介绍了Go语言中如何定义包.如何导出包的内容及如何导入其他包. Go语言的包(packag ...

  8. R语言:recommenderlab包的总结与应用案例

    R语言:recommenderlab包的总结与应用案例   1. 推荐系统:recommenderlab包整体思路 recommenderlab包提供了一个可以用评分数据和0-1数据来发展和测试推荐算 ...

  9. Android利用Fiddler进行网络数据抓包【怎么跟踪微信请求】

    主要介绍Android及IPhone手机上如何利用Fiddler进行网络数据抓包,比如我们想抓某个应用(微博.微信.墨迹天气)的网络通信请求就可以利用这个方法. Mac 下请使用 Charles 代替 ...

随机推荐

  1. NSight Compute 用户手册(上)

    NSight Compute 用户手册(上) 非交互式配置文件活动 从NVIDIA Nsight Compute启动目标应用程序 启动NVIDIA Nsight Compute时,将出现欢迎页面.单击 ...

  2. 部署通用基础设施, 满足顶级 SLA 要求

    部署通用基础设施, 满足顶级 SLA 要求 Telefónica 使用基于英特尔 至强 可扩展处理器和英特尔 傲腾 数据中心级固态盘 的 VMware 虚拟存储区域网络 (vSAN)* 架构,完成对高 ...

  3. MySQL 主从复制&读写分离 简介

    1. 读写分离&读写分离 简介 主从同步延迟 分配机制 解决单点故障 总结 2. 主从复制&读写分离 搭建 搭建主从复制(双主) 搭建读写分离 1. 读写分离&读写分离 简介 ...

  4. 痞子衡嵌入式:嵌入式里串口(UART)自动波特率识别程序设计与实现

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是嵌入式里串口(UART)自动波特率识别程序设计与实现. 串口(UART)是嵌入式里最基础最常用也最简单的一种通讯(数据传输)方式,可以说 ...

  5. mybatis自定义打印执行时间并格式化sql插件

    打印SQL的执行时间,我们可以实现mybatis官方我们提供的接口org.apache.ibatis.plugin.Interceptor,我们可以拦截的类有多个Executor,StatementH ...

  6. Oracle 审计文件

    Oracle审计功能: Oracle11g推出了审计功能,但这个功能会针对很多操作都产生审计文件.aud,日积月累下来这些文件也很多,默认情况下,系统为了节省资源,减少I/0操作,其审计功能是关闭的 ...

  7. Redisson 分布式锁源码 01:可重入锁加锁

    前言 相信小伙伴都是使用分布式服务,那一定绕不开分布式服务中数据并发更新问题! 单系统很容易想到 Java 的各种锁,像 synchronize.ReentrantLock 等等等,那分布式系统如何处 ...

  8. win7旗舰版任务栏窗口不合并显示,鼠标移至窗口时可预览应用内容

    1.鼠标移至任务栏--右键--属性: 2.选择"当任务栏被占满时合并"或"从不合并",第一个选项更优: 3.右键桌面"计算机"的" ...

  9. Mysql在线DDL

    1.  Mysql各版本DDL方式 1.1 MysqlDDL演进 当mysql某个业务表上有未提交的活动事务的时候,你去执行在线DDL,这相当危险,直接会被卡住,show processlist里面会 ...

  10. mysql字符集utf8和utf8mb4区别

    1.起因 公司游戏项目上线第一天,出现单个区服异常宕机的问题,根据日志排查下来,连接数据的时候报错,后面排查是因为有玩家插入Emoji 等表情导致无法存储如数据库,数据库字符集编码为utf8,后续改成 ...