目录
1.简介
在C++应用程序中,经常会涉及到对一些数据进行序列化和反序列化的处理。序列化可以将一个对象转换为一串字节流,这样就可以将其存储在硬盘上或者通过网络传输到其他设备上。而反序列化则是将这些字节流解析成原始的对象。
在Qt中,数据的序列化和反序列化可以使用QDataStream类来完成。QDataStream是一个方便的Qt类,它可以将基本数据类型、Qt数据类型以及用户定义的数据类型都进行序列化和反序列化。在使用QDataStream进行序列化时,需要指定一个QIODevice类的子类(例如QFile或QBuffer)来将数据写入到文件或者内存中。
示例如下:
//【1】结构体
typedef struct _stCtrlCmdHeader
{
unsigned char rCtrlObj;
unsigned char rCtrlObjID;
unsigned char rCtrlCmd;
unsigned short rDataLen;
public:
_stCtrlCmdHeader()
{
memset(this, 0, sizeof(_stCtrlCmdHeader));
rCtrlObj = 0x01;
rCtrlObjID = 0x00;
}
friend QDataStream& operator<<(QDataStream& dataStream, const _stCtrlCmdHeader& data) //序列化
{
dataStream << data.rCtrlObj;
dataStream << data.rCtrlObjID;
dataStream << data.rCtrlCmd;
dataStream << data.rDataLen;
return dataStream;
}
friend QDataStream& operator>>(QDataStream& dataStream, _stCtrlCmdHeader& data) //反序列化
{
dataStream >> data.rCtrlObj;
dataStream >> data.rCtrlObjID;
dataStream >> data.rCtrlCmd;
dataStream >> data.rDataLen;
return dataStream;
}
}stCtrlCmdHeader;
//【2】序列化到QByteArray
QByteArray data;
QDataStream out(&data, QIODevice::WriteOnly); // we will serialize the data into the data
dataStream.setByteOrder(QDataStream::LittleEndian); //设置小端对齐
out << QString("the answer is"); // serialize a string
out << (qint32)42; // serialize an integer
stCtrlCmdHeader header;
//...
out << header; //serialize struct or class
//【3】反序列化
char szBuffer[1024] = { 0 };
int len = 0 ;
//...获取网络数据
QByteArray byArray(szBuffer, len);
QDataStream dataStream(&byArray, QIODevice::ReadOnly);
dataStream.setByteOrder(QDataStream::LittleEndian);
char szText[20] = { 0 };
dataStream.readRawData((char*)szText, 10); //unserialize a string
qint32 number = 0;
dataStream >> number ; //unserialize an integer
stCtrlCmdHeader header;
//...
out >> header; //unserialize struct or class
在Qt中很方便地可以用到这些类,但是脱离Qt,在标准的C++标准库就没有这么方便了,今天就模仿QByteArray和QDataStream类的接口,写了序列化和反序列化的类CByteArray和CDataStream,可以无缝替换QByteArray和QDataStream,下面就来介绍一下CByteArray和CDataStream的设计和实现。
2.CByteArray设计实现
设计要点:
1) CByteArray可以理解是纯字节数组,可以存放任意格式的数据,于是后台用 std::basic_string<char>来存储实际的数据。
2)构造函数接收字符数组(char*+len)
3) 构造函数接受右值引用,实现快速的移动语义
4)重载符号=,接受右值引用,实现快速的移动语义
5)根据长度写入和读取原始数据接口
int writeRawData(const char* data, PUInt32 size) override;
int readRawData(char* datsa, PUInt32 size) override;
6) 整个类采用Pimpl技法实现
具体实现:
类型定义:DataType.h
#ifndef _DATA_TYPE_H_
#define _DATA_TYPE_H_
typedef char PInt8;
typedef unsigned char PUInt8;
typedef short PInt16;
typedef unsigned short PUInt16;
typedef int PInt32;
typedef unsigned int PUInt32;
typedef long long PInt64;
typedef unsigned long long PUInt64;
typedef float PFloat32;
typedef double PFloat64;
#endif
抽象接口定义如下:IDataBuffer.h
#ifndef _IDATA_BUFFER_H_
#define _IDATA_BUFFER_H_
#include "DataType.h"
class IDataBuffer
{
public:
virtual ~IDataBuffer(){}
virtual int writeBytes(const char*, PUInt32) = 0;
virtual int readBytes(char*, PUInt32) = 0;
virtual int writeRawData(const char*, PUInt32) = 0;
virtual int readRawData(char*, PUInt32) = 0;
virtual int bytesAvailable() const = 0;
virtual PUInt64 skip(PUInt64 maxSize) = 0;
};
#endif
ByteArray.h
#ifndef _BYTE_ARRAY_H_
#define _BYTE_ARRAY_H_
#include <string>
#include <memory>
#include "IDataBuffer.h"
class CByteArrayPrivate;
class CByteArray : public IDataBuffer
{
public:
explicit CByteArray();
CByteArray(const CByteArray& other);
CByteArray(CByteArray&& other);
CByteArray(const char* buffer, int size);
CByteArray& operator=(CByteArray&& other);
operator bool() const;
virtual ~CByteArray();
public:
int writeRawData(const char* data, PUInt32 size) override;
int readRawData(char* datsa, PUInt32 size) override;
int writeBytes(const char*, PUInt32) override;
int readBytes(char*, PUInt32) override;
const char* data() const;
int size() const;
int bytesAvailable() const override;
PUInt64 skip(PUInt64 maxSize) override;
CByteArray& append(const char* data, PUInt32 size);
CByteArray& append(const char* data);
void clear();
private:
std::unique_ptr<CByteArrayPrivate> d_ptr;
};
#endif
ByteArray.cpp
#include "ByteArray.h"
#include <assert.h>
#include <string.h>
//#include <string_view>
class CByteArrayPrivate
{
public:
explicit CByteArrayPrivate():m_readPos(0) {}
CByteArrayPrivate(const char* buffer, int size): m_buffer(buffer, size), m_readPos(0) {}
//这个类,有点类似于Golang语言中的切片Slice。这就意味着std::string_view本身并不拥有内存本身,
//它只是一个View,一个窗口,观看内存的窗口
//std::string_view m_buffer;
std::basic_string<char> m_buffer;
int m_readPos; //暂时没用
};
CByteArray::CByteArray()
: d_ptr(new CByteArrayPrivate)
{
}
CByteArray::CByteArray(const CByteArray& other)
: d_ptr(new CByteArrayPrivate)
{
d_ptr->m_buffer = other.d_ptr->m_buffer;
d_ptr->m_readPos = other.d_ptr->m_readPos;
}
CByteArray::CByteArray(CByteArray&& other)
: d_ptr(other.d_ptr.release())
{
other.d_ptr.reset(new CByteArrayPrivate);
}
CByteArray::CByteArray(const char* buffer, int size)
: d_ptr(new CByteArrayPrivate(buffer,size))
{
}
CByteArray::operator bool() const
{
return (0 ==size());
}
CByteArray& CByteArray::operator=(CByteArray&& other)
{
if (d_ptr == other.d_ptr){
return *this;
}
CByteArrayPrivate* temp = d_ptr.release();
d_ptr.reset(other.d_ptr.release());
other.d_ptr.reset(temp);
return *this;
}
CByteArray::~CByteArray()
{
}
int CByteArray::writeRawData(const char* data, PUInt32 size)
{
d_ptr->m_buffer.append(data, size);
return size;
}
int CByteArray::readRawData(char* data, PUInt32 size)
{
if (d_ptr->m_buffer.copy(data, size, 0) != size){
assert(0);
return 0;
}
d_ptr->m_buffer.erase(0, size);
return size;
}
const char* CByteArray::data() const
{
return d_ptr->m_buffer.data();
}
int CByteArray::size() const
{
return d_ptr->m_buffer.size();
}
int CByteArray::bytesAvailable() const
{
return size();
}
PUInt64 CByteArray::skip(PUInt64 maxSize)
{
PUInt64 nSkip = maxSize;
if (maxSize >= d_ptr->m_buffer.size()){
nSkip = d_ptr->m_buffer.size();
d_ptr->m_buffer.clear();
return nSkip;
}else{
d_ptr->m_buffer.erase(0, maxSize);
return nSkip;
}
}
int CByteArray::writeBytes(const char*, PUInt32)
{
assert(0);
return 0;
}
int CByteArray::readBytes(char*, PUInt32)
{
assert(0);
return 0;
}
CByteArray& CByteArray::append(const char* data, PUInt32 size)
{
d_ptr->m_buffer.append(data, size);
return *this;
}
CByteArray& CByteArray::append(const char* data)
{
if (!data){
return *this;
}
int len = strlen(data);
return append(data, len);
}
void CByteArray::clear() {
d_ptr->m_buffer.clear();
}
3.CDataStream设计实现
设计要点:
1) 支持所有支持IDataBuffer的实现类
2)支持C++的基本数据类型序列化和反序列化,重载operator<<和operator>>
3) 支持自定义数据类型的序列化和反序列化,如结构体或类
4)反序列化时,可以忽略指定的字节
5)反序列化可变字节长度时,可以获取未序列化的数据长度,用户获取长度申请空间并反序列化到指定的内存中
具体实现:
ShortWaveToolFun.h
#ifndef _SHORT_WAVE_TOOL_FUNC_H_
#define _SHORT_WAVE_TOOL_FUNC_H_
#include <string>
#include "DataType.h"
#include <functional>
#include <memory>
#include <string.h>
#define WORK_MSG_ID(link, type, sub) ( (link<<16) | (type<<8) | sub )
#define GET_WORK_TYPE(id) ((id >> 8) & 0xff)
#define GET_WORK_CMD(id) (id & 0xff)
#define GET_WORK_B_TYPE(id) ((id >> 16) & 0xff)
#define MEMBER_OFF_SET(type, member) ((size_t) &(static_cast<type*>(0))->member)
#define CONTAINER_OF(ptr, type, member) ({ \
const typeof(((type*)0)->member) * __mptr = (ptr); \
(type*)((char*)__mptr - MEMBER_OFF_SET(type,member)); })
template <typename T, class = typename std::enable_if<sizeof(T)%2==0>::type>
T reverseNumber(T value)
{
static_assert(sizeof(T)%2==0, "T type is invalid!!!");
union X{
T val1;
char val2[sizeof(T)];
};
int i = 0;
int size = sizeof(T);
char temp = 0;
X x;
x.val1 = value;
for (i = 0; i < sizeof(T)/2; i++){
temp = x.val2[i];
x.val2[i] = x.val2[size-i-1];
x.val2[size-i-1] = temp;
}
return x.val1;
}
template <typename T, class = typename std::enable_if<sizeof(T)<=sizeof(PUInt64)>::type>
PUInt64 transTToPUInt64(T value)
{
static_assert(sizeof(T)<=sizeof(PUInt64), "T type is invalid!!!");
union X{
T val1;
PUInt64 val2;
};
X x;
x.val2 = 0; //将高位置0
x.val1 = value;
return x.val2;
}
template <typename T, class = typename std::enable_if<sizeof(T)<=sizeof(PUInt64)>::type>
T transPUInt64ToT(PUInt64 value)
{
static_assert(sizeof(T)<=sizeof(PUInt64), "T type is invalid!!!");
union X{
T val1;
PUInt64 val2;
};
X x;
x.val2 = value;
return x.val1;
}
#endif
DataStream.h
#ifndef _DATA_STREAM_H_
#define _DATA_STREAM_H_
#include "DataType.h"
class IDataBuffer;
class CByteArray;
class CDataStream
{
public:
explicit CDataStream(IDataBuffer* pBuffer);
~CDataStream();
bool bigEndian() const;
void setBigEndian(bool newBigEndian);
CDataStream& operator<<(bool i);
CDataStream& operator<<(PInt8 i);
CDataStream& operator<<(PUInt8 i);
CDataStream& operator<<(PInt16 i);
CDataStream& operator<<(PUInt16 i);
CDataStream& operator<<(PInt32 i);
CDataStream& operator<<(PUInt32 i);
CDataStream& operator<<(PInt64 i);
CDataStream& operator<<(PUInt64 i);
CDataStream& operator<<(PFloat32 i);
CDataStream& operator<<(PFloat64 i);
CDataStream& operator<<(const char* i);
CDataStream& operator<<(const CByteArray& data);
template <typename T>
CDataStream& operator<<(const T& t)
{
(*this) << t;
return *this;
}
CDataStream& operator>>(bool& i);
CDataStream& operator>>(PInt8& i);
CDataStream& operator>>(PUInt8& i);
CDataStream& operator>>(PInt16& i);
CDataStream& operator>>(PUInt16& i);
CDataStream& operator>>(PInt32& i);
CDataStream& operator>>(PUInt32& i);
CDataStream& operator>>(PInt64& i);
CDataStream& operator>>(PUInt64& i);
CDataStream& operator>>(PFloat32& i);
CDataStream& operator>>(PFloat64& i);
CDataStream& operator>>(char*& i);
CDataStream& operator>>(CByteArray& data);
template <typename T>
CDataStream& operator>>(T& t)
{
(*this) >> t;
return *this;
}
int writeRawData(const char* data, PUInt32 size);
int readRawData(char* data, PUInt32 size);
bool atEnd() const;
int bytesAvailable() const;
int skipRawData(int len);
private:
bool m_bigEndian;
IDataBuffer* m_buffer;
};
#endif
DataStream.cpp
#include "IDataBuffer.h"
#include "DataStream.h"
#include "ShortWaveToolFun.h"
#include <string.h>
#include "ByteArray.h"
CDataStream::CDataStream(IDataBuffer* pBuffer)
: m_bigEndian(false)
, m_buffer(pBuffer)
{
}
CDataStream::~CDataStream()
{
}
bool CDataStream::bigEndian() const
{
return m_bigEndian;
}
void CDataStream::setBigEndian(bool newBigEndian)
{
m_bigEndian = newBigEndian;
}
CDataStream& CDataStream::operator<<(bool i)
{
*this << (PInt8)i;
return *this;
}
CDataStream& CDataStream::operator<<(PInt8 i)
{
PInt8 ch = i;
m_buffer->writeRawData(&ch, sizeof(ch));
return *this;
}
CDataStream& CDataStream::operator<<(PUInt8 i)
{
PInt8 ch = i;
m_buffer->writeRawData(&ch, sizeof(ch));
return *this;
}
CDataStream& CDataStream::operator<<(PInt16 i)
{
PInt16 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PUInt16 i)
{
PUInt16 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PInt32 i)
{
PInt32 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PUInt32 i)
{
PUInt32 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PInt64 i)
{
PInt64 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PUInt64 i)
{
PUInt64 temp = i;
if (m_bigEndian){
temp = reverseNumber(i);
}
m_buffer->writeRawData((char*)&temp, sizeof(temp));
return *this;
}
CDataStream& CDataStream::operator<<(PFloat32 i)
{
PFloat32 temp = i;
if (m_bigEndian){
union {
float val1;
PUInt32 val2;
} x;
x.val1 = i;
x.val2 = reverseNumber(x.val2);
m_buffer->writeRawData((char*)&x.val2, sizeof(i));
}else{
m_buffer->writeRawData((char*)&temp, sizeof(i));
}
return *this;
}
CDataStream& CDataStream::operator<<(PFloat64 i)
{
PFloat64 temp = i;
if (m_bigEndian){
union {
double val1;
PUInt64 val2;
} x;
x.val1 = i;
x.val2 = reverseNumber(x.val2);
m_buffer->writeRawData((char*)&x.val2, sizeof(i));
}else{
m_buffer->writeRawData((char*)&temp, sizeof(i));
}
return *this;
}
CDataStream& CDataStream::operator<<(const char* i)
{
if (!i) {
*this << (PUInt32)0;
return *this;
}
int len = strlen(i) + 1;
*this << (PUInt32)len;
m_buffer->writeRawData(i, len);
return *this;
}
CDataStream& CDataStream::operator>>(bool& i)
{
PInt8 v;
*this >> v;
i = !!v;
return *this;
}
CDataStream& CDataStream::operator>>(PInt8& i)
{
m_buffer->readRawData(&i, 1);
return *this;
}
CDataStream& CDataStream::operator>>(PUInt8& i)
{
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
return *this;
}
CDataStream& CDataStream::operator>>(PInt16& i)
{
m_buffer->readRawData((char *)(&i), sizeof(i));
if (m_bigEndian){
i = reverseNumber(i);
}
return *this;
}
CDataStream& CDataStream::operator>>(PUInt16& i)
{
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
if (m_bigEndian){
i = reverseNumber(i);
}
return *this;
}
CDataStream& CDataStream::operator>>(PInt32& i)
{
m_buffer->readRawData((char *)(&i), sizeof(i));
return *this;
}
CDataStream& CDataStream::operator>>(PUInt32& i)
{
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
if (m_bigEndian){
i = reverseNumber(i);
}
return *this;
}
CDataStream& CDataStream::operator>>(PInt64& i)
{
m_buffer->readRawData((char *)(&i), sizeof(i));
if (m_bigEndian){
i = reverseNumber(i);
}
return *this;
}
CDataStream& CDataStream::operator>>(PUInt64& i)
{
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
if (m_bigEndian){
i = reverseNumber(i);
}
return *this;
}
CDataStream& CDataStream::operator>>(PFloat32& i)
{
i = 0.0f;
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
if (m_bigEndian){
union {
float val1;
PUInt32 val2;
} x;
x.val2 = reverseNumber(*reinterpret_cast<PUInt32 *>(&i));
i = x.val1;
}
return *this;
}
CDataStream& CDataStream::operator>>(PFloat64& i)
{
i = 0.0f;
m_buffer->readRawData(reinterpret_cast<char *>(&i), sizeof(i));
if (m_bigEndian){
union {
double val1;
PUInt64 val2;
} x;
x.val2 = reverseNumber(*reinterpret_cast<PUInt64 *>(&i));
i = x.val1;
}
return *this;
}
CDataStream& CDataStream::operator>>(char*& i)
{
PUInt32 len = 0;
*this >> len;
if (len == 0)
return *this;
m_buffer->readRawData(i, len);
i[len] = '\0';
return *this;
}
CDataStream& CDataStream::operator<<(const CByteArray& data)
{
m_buffer->writeRawData(data.data(), data.size());
return *this;
}
CDataStream& CDataStream::operator>>(CByteArray& data)
{
PUInt32 len = 0;
*this >> len;
if (len == 0)
return *this;
std::unique_ptr<char[]> pBuffer(new char[len]);
m_buffer->readRawData(pBuffer.get(), len);
data.append(pBuffer.get(), len);
return *this;
}
int CDataStream::writeRawData(const char* data, PUInt32 size)
{
return m_buffer->writeRawData(data, size);
}
int CDataStream::readRawData(char* data, PUInt32 size)
{
return m_buffer->readRawData(data, size);
}
bool CDataStream::atEnd() const
{
return m_buffer->bytesAvailable() <= 0;
}
int CDataStream::bytesAvailable() const
{
return m_buffer->bytesAvailable();
}
int CDataStream::skipRawData(int len)
{
return m_buffer->skip(len);
}
4.使用实例
结构体定义:
//传输的整个报文
typedef struct _stShortWavePacket
{
//共用
PUInt32 contextID;
void* pAppData; //正文数据
PUInt16 frameIndex;
stObjectFlag srcObjMark;
stObjectFlag destObjMark;
PUInt8 isMustResponse;
PUInt8 responseStatus;
public:
_stShortWavePacket() {
}
}stShortWavePacket;
using stChannelWorkParamData = KVData<PUInt16, PUInt64>;
using ChannelWorkParamDataContainer = std::vector<std::shared_ptr<stChannelWorkParamData>>;
//结构体定义
template <bool isHaveType>
struct stChannelWorkParam
{
PUInt16 signalType; //类别
ChannelWorkParamDataContainer workParamData;
public:
stChannelWorkParam() {
signalType = 0;
}
stChannelWorkParam(const stChannelWorkParam&) = delete;
stChannelWorkParam& operator=(const stChannelWorkParam&) = delete;
PUInt16 getDataSize() {
PUInt16 size = isHaveType ? 2 : 0;
for (auto& it : workParamData) {
size += it->getSize();
}
return size;
}
std::string toString() const {
return std::string("type:") + numConvertString(signalType);
}
friend CDataStream& operator<<(CDataStream& dataStream, const stChannelWorkParam& data)
{
if (isHaveType) {
dataStream << data.signalType;
}
for (auto& it : data.workParamData) {
it->serializeData(dataStream);
}
return dataStream;
}
friend CDataStream& operator>>(CDataStream& dataStream, stChannelWorkParam& data)
{
if (isHaveType) {
dataStream >> data.signalType;
}
while (dataStream.bytesAvailable() > 6) {
std::shared_ptr<stChannelWorkParamData> pData = std::make_shared<stChannelWorkParamData>();
pData->unserializeData(dataStream);
data.workParamData.push_back(pData);
}
return dataStream;
}
};
using stSendChannelWorkParam = stChannelWorkParam<true>;
using stUploadRegisterStatus = stChannelWorkParam<false>;
序列化数据:
template <PUInt32 type, typename stType>
int encodeData(char* data, int& len, const stShortWavePacket* pPacket)
{
PUInt16 crcCode = 0;
CByteArray byArray;
CDataStream dataStream(&byArray);
stType* pMessage = reinterpret_cast<stType*>(pPacket->pAppData);
if (pMessage == nullptr)
return 1;
//[1]
dataStream.writeRawData((const char*)m_startFlag, sizeof(m_startFlag));
dataStream << getFrameNo();
dataStream << pPacket->srcObjMark.data;
dataStream << pPacket->destObjMark.data;
dataStream << pPacket->isMustResponse;
dataStream << (PUInt8)GET_WORK_TYPE(type);
dataStream << pMessage->getDataSize();
//[2]
dataStream << (*pMessage);
crcCode = getCRCCode(byArray.data(), byArray.size());
dataStream << crcCode;
dataStream.writeRawData((const char*)m_endFlag, sizeof(m_endFlag));
len = byArray.size();
memcpy(data, byArray.data(), len);
return 0;
}
接收到网络数据,反序列化数据:
template <PUInt32 type, typename stType>
int parseData(const char* data, int len, stShortWavePacket* pPacket)
{
CByteArray byArray(data, len);
CDataStream dataStream(&byArray);
PUInt16 length = 0;
pPacket->contextID = type;
dataStream.skipRawData(4); //忽略4个字节
dataStream >> pPacket->frameIndex;
m_frameNo = pPacket->frameIndex;
dataStream >> pPacket->srcObjMark.data;
dataStream >> pPacket->destObjMark.data;
dataStream >> pPacket->isMustResponse; //是否应答
dataStream.skipRawData(1); //忽略功能指令,1个字节
dataStream >> length;
if (length != stType::getDataSize())
return -1;
std::unique_ptr<stType> pAppData = std::make_unique<stType>();
dataStream >> (*pAppData);
pPacket->pAppData = pAppData.release();
return 0;
}
5.总结
以上代码都是我项目上用到的代码,绝对有效。也希望可以帮到有需求的朋友。如果有不明白的地方,也可以私信我。