IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言数据拼包 -> 正文阅读

[C++知识库]C语言数据拼包

单片机数据拼包

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

  • 数组
  • 指针
  • 结构体

下文将此三种方式分别列举此数据包的实现。
然后对比优缺点。

本文举例数据包协议

包头长度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++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 11:53:50  更:2021-07-26 11:54:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/25 14:24:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码