對於數據包拼包方式常規方式有: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; }
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 還定義了一些帶擴容的方法。能夠在外部不傳入緩衝的狀況下完成數據包構建。
因爲內部使用了堆,因此須要手動釋放內存。
自帶擴容的方式,屬於另外一種使用方式了,這裏不作對比。ui
如下評判爲我的經驗判斷,歡迎討論。指針
執行速度:指針>結構體>數組>流
技術難度:指針>結構體>數組>流
寫錯可能性:指針>數組>結構體>流
易讀性:結構體>流>數組>指針code