大佬教程收集整理的这篇文章主要介绍了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;
}
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;
}
// 压栈编译器配置
#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;
}
// 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 还定义了一些带扩容的方法。可以在外部不传入缓冲的情况下完成数据包构建。
由于内部使用了堆,所以需要手动释放内存。
自带扩容的方式,属于另一种使用方式了,这里不做对比。
以下评判为个人经验判断,欢迎讨论。
执行速度:指针>结构体>数组>流
技术难度:指针>结构体>数组>流
写错可能性:指针>数组>结构体>流
易读性:结构体>流>数组>指针
// 为了减少折腾,我采用Stream方法写的。
// https://github.com/NewLifeX/microCLib.git
#include "Version.h"
#include "HardwareVersion.h"
#include "Cpu.h"
/// <sumMary>同服务器打招呼,携带软硬件版本和产品唯一ID</sumMary>
/// <param name="seq">消息序列号</param>
/// <param name="data">消息输出缓冲区</param>
/// <param name="len">缓冲区长度</param>
/// <returns>返回消息真实长度</returns>
int BuildPinMsg(byte seq, byte* data, int len)
{
// 固件版本。软件编译时间/发布工具生成的时间(特定格式)。
uint fwVer = GetVersion();
// 代码内写的时间转换而来。
// 一般用电路板画板时间作为基准。规避电路板命名的差异。
uint hdVer = GetHardwareVersion();
// CPU唯一ID
byte cpuid[12];
GetCpuid(cpuid, sizeof(cpuid));
byte payload[20];
Stream_t st;
StreamInit(&st, payload, sizeof(payload));
StreamWriteBytes(&st, (byte*)&fwVer, sizeof(fwVer));
StreamWriteBytes(&st, (byte*)&hdVer, sizeof(hdVer));
StreamWriteBytes(&st, (byte*)&cpuid, sizeof(cpuid));
return BuildByStream(Ping, seq, payload, st.Position, data, len);
}
以上是大佬教程为你收集整理的c语言数据拼包全部内容,希望文章能够帮你解决c语言数据拼包所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。