字节序转换 + 模板 这里有一个结构体:// 类型别名对齐协议 typedef quint8 uchar; typedef qint8 schar; typedef quint16 ushort; typedef qint16 sshort; typedef quint32 uint; typedef qint32 sint; #pragma pack (push,1) // 报文头 typedef struct tagHead { unsigned char m_Iofo; unsigned short m_MesLength; unsigned short m_Send; unsigned short m_Rece; unsigned char m_TaskAttr; unsigned char m_CommType; unsigned int m_Serial; void init(quint8 Iden, quint16 len, quint16 sender, quint16 recv, quint8 task, quint8 label) { m_Iofo Iden; m_MesLength len; m_Send sender; m_Rece recv; m_TaskAttr task; m_CommType label; } }HEAD; typedef struct tagMainCtrlBitReplyMes { HEAD m_head; unsigned char m_MainCtrl; unsigned char m_Ser; unsigned char m_CheckSum; // 校验和 }MainCtrlBitReplyMes; #pragma pack (pop)在网络通信中因为我和对方的电脑都是小端序之前我们直接通过内存映射来将这段信息转为字节流。MultiDataMes msg; QByteArray ba((const char *)msg,sizeof(tagMultiDataMes));但现在要求网络传输使用大端序这里就涉及到字节序转换了。小端 --- 大端(网络字节序) --- 小端先实现小端 --- 大端(网络字节序)我们的代码涉及到需要转换的基础数据类型:int,unsigned intshort,unsigned short#include QByteArray #include QDataStream QByteArray HeadToNet(const HEAD head) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); // 强制网络大端序 ds.setByteOrder(QDataStream::BigEndian); // 按结构体顺序依次写入 ds head.m_Iofo; ds head.m_MesLength; ds head.m_Send; ds head.m_Rece; ds head.m_TaskAttr; ds head.m_CommType; ds head.m_Serial; return buf; }上面的代码把结构体Head对象以大端序转为字节流存储QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes msg) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); // 1. 先写入报文头全部字段 QByteArray headBuf HeadToNet(msg.m_head); ds.writeRawData(headBuf.constData(), headBuf.size()); // 2. 写入报文体 ds msg.m_MainCtrl; ds msg.m_Ser; ds msg.m_CheckSum; return buf; }我们的结构体是两层嵌套的上面的代码把结构体MainCtrlReplyToNet对象以大端序转为字节流存储QByteArray headBuf HeadToNet(msg.m_head); ds.writeRawData(headBuf.constData(), headBuf.size());这里需要使用writeRawData函数不可以直接写成ds headBuf;这里headBuf为: 9e000f02011001a12000000000但是 ds前面多出来0000000d原因:QDataStream 序列化 QByteArray 时默认先写入 4 字节长度头大端 int再写数组内容你看到前面多的0000000d就是长度字段0x0d 13HEAD 刚好 13 字节。大端序 --- 结构体依然使用QDataStream// 返回true解析成功false数据长度不足/解析失败 bool NetToHead(QDataStream ds, HEAD outHead) { // 逐个读取字段 ds outHead.m_Iofo; ds outHead.m_MesLength; ds outHead.m_Send; ds outHead.m_Rece; ds outHead.m_TaskAttr; ds outHead.m_CommType; ds outHead.m_Serial; // 校验流状态读取出错返回false return ds.status() QDataStream::Ok; }bool NetToMainCtrlReply(const QByteArray netData, MainCtrlBitReplyMes outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 NetToHead(ds,outMsg.m_head); // 读取应答数据域 ds outMsg.m_MainCtrl; ds outMsg.m_Ser; ds outMsg.m_CheckSum; return ds.status() QDataStream::Ok; }实际上关键就是QDataStream我们使用它进行 结构体 --- 大端序字节流 大端序字节流 --- 结构体进一步来讲我的软件涉及到可能30个类似的报文我不想手动一个个去写我希望可以自动化一些。例// 混合数值报文示例包含全部 short/ushort/int/uint/char/uchar typedef struct tagMultiDataMes { HEAD m_head; // 单字节 schar m_charVal; uchar m_ucharVal; // 两字节 short sshort m_shortVal; ushort m_ushortVal; // 四字节 int sint m_intVal; uint m_uintVal; // 末尾校验字节和你原有报文风格统一 uchar m_CheckSum; } MultiDataMes;自动化思路:实际上对于每个结构体来说Head部分都是相同的代码我们只需要特化数据部分。HEAD相关函数:#include QByteArray #include QDataStream QByteArray HeadToNet(const HEAD head) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); // 强制网络大端序 ds.setByteOrder(QDataStream::BigEndian); // 按结构体顺序依次写入 ds head.m_Iofo; ds head.m_MesLength; ds head.m_Send; ds head.m_Rece; ds head.m_TaskAttr; ds head.m_CommType; ds head.m_SerialNum; return buf; } // 返回true解析成功false数据长度不足/解析失败 bool NetToHead(QDataStream ds, HEAD outHead) { // 逐个读取字段 ds outHead.m_Iofo; ds outHead.m_MesLength; ds outHead.m_Send; ds outHead.m_Rece; ds outHead.m_TaskAttr; ds outHead.m_CommType; ds outHead.m_SerialNum; // 校验流状态读取出错返回false return ds.status() QDataStream::Ok; } QDataStream operator(QDataStream ds, const HEAD head) { QByteArray msg HeadToNet(head); ds.writeRawData(msg.constData(), msg.size()); return ds; } QDataStream operator(QDataStream ds, HEAD head) { NetToHead(ds,head); return ds; }HEAD配套完整 QDataStream 重载这时候可以优化一下之前的代码:QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes msg) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); // 1. 先写入报文头全部字段 ds msg.m_head; // 2. 写入报文体 ds msg.m_MainCtrl; ds msg.m_Ser; ds msg.m_CheckSum; return buf; } bool NetToMainCtrlReply(const QByteArray netData, MainCtrlBitReplyMes outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 ds outMsg.m_head; // 读取应答数据域 ds outMsg.m_MainCtrl; ds outMsg.m_Ser; ds outMsg.m_CheckSum; return ds.status() QDataStream::Ok; }注意我想使用脚本来完成这部分工作输入是我们的结构体输出是下面的文本:QDataStream operator(QDataStream ds, const MainCtrlBitReplyMes msg) { ds msg.m_head; ds msg.m_MainCtrl; ds msg.m_Ser; ds msg.m_CheckSum; return ds; } QDataStream operator(QDataStream ds, MainCtrlBitReplyMes msg) { ds msg.m_head; ds msg.m_MainCtrl; ds msg.m_Ser; ds msg.m_CheckSum; return ds; }那么代码可以进一步优化:QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes msg) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); ds msg; return buf; } bool NetToMainCtrlReply(const QByteArray netData, MainCtrlBitReplyMes outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 ds outMsg; return ds.status() QDataStream::Ok; }再添加一个结构体:typedef struct tagTestDataMes { HEAD m_head; int m_i32; unsigned int m_ui32; short m_i16; unsigned short m_ui16; char m_c8; unsigned char m_uc8; }TestDataMes; // char 输出重载 QDataStream operator(QDataStream ds, char c) { ds static_castqint8(c); return ds; } // char 输入重载 QDataStream operator(QDataStream ds, char c) { qint8 tmp; ds tmp; c static_castchar(tmp); return ds; } // TestDataMes serialize QDataStream operator(QDataStream ds, const TestDataMes msg) { ds msg.m_head; ds msg.m_i32; ds msg.m_ui32; ds msg.m_i16; ds msg.m_ui16; ds msg.m_c8; ds msg.m_uc8; return ds; } // TestDataMes deserialize QDataStream operator(QDataStream ds, TestDataMes msg) { ds msg.m_head; ds msg.m_i32; ds msg.m_ui32; ds msg.m_i16; ds msg.m_ui16; ds msg.m_c8; ds msg.m_uc8; return ds; }需要注意的是需要重载char类型QDataStream的和只支持 Qt 标准整数类型qint8/quint8/qint16/quint16/qint32/quint32代码里直接用了原生charQDataStream没有重载char编译直接报二元运算符错误。所以我们只需要生成结构体对应的QDataStream 重载函数。我们再继续优化编写模板函数:#include QByteArray #include QDataStream // 通用结构体 → 网络字节数组 templatetypename T QByteArray StructToNet(const T msg) { QByteArray buf; QDataStream ds(buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); ds msg; return buf; } // 通用网络字节数组 → 结构体 templatetypename T bool NetToStruct(const QByteArray netData, T outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); ds outMsg; return ds.status() QDataStream::Ok; }测试程序1:int main(int argc, char *argv[]) { QApplication a(argc, argv); MainCtrlBitReplyMes msg; memset(msg,0,sizeof(msg)); msg.m_head.init(0x9e,sizeof(MainCtrlBitReplyMes) - 1,0x0201,0x1001,0xA1,0x20); msg.m_MainCtrl 0x01; msg.m_Ser 0x10; msg.m_CheckSum 0x20; QByteArray ba; ba.append((const char *)msg,sizeof(msg)); qDebug() ba.toHex(); QByteArray changed StructToNet(msg); qDebug() changed.toHex(); MainCtrlBitReplyMes msg1; NetToStruct(changed,msg1); { QByteArray ba; ba.append((const char *)msg1,sizeof(msg1)); qDebug() ba.toHex(); qDebug() __FILE__ __LINE__ msg1.m_Ser; } return a.exec(); }9e0f0001020110a120000000000110209e000f02011001a120000000000110209e0f0001020110a12000000000011020..\Test\main.cpp 211 16测试程序2:int main(int argc, char *argv[]) { QApplication a(argc, argv); TestDataMes msg; memset(msg,0,sizeof(msg)); msg.m_head.init(0x9e,sizeof(TestDataMes) - 1,0x0201,0x1001,0xA1,0x20); msg.m_i32 1234; msg.m_uc8 3; QByteArray ba; ba.append((const char *)msg,sizeof(msg)); qDebug() ba.toHex(); QByteArray changed StructToNet(msg); qDebug() changed.toHex(); TestDataMes msg1; NetToStruct(changed,msg1); { QByteArray ba; ba.append((const char *)msg1,sizeof(msg1)); qDebug() ba.toHex(); qDebug() __FILE__ __LINE__ msg1.m_i32 msg.m_uc8; } return a.exec(); }输出:9e1a0001020110a12000000000d2040000000000000000000000039e001a02011001a12000000000000004d2000000000000000000039e1a0001020110a12000000000d204000000000000000000000003..\Test\main.cpp 210 1234 3生成QDataStream的和重载函数的脚本文件gen_struct_code.pyimport re # 在这里粘贴你的所有结构体文本 struct_src typedef struct tagMainCtrlBitReplyMes { HEAD m_head; // 报文头 unsigned char m_MainCtrlStatus; // 主控板状态00H:正常 11H:异常 unsigned char m_SerStatus; // 伺服板状态00H:正常 11H:异常 unsigned char m_CheckSum; // 校验和 }MainCtrlBitReplyMes; typedef struct tagTestDataMes { HEAD m_head; int m_i32; unsigned int m_ui32; short m_i16; unsigned short m_ui16; char m_c8; unsigned char m_uc8; }TestDataMes; # def parse_structs(text): pat re.compile(rtypedef struct\s(\w)\s*\{(.*?)\}\s*(\w);, re.S) res [] for m in pat.finditer(text): struct_tag m.group(1) body m.group(2).strip() struct_name m.group(3) fields [] lines [line.strip() for line in body.splitlines() if line.strip()] for line in lines: line_no_comment re.sub(r//.*, , line).strip() if not line_no_comment: continue pure line_no_comment.rstrip(;) parts pure.split() var parts[-1] ty .join(parts[:-1]) fields.append((ty, var)) res.append((struct_name, fields)) return res def gen_cpp_code(struct_list): output [] for name, fields in struct_list: output.append(f// {name} 序列化反序列化 ) # // 输出 operator output.append(fQDataStream operator(QDataStream ds, const {name} msg)) output.append({) for _, var in fields: output.append(f ds msg.{var};) output.append( return ds;) output.append(}\n) # // 输出 operator output.append(fQDataStream operator(QDataStream ds, {name} msg)) output.append({) for _, var in fields: output.append(f ds msg.{var};) output.append( return ds;) output.append(}\n\n) return \n.join(output) if __name__ __main__: structs parse_structs(struct_src) code gen_cpp_code(structs) print( 生成完成复制下面全部代码到Qt头文件 ) print(code) # 自动输出到文本文件 with open(serial_code.h, w, encodingutf-8) as f: f.write(code) print(\n文件已自动生成serial_code.h和脚本同一文件夹)执行:python gen_struct_code.py打印: 生成完成复制下面全部代码到Qt头文件 // MainCtrlBitReplyMes 序列化反序列化 QDataStream operator(QDataStream ds, const MainCtrlBitReplyMes msg) { ds msg.m_head; ds msg.m_MainCtrlStatus; ds msg.m_SerStatus; ds msg.m_CheckSum; return ds; } QDataStream operator(QDataStream ds, MainCtrlBitReplyMes msg) { ds msg.m_head; ds msg.m_MainCtrlStatus; ds msg.m_SerStatus; ds msg.m_CheckSum; return ds; } // TestDataMes 序列化反序列化 QDataStream operator(QDataStream ds, const TestDataMes msg) { ds msg.m_head; ds msg.m_i32; ds msg.m_ui32; ds msg.m_i16; ds msg.m_ui16; ds msg.m_c8; ds msg.m_uc8; return ds; } QDataStream operator(QDataStream ds, TestDataMes msg) { ds msg.m_head; ds msg.m_i32; ds msg.m_ui32; ds msg.m_i16; ds msg.m_ui16; ds msg.m_c8; ds msg.m_uc8; return ds; } 文件已自动生成serial_code.h和脚本同一文件夹