我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。
这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。
目录
概述
这是我用于共享内存数据标识的元数据协议。共享内存或者网络协议都不应该假设对方和自己是一样的——不同的编程语言、不同的硬件架构,数据应当从字节层面定义。
主要要考虑的问题
- 字节序,不同架构的硬件字节序不同,只在同一架构下运行可以不考虑,但是一旦要发送给别的架构,再修改程序就来不及了,所以最好一开始就考虑到
- 数据类型长度,数据类型的长度在C和C++上是不确定的,而且可以有不同的对齐方式,因此将整个结构(struct)当成数据块处理是不行的
- 操作时的数据对齐,从网络或文件读取到的数据不一定符合地址对齐要求,强制转换类型可能导致程序因为地址对齐错误而退出
- 字符编码,这个我也没想过,我们写程序一般只识别整数和ASCII字符,带有中文的由前端去想办法
这个代码的说明如下
元数据协议规定共享的数据结构的元信息的表述方式
对于诸如共享内存这样的数据在访问数据前检查其数据格式是有必要的
共享的数据结构的定义的改变会导致无法正确访问共享数据
编译时的对齐方式的不同也会导致无法正确访问共享数据
因此在共享数据的头部放置以字节方式规定的数据元信息
由于元信息以字节方式定义,因此不存在格式混乱问题
元信息定义如下:
所有字节都是有符号的
整个元信息大小256字节
从偏移量0开始依次为:
0 GUID 36字节 包含一个GUID字符串,注册表格式,例如"A880D4F7-20AC-49BC-A58F-705D3BF05230"
意味着一种数据的唯一标识
36 SIZEOFSHORT 1字节 short的字节数
37 SIZEOFINT 1字节 int的字节数
38 SIZEOFLONG 1字节 long的字节数
39 BYTEORDER 1字节 主机字节序,低位放在低地址为1,否则为0
40+n*4 USERINT数组 每个4字节 用户定义的整数,主机字节序,共54个,若未用则应置为0
代码如下
//元数据辅助类
class CMeta
{
signed char data[256];//不要试图用int来冒充4个字节
public:
bool Set(signed char const * szGuid,int const * pInt,int nIntCount)
{
if(sizeof(int)!=4)
{
cout<<"fatal error : sizeof(int)!=4 , class Meta can not work . "<<endl;
return false;
}
if(NULL==szGuid || NULL==pInt || strlen((char const *)szGuid)>36 || nIntCount>54)return false;
memset(data,0,256);
strcpy((char *)(data+0),(char const *)szGuid);
data[36]=(signed char)sizeof(short);
data[37]=(signed char)sizeof(int);
data[38]=(signed char)sizeof(long);
int tempint=1;
data[39]=(signed char)*(signed char *)&tempint;
memcpy(data+40,pInt,4L*nIntCount);
return true;
}
string & toString(string & s)const
{
s="";
if(sizeof(int)!=4)
{
s="fatal error : sizeof(int)!=4 , class Meta can not work . \n";
return s;
}
char buf[1024];
memcpy(buf,data,36);
buf[36]='\0';
s+="GUID = ";
s+=buf;
s+=" \n";
sprintf(buf,"sizeof short= %d int= %d long= %d byteorder= %d\n",data[36],data[37],data[38],data[39]);
s+=buf;
int i,k;
k=0;
for(i=0;i<54;++i)
{
int temp;
memcpy(&temp,data+40+4*i,4);//注意,由于对齐问题,直接转换成int*可能会导致core dump!所以这样复制一次
if(0!=temp)k=i;//找到最大的i
}
for(i=0;i<=k;++i)
{
int temp;
memcpy(&temp,data+40+4*i,4);//注意,由于对齐问题,直接转换成int*可能会导致core dump!所以这样复制一次
sprintf(buf,"%2d = %6d ",i,temp);
s+=buf;
if(0==(i+1)%6)s+="\n";
}
return s;
}
bool Compare(CMeta const& tmp, string & msg)const
{
if (0 != memcmp(data, tmp.data, 256))
{
stringstream ss;
for (long i = 0; i < 256; ++i)
{
if (data[i] != tmp.data[i])
{
ss << "字节 " << i << " " << (int)(data[i]) << " " << (int)(tmp.data[i]) << endl;
}
}
msg = ss.str();
return false;
}
else
{
return true;
}
}
bool CheckGuid(signed char const * szGuid)const
{
return 0 == memcmp(data, szGuid, 36);
}
bool CheckSys()const
{
if(data[36]!=(signed char)sizeof(short))return false;
if(data[37]!=(signed char)sizeof(int))return false;
if(data[38]!=(signed char)sizeof(long))return false;
int tempint=1;
if(data[39]!=(signed char)*(signed char *)&tempint)return false;
return true;
}
int GetInt(int i)const
{
int ret;
memcpy(&ret, data + 40 + i * sizeof(int), sizeof(int));
return ret;
}
int GetIntCount()const
{
int i, k;
k = -1;
for (i = 0; i < 54; ++i)
{
int temp;
memcpy(&temp, data + 40 + 4 * i, 4);//注意,由于对齐问题,直接转换成int*可能会导致core dump!所以这样复制一次
if (0 != temp)k = i;//找到最大的i
}
return k + 1;
}
bool CopyTo(signed char * pTo)const
{
memcpy(pTo,data,256);
return true;
}
bool CopyFrom(signed char const * pFrom)
{
memcpy(data,pFrom,256);
return true;
}
};
用法
Set 用GUID和一组用户定义的整数填充,字节序等数值自动设置。一般传入数据结构的大小和版本
Compare 与另一个比较
CheckGuid 比较GUID是否一致,在我的使用方式里GUID相同但别的有差异说明版本或机型不同需要处理,GUID不一致说明不是预期的数据块
CheckSys 检查字节序和整数大小是否相同,这个方法的名字起得不好
GetInt 获取一个整数值
GetIntCount 获取设置的整数的数量,意思不大,如果最后的整数是0会被忽略,因此不能用来获取当初设置了几个整数值
CopyTo 复制到内存区域,其实就是个memcpy
CopyFrom 从内存区域复制,其实就是个memcpy
一般从网络或文件读取数据后应该先校验元数据是否符合预期,不符合预期就不应该胡乱处理了。
内存的struct和网络、文件数据之间是一般要经过转换,除非数据结构经过仔细规划,保证符合硬件架构和对齐设置,网络传输时很难保证,但是对于单一架构则比较容易。
不过即使是单一架构,也存在数据结构版本的问题,所以正确的元数据处理仍然是必要的。
(这里是结束)