让数据解析能够做到向前向后完全兼容(最近做项目总结)
来源:互联网 发布:王者荣耀排位刷星软件 编辑:程序博客网 时间:2024/06/05 14:35
原文: http://www.vimer.cn/2010/03/%E8%AE%A9%E6%95%B0%E6%8D%AE%E8%A7%A3%E6%9E%90%E8%83%BD%E5%A4%9F%E5%81%9A%E5%88%B0%E5%90%91%E5%89%8D%E5%90%91%E5%90%8E%E5%AE%8C%E5%85%A8%E5%85%BC%E5%AE%B9%E6%9C%80%E8%BF%91%E5%81%9A%E9%A1%B9%E7%9B%AE.html
转载自Vimer的程序世界 [ http://www.vimer.cn ]
最近在做项目的时候,遇到一个问题,即结构体内的字段可能会在未来的时间内不停的增加(不会减少或者删除),所以在打包解包的时候就会涉及到版本兼容的问题,并且是向前和向后同时兼容。
我们先来看一下,如果结构体的内容永远不变,那么我们用结构体自解析的方法:
typedef struct _farmbase_land1
{/*{{{*/
unsigned char ID;
unsigned char bitmap;
_farmbase_land1()
{
ID = 0;
bitmap = 0;
}
int Output(unsigned int /*ver*/,char*& buff,int& iLen,int iMaxLen)
{/*{{{*/
int needLen = sizeof(unsigned char)*2;
if(needLen>iMaxLen)
{
return FBErrSystemNoMem;
}
char *t_Buff = buff;
*(unsigned char*)t_Buff = ID;
t_Buff+=sizeof(unsigned char);
*(unsigned char*)t_Buff = bitmap;
t_Buff+=sizeof(unsigned char);
iLen = t_Buff - buff;
return 0;
}/*}}}*/
int Input(unsigned int /*ver*/,char *buff,int& iLen,int iMaxLen)
{/*{{{*/
int needLen = sizeof(unsigned char)*2;
if(needLen>iMaxLen)
{
return FBErrSystemNoMem;
}
char *t_Buff = buff;
ID = *(unsigned char*)t_Buff;
t_Buff+=sizeof(unsigned char);
bitmap = *(unsigned char*)t_Buff;
t_Buff+=sizeof(unsigned char);
iLen = t_Buff - buff;
return 0;
}/*}}}*/
}CFarmBaseLand1;/*}}}*/
可以看出,结构体能够自己在打包/解包的时候,返回使用了的buff的长度,所以,如果我们是处理上述结构体的一个数组,那么代码可以这样写:
static int Output(unsigned int ver,char *buff,int& iLen,int iMaxLen,map<unsigned int,T>* ptrMap)
{/*{{{*/
if(ptrMap==NULL)
{
return -1;
}
char *t_Buff = buff;
int t_Len=0;
int t_MaxLen=iMaxLen;
*(unsigned short*)t_Buff = ptrMap->size();
t_Buff += sizeof(unsigned short);
for(typename map<unsigned int,T>::iterator it=ptrMap->begin();it!=ptrMap->end();++it)
{
t_MaxLen = iMaxLen - (t_Buff-buff);
int ret = it->second.Output(ver,t_Buff,t_Len,t_MaxLen);
if(ret)
{
return -3;
}
t_Buff += t_Len;
}
iLen = t_Buff - buff;
return 0;
}/*}}}*/
static int Input(unsigned int ver,char *buff,int& iLen,int iMaxLen,map<unsigned int,T>* ptrMap)
{/*{{{*/
if(ptrMap==NULL)
{
return -1;
}
if(iMaxLen == 0)
{
//这个字段暂时没有数据
iLen = 0;
return 100;
}
int t_Len=0;
int t_MaxLen=iMaxLen;
char *t_Buff = buff;
if(sizeof(unsigned short)>(unsigned int)t_MaxLen)
{
return -2;
}
unsigned short sCount = *(unsigned short*)t_Buff;
t_Buff += sizeof(unsigned short);
for(unsigned int i = 0;i<sCount;++i)
{
T t_data;
t_MaxLen = iMaxLen - (t_Buff - buff);
int ret = t_data.Input(ver,t_Buff,t_Len,t_MaxLen);
if(ret)
{
return -3;
}
t_Buff += t_Len;
(*ptrMap)[t_data.ID] = t_data;
}
iLen = t_Buff - buff;
return 0;
}/*}}}*/
但是这样自解析带来的最大问题就是,我们会控制大量的版本,并且每次升级版本都要重发所有程序,这个成本是非常大的。
所以,我们需要对这种解析方式进行更改,需要考虑两个问题:
1.当新的API读到旧的格式的数据的时候,并写回的时候,怎么做。(即兼容旧数据)
2.当旧的已经发布的API读到新的数据的时候,并写回的时候,怎么做。(即兼容新数据)
答案是:
1.当新API读到旧的数据的时候,新API多的参数用默认值填充,写回的时候按照新API的格式写回。
2.当旧API读到新数据,自己不认识的那段buff,要保存起来,写回的时候,将这段buff原样memcpy。
所以output和input函数将会升级成这个样子:
typedef struct _farmbase_land1
{/*{{{*/
unsigned short precId;
string extrabuff;
_farmbase_land1()
{
precId = 0;
}
int ExtraOutput(unsigned int /*ver*/,char*& buff,int& iLen,int iMaxLen)
{/*{{{*/
int needLen = extrabuff.size()+sizeof(unsigned short)+0+sizeof(unsigned char);//这个地方要加上最新的字段
if(needLen>iMaxLen)
{
return FBErrSystemNoMem;
}
char *t_Buff = buff;
*(unsigned char *)t_Buff = extrabuff.size()+sizeof(unsigned short)+0; //这里的0代表以后扩展字段的sizeof
t_Buff+=sizeof(unsigned char);
/*在这里添加字段*/
*(unsigned short *)t_Buff = precId;
t_Buff+=sizeof(unsigned short);
if(extrabuff.size()>0)
{
memcpy(t_Buff,extrabuff.c_str(),extrabuff.size());
}
t_Buff+=extrabuff.size();
iLen = t_Buff - buff;
return 0;
}/*}}}*/
int ExtraInput(unsigned int /*ver*/,char *buff,int& iLen,int iMaxLen)
{/*{{{*/
char *t_Buff = buff;
unsigned char t_size=0;
unsigned char allsize = (unsigned char)*t_Buff;
t_Buff+=sizeof(unsigned char);
/*
//在这里可以任意的添加字段了
//这里这样写,主要是为了当新的api读到老数据的时候
unsigned char testdata;
t_size = allsize - (t_Buff-buff-sizeof(unsigned char));
if(t_size != 0)
{
testdata = (unsigned char)*t_Buff;
t_Buff+=sizeof(unsigned char);
}
else
{
testdata = 0;
}
unsigned char testdata2;
t_size = allsize - (t_Buff-buff-sizeof(unsigned char));
if(t_size != 0)
{
testdata2 = (unsigned char)*t_Buff;
t_Buff+=sizeof(unsigned char);
}
else
{
testdata2 = 0;
}
*/
t_size = allsize - (t_Buff-buff-sizeof(unsigned char));
if(t_size != 0)
{
precId = *(unsigned short*)t_Buff;
t_Buff+=sizeof(unsigned short);
}
else
{
precId = 0;
}
t_size = allsize - (t_Buff-buff-sizeof(unsigned char));
extrabuff.resize(t_size);
if(extrabuff.size()>0)
{
memcpy((char*)extrabuff.c_str(),t_Buff,extrabuff.size());
}
t_Buff+=extrabuff.size();
iLen = t_Buff - buff;
return 0;
}/*}}}*/
}CFarmBaseLand1;/*}}}*/
而当解析上面的结构体数组时,则和原来的函数没有什么区别,所以保证了对外的接口统一。
最终问题完美解决~~
- 让数据解析能够做到向前向后完全兼容(最近做项目总结)
- “向前兼容”&&“向后兼容”
- 向前兼容,向后兼容
- 兼容 向前兼容 向后兼容
- 向前向后兼容
- 向前兼容和向后兼容
- 向前兼容和向后兼容
- 向前兼容与向后兼容
- 向前兼容和向后兼容
- “向前兼容”与“向后兼容”
- 向前兼容与向后兼容
- 向前兼容和向后兼容
- 向前兼容和向后兼容
- 向前兼容和向后兼容
- 向前兼容和向后兼容
- 向前兼容和向后兼容
- 向下兼容、向上兼容、向前兼容、向后兼容
- 关于向前兼容和向后兼容
- javaio-WriteStringToFile
- 纠结...
- 编程语言的发展趋势及未来方向(3):函数式编程
- [C#]BinaryFormatter、SoapFormatter、XML3种序列化
- 文件内容查找 find+grep
- 让数据解析能够做到向前向后完全兼容(最近做项目总结)
- word2003 中调出“公式编辑器”到工具栏中
- 他们说过,我觉得它是对的
- Symbian C++ 的 NewL ConstructL NewLC ELeave
- 取得库中所有的外键
- 要开始交软件学习的朋友了
- 求J2ME API中文版
- 编码中常使用的工具
- SQL 常用语句