#include <iostream> //下面说的是在服务器差异脏标记,不管在想客户端还是在数据库中经常用到的一种方法 //由于其实直接拿为进行操作,所以速度是非常快的,也许刚开始不太好理解,但只要熟悉之后就非常好用 //对于包而言如果Update包只改变一项你把整个结构体都发过去会大大增加网络带快,现在基本都走差异更新 enum UpdateAtt { eUpdate1, eUpdate2, eUpdate3, eUpdate4, eUpdate5, eUpdate6, eUpdate7, eUpdateMax }; #define Update_Att_Max_Num ((eUpdateMax >> 3) + 1) /** *@brief 脏标记类,关键理解此类 */ struct UserAttrFlag { bool IsUpdated(int nAttr) const { if(m_Flags[nAttr/8] & (1<<(nAttr%8))) return true; return false; } void SetBitFlag(int nAttr) { m_Flags[nAttr/8] |= (1<<(nAttr%8)); } void ClearBitFlag() { memset(m_Flags, 0, Update_Att_Max_Num); } char m_Flags[Update_Att_Max_Num]; }; struct UserReg { unsigned char m_AttrOffset[eUpdateMax]; ///< 角色属性偏移量,距离首地址的偏移位置 unsigned char m_AttrSize[eUpdateMax]; ///< 角色属性大小 }; struct Update { private://需要将此类的所有数据成员设置成私有,针对没个成员提供set/get接口 int nUpdate1; int nUpdate2; int nUpdate3; int nUpdate4; int nUpdate5; int nUpdate6; int nUpdate7; public: void SetUpdate1(int nData) { if(nUpdate1 != nData) { nUpdate1 = nData; SetDBEnumAttrDirty(eUpdate1); } } void SetUpdate7(int nData) { if(nUpdate7 != nData) { nUpdate7 = nData; SetDBEnumAttrDirty(eUpdate7); } } int GetUpdate1(){return nUpdate1;} int GetUpdate7(){return nUpdate7;} Update() { RegisterAttributes(); memset(&m_attrFlag, 0, sizeof(m_attrFlag)); } void RegisterAttributes();//注册标记 void _RegAttr(UpdateAtt eAtt, int nOffset, int nSize) { /// 角色属性偏移量,距离首地址的偏移位置,之前看到天龙代码直接存的是变量的地址,这种只适合单线程使用,多线程会发生内存拷贝 /// 所以用偏移量更合适 m_attReg.m_AttrOffset[eAtt] = nOffset; m_attReg.m_AttrSize[eAtt] = nSize; } void SetDBEnumAttrDirty(UpdateAtt eUpdateAttr) { m_attrFlag.SetBitFlag(eUpdateAttr); } UserAttrFlag m_attrFlag;//属相脏标记 UserReg m_attReg;//属相注册 public: const UserAttrFlag* GetAttrFlag() const {return &m_attrFlag;} const UserReg* GetAttrReg() const {return &m_attReg;} }; void Update::RegisterAttributes() { #define REG_DB_ATTR(type,var) _RegAttr(type, ((int)&(var) - (int)this), sizeof(var)); REG_DB_ATTR(eUpdate1, nUpdate1) REG_DB_ATTR(eUpdate2, nUpdate2) REG_DB_ATTR(eUpdate3, nUpdate3) REG_DB_ATTR(eUpdate4, nUpdate4) REG_DB_ATTR(eUpdate5, nUpdate5) REG_DB_ATTR(eUpdate6, nUpdate6) REG_DB_ATTR(eUpdate7, nUpdate7) } int main() { Update update; update.SetUpdate1(12); update.SetUpdate7(120); char* buff = new char[1024];//拼buff //拼包操作 int nOffset = 0; for (int i = 0; i < eUpdateMax; i++) { if (update.GetAttrFlag()->IsUpdated(i))//判断其是否为脏 { memcpy(buff + nOffset, &i, sizeof(int)); nOffset += sizeof(int); memcpy(buff + nOffset, &update + update.GetAttrReg()->m_AttrOffset[i], update.GetAttrReg()->m_AttrSize[i]); nOffset += update.GetAttrReg()->m_AttrSize[i]; } } getchar(); return 0; } //现在简单解释一下脏标记的处理 /* #define Update_Att_Max_Num ((eUpdateMax >> 3) + 1) 定义的宏,根据此宏就知道定义多大的char数组,他是吧将8个枚举作为一个char,而一个char正好是8位,+1是为了不足8位,也为其分配一个char void SetBitFlag(int nAttr) { m_Flags[nAttr/8] |= (1<<(nAttr%8)); } m_Flags[nAttr/8]是定位其在那个char上,如0,肯定是char[0],如果是9就是char[1] (1<<(nAttr%8))表示其在某个char的哪个位上,如9,(1<<(nAttr%8))就是第二个char[1]&10,这样就正确的设置到对应的位上了 其实将char[..]看出1011111这样位,而此种方法方法就是能更方便的操作,c++没有对应的位的类型,stl里面好像有个bitset,但复杂类型在某些场合不适用的 |=或操作正好将其设置成1 bool IsUpdated(int nAttr) const { if(m_Flags[nAttr/8] & (1<<(nAttr%8))) return true; return false; } 上面那个理解了,这个就好理解了 m_Flags[nAttr/8]也是定位哪个char,(1<<(nAttr%8)))也是对应char那个位置 &操作获取对应为是0,还是1 对于其他的大小和偏移量,为了打包更好获取其地址和大小,更快的打包而设置的,好理解 */ /* 额外补充 对于DBSvr怎么解析,是根绝实现定义好的function,然后根据类型直接调用,那个类型里面关于数据类型都是写死的,个人觉得不是太好 客户端我问了,他是根绝枚举,硬猜是哪个包,已醉 */
时间: 2024-10-12 08:04:44