c語言數據拼包

單片機數據拼包

對於數據包拼包方式常規方式有git

  • 數組
  • 指針
  • 結構體

下文將此三種方式分別列舉此數據包的實現。
而後對比優缺點。github

本文舉例數據包協議編程

包頭 長度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 還定義了一些帶擴容的方法。能夠在外部不傳入緩衝的狀況下完成數據包構建。
因爲內部使用了堆,因此須要手動釋放內存。
自帶擴容的方式,屬於另外一種使用方式了,這裏不作對比。ui

對比總結

如下評判爲我的經驗判斷,歡迎討論。指針

執行速度:指針>結構體>數組>流
技術難度:指針>結構體>數組>流
寫錯可能性:指針>數組>結構體>流
易讀性:結構體>流>數組>指針code

相關文章
相關標籤/搜索