单片机数据拼包

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

  • 数组
  • 指针
  • 结构体

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

然后对比优缺点。

本文举例数据包协议

包头 长度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. CUDA 11功能展示

    CUDA 11功能展示 CUDA 11 Features Revealed 新的NVIDIA A100 GPU基于NVIDIA安培GPU架构,实现了加速计算的最大一代飞跃.A100 GPU具有革命性的 ...

  2. 嵌入式Linux的OTA更新,基础知识和实现

    嵌入式Linux的OTA更新,第1部分-基础知识和实现 OTA updates for Embedded Linux,  Fundamentals and implementation 更新的需要 一 ...

  3. Django(59)验证和授权

    验证和授权概述   Django有一个内置的授权系统.他用来处理用户.分组.权限以及基于cookie的会话系统.Django的授权系统包括验证和授权两个部分.验证是验证这个用户是否是他声称的人(比如用 ...

  4. 「题解」USACO15FEB Fencing the Herd G

    本文将同步发布于: 洛谷博客: csdn: 博客园: 简书: 题目 题目链接:洛谷 P3122.USACO 官网. 题意概述 给你平面上的一些点和直线,有两种操作: 新加入一个点 \((x,y)\): ...

  5. 一、安装Tomcat服务器

    [root@ web1 ~]# yum -y install java-1.8.0-openjdk   #安装jdk [root@web1 ~]# yum -y install java-1.8.0- ...

  6. 重磅!GitHub官方开源新命令行工具

    近日,GitHub 发布命令列工具 (Beta) 测试版,官方表示,GitHub CLI提供了一种更简单.更无缝的方法来使用Github.这个命令行工具叫做GitHub CLI,别名gh. 现在,你就 ...

  7. RobotFramework + Python 自动化入门 三 (Web自动化)

    在<RobotFramwork + Python 自动化入门 一>中,完成了一个Robot环境搭建及测试脚本的创建和执行. 在<RobotFramwork + Python 自动化入 ...

  8. Java进阶 | Proxy动态代理机制详解

    一.Jvm加载对象 在说Java动态代理之前,还是要说一下Jvm加载对象的过程,这个依旧是理解动态代理的基础性原理: Java类即源代码程序.java类型文件,经过编译器编译之后就被转换成字节代码.c ...

  9. js笔记5

    1.逻辑运算 ||  &&  ! ||:遇到第一个为true的值就中止并返回 &&:遇到第一个为false的值就中止并返回,如果没有false值,就返回最后一个不是fa ...

  10. excel VBA构造正则函数(单参数)

    Function zhengze(Rng As Range)    Set regx = CreateObject("vbscript.regexp")With regx  .Gl ...