多级缓冲的服务器数据服务机制实现(二)
来源:互联网 发布:c语言接口与实现 豆瓣 编辑:程序博客网 时间:2024/04/30 04:03
昨天,写了一篇关于多级缓冲服务的文章。
那么今天,我们就来点实际的代码,完成以上的所有功能吧。
按照昨天的思路,我需要两个程序,一个是和客户端通讯的程序,这个程序我们姑且认为它就是游戏服务器,那么,与之对应的,还有一个专门负责和后来存储介质通讯的服务进程。
既然要做这道菜,先看看我们需要点什么佐料。
(1)一个共享内存的类,这个类提供给我们与共享内存交互的功能,对外的接口需要,获得一个内存指针地址,获得当前已有的数据个数,删除其中一个数据,得到自由的内存块个数等等。
(2)我需要一个提供MRU算法的类,用于管理我的有效的共享内存指针,并提供相应的替换算法。
(3)对应我的开源服务器,我需要实现一个dll(或者so),来处理玩家创建,登陆,离开,更新,查询操作。
(4)我需要一个IO的类,用于如果我在内存中命中不到数据的时候,可以从IO里进行查找获得数据。
(5)一个定时执行的类,用于数据进程定时刷入IO接口。
好的,让我们开始把。
对于(1),我需要组织一个支持windows和linux共享内存的接口。要能自动根据操作系统的不同选用不同的方法,并实现一个模板类,完成对共享内存的管理,对于我而言,每个数据都是一个T*,当共享内存创建的时候,我需要指定一个T的个数,我会根据这个sizeof(T)*nCount来创建一整块巨大的内存,从里面切分出不同的T*,这样没有内存碎片。同时,还需要共享内存提供一个"头"的数据空间,用来标明每个T*的使用状态,当然,可能聪明的你已经发现,我使用的是sizrof(T),这样是不是有问题呢?因为对于玩家而言,我可能有玩家的数据,还可能有各种的数组,比如装备格子,技能格子等。这里我要强调一下,为了保证我对数据的统一管理和检查,我要求玩家数据必须是定长的,也就是说对于玩家数据vector,dueue,map等STL容器以及带缓冲的容器是不被允许的,因为变长会导致出错后内存查找的困难。当然,你在逻辑计算过程中,可以使用这个。但是元数据一定是需要定长的。如果你有兴趣,可以尝试在这里改造成变长的。
我的共享内存实现,你可以查看CSMAccessObject类和ShareMemoryAPI类,基础知识。
关键借助这两个类,我实现了一个CSMPool。
里面包含了这样一个数据头结构:
复制代码
这个结构就是一个完整的数据"头",这里我要解释一下,DS是啥,这个是我私自起的名字(DataServer服务进程的简称,就是我说的共享内存和IO同步所执行的进程)。在这里m_ttUpdateTime是由DS负责修改,当IO写入成功之后,需要更新这个变量,这样,只要比对t*中的时间戳和这个时间戳,我就知道哪些数据需要我更新到IO里面,因为很有可能,大部分数据在某时刻是不需要更新的。m_pT就是你的数据类,这个类实现是在PlayerObject.h里面,这里面有一个基类CObject是需要被填充的。而实际体class CPlayerData : public CObject
我先说说,CObject有什么关键性数据:
复制代码
这个类有一个更新写标记,这个写标记给DS使用的,当DS判断写标记正在写入的时候,就不会存储这些数据,等到下一次执行的时候在存储,同时当写标记完成的时候,基类自动更新m_ttUpdateTime这个时间戳,DS会比对_SMBlock.m_ttUpdateTime和T->m_ttUpdateTime的数值,看看需要不需要进行存储。继承这个类,当数据发生修改的时候,一定要套用写入宏,比如这样:
复制代码
这样就能最大程度的保证数据的完整性,尽量减少存入半截数据的风险。
我的CPlayerData类只是举一个例子,当然,你可以为这个类提供更多的变量和方法。根据你的需求而定。
好了,话说回来。我的CSMPool是个什么样子呢?
复制代码
(2)MRU算法
CMapTemplate类的实现,这部分代码我改进了当初我写的MRU代码文章,添加了几个函数和扩展了一些函数参数来满足我的需求。具体可以参考MapTemplate.h
(3)对应我的PruenessScopeServer框架,我只需要创建一个dll工程,引用一些头文件就可以完全不用框架代码了。具体引用的头文件在IObject目录里面,这样,我可以无视PruenessScopeServer是否存在,只要专心开发我的业务逻辑即可。
当然,规范还是要有的,具体看看我的PlayerPool.cpp,里面90%的代码写法都是固定的。可以和开源框架中的Base的dll工程比较一下,呵呵,唯一不同的就是,我这个类需要支持以下处理方法。
#define COMMAND_PLAYINSERT 0x1010 //用户数据创建
#define COMMAND_PLAYUPDATE 0x1011 //用户数据更新
#define COMMAND_PLAYDELETE 0x1012 //用户数据删除
#define COMMAND_PLAYSEACH 0x1013 //用户查询
#define COMMAND_PLAYLOGIN 0x1014 //用户登陆
#define COMMAND_PLAYLOGOFF 0x1015 //用户离开
客户端会给我以上的调用,那么,我们来根据以上的方法去实现代码吧。
对应以上需求,我定义了:
复制代码
以上的方法来实现对这些命令的处理。具体方法可以参考PlayerPoolCommand.cpp的实现。这里就不多说了。
(4)对于共享内存和介质之间的操作。
为了举例,我不用数据库,使用文件来说明,假设我的文件就是我的数据源。当然,你可以用你的数据库引擎替代这里的实现。
复制代码
这里你可以填充你的代码。
(5)这里因为我用的是ACE的框架,所以自然也就用ACE的定时器,比较手熟,当然,也可以用你自己喜欢的定时器替换。
复制代码
代码很简单,其实是我最喜欢的,因为越简单的代码,出错机会越低。
以上完整代码如下:我在window7+VS2005下测试通过,linux版本还没测试,等有时间我在上面编译一下,我相信会很顺利的。
好了,把PlayerPool编译一下,生成dll,然后再框架的配置文件main.conf里面添加
ModuleString=PlayerPool.dll
行了,PurenessScopeServer框架启动就会加载PlayerPool模块,并把相关PlayerPool的消息给它。就这么简单,简单吧。
洋洋洒洒写了这么多,就是为了举个例子,当然,我希望你能够在我的例子上,加上你的想法,并把它改的更加高效,这才是进步。如果愿意,你也可以在这里分享给大家你的改进结果。
我一直认为,技术这种东西,是可以后天学习的。但是信仰,才会使我们走的更远。
学习就是这样,先走别人走过的路,然后根据自己的感悟和习惯,融合成属于自己的实现,这样的程序,才是优秀的程序,把你的思维和理解留在代码的字里行间,并让那些追逐梦想的后者,从中获益。只有敢于让别人踩在你的肩膀上,信念才会传承,而路也会越走越远,不是吗?
代码如下:
那么今天,我们就来点实际的代码,完成以上的所有功能吧。
按照昨天的思路,我需要两个程序,一个是和客户端通讯的程序,这个程序我们姑且认为它就是游戏服务器,那么,与之对应的,还有一个专门负责和后来存储介质通讯的服务进程。
既然要做这道菜,先看看我们需要点什么佐料。
(1)一个共享内存的类,这个类提供给我们与共享内存交互的功能,对外的接口需要,获得一个内存指针地址,获得当前已有的数据个数,删除其中一个数据,得到自由的内存块个数等等。
(2)我需要一个提供MRU算法的类,用于管理我的有效的共享内存指针,并提供相应的替换算法。
(3)对应我的开源服务器,我需要实现一个dll(或者so),来处理玩家创建,登陆,离开,更新,查询操作。
(4)我需要一个IO的类,用于如果我在内存中命中不到数据的时候,可以从IO里进行查找获得数据。
(5)一个定时执行的类,用于数据进程定时刷入IO接口。
好的,让我们开始把。
对于(1),我需要组织一个支持windows和linux共享内存的接口。要能自动根据操作系统的不同选用不同的方法,并实现一个模板类,完成对共享内存的管理,对于我而言,每个数据都是一个T*,当共享内存创建的时候,我需要指定一个T的个数,我会根据这个sizeof(T)*nCount来创建一整块巨大的内存,从里面切分出不同的T*,这样没有内存碎片。同时,还需要共享内存提供一个"头"的数据空间,用来标明每个T*的使用状态,当然,可能聪明的你已经发现,我使用的是sizrof(T),这样是不是有问题呢?因为对于玩家而言,我可能有玩家的数据,还可能有各种的数组,比如装备格子,技能格子等。这里我要强调一下,为了保证我对数据的统一管理和检查,我要求玩家数据必须是定长的,也就是说对于玩家数据vector,dueue,map等STL容器以及带缓冲的容器是不被允许的,因为变长会导致出错后内存查找的困难。当然,你在逻辑计算过程中,可以使用这个。但是元数据一定是需要定长的。如果你有兴趣,可以尝试在这里改造成变长的。
我的共享内存实现,你可以查看CSMAccessObject类和ShareMemoryAPI类,基础知识。
关键借助这两个类,我实现了一个CSMPool。
里面包含了这样一个数据头结构:
- //记录每个队列的数据容器
- struct _SMBlock
- {
- T* m_pT; //数据对象
- int m_nID; //数据当前编号
- bool m_blUse; //是否在使用true是正在使用,false是没有使用
- time_t m_ttUpdateTime; //DS服务器更新完成后回写的信息时间。
- _SMBlock()
- {
- m_pT = NULL;
- m_nID = 0;
- m_blUse = false;
- }
- };
这个结构就是一个完整的数据"头",这里我要解释一下,DS是啥,这个是我私自起的名字(DataServer服务进程的简称,就是我说的共享内存和IO同步所执行的进程)。在这里m_ttUpdateTime是由DS负责修改,当IO写入成功之后,需要更新这个变量,这样,只要比对t*中的时间戳和这个时间戳,我就知道哪些数据需要我更新到IO里面,因为很有可能,大部分数据在某时刻是不需要更新的。m_pT就是你的数据类,这个类实现是在PlayerObject.h里面,这里面有一个基类CObject是需要被填充的。而实际体class CPlayerData : public CObject
我先说说,CObject有什么关键性数据:
- //数据结构体的基类
- class CObject
- {
- public:
- CObject() { m_blWrite = false; m_ttUpdateTime = time(NULL); };
- virtual ~CObject() {};
- void EnterWrite() { m_blWrite = true;}
- void LeavelWrite() { m_ttUpdateTime = time(NULL); m_blWrite = false;}
- bool GetWriteSate() { return m_blWrite; }
- #define ENTERWRITE() EnterWrite(); //定义写入的宏
- #define LEAVELWEITE() LeavelWrite(); //定义写完的宏
- private:
- bool m_blWrite; //写标记
- public:
- time_t m_ttUpdateTime; //数据更新时间,DS服务器会更具这个时间来决定是否更新。
- };
这个类有一个更新写标记,这个写标记给DS使用的,当DS判断写标记正在写入的时候,就不会存储这些数据,等到下一次执行的时候在存储,同时当写标记完成的时候,基类自动更新m_ttUpdateTime这个时间戳,DS会比对_SMBlock.m_ttUpdateTime和T->m_ttUpdateTime的数值,看看需要不需要进行存储。继承这个类,当数据发生修改的时候,一定要套用写入宏,比如这样:
- void Create(const char* pPlayerName)
- {
- ENTERWRITE(); //标记写标记
- //你要做的事情在这里做
- sprintf_safe(m_szPlayerName, 50, "%s", pPlayerName);
- m_nPlayerID = 0;
- m_nLevel = 1;
- LEAVELWEITE(); //释放写标记
- };
这样就能最大程度的保证数据的完整性,尽量减少存入半截数据的风险。
我的CPlayerData类只是举一个例子,当然,你可以为这个类提供更多的变量和方法。根据你的需求而定。
好了,话说回来。我的CSMPool是个什么样子呢?
- class CSMPool
- {
- private:
- //记录每个队列的数据容器
- struct _SMBlock
- {
- };
- public:
- CSMPool()
- {
- };
- ~CSMPool()
- {
- };
- bool Init(SMKey key, int nMaxCount) //根据一个key打开或者新建指定的共享内存单元,并指定块数。
- {
- };
- void Close() //当共享内存需要关闭的时候需要做的一些事情。
- {
- }
- T* NewObject() //获得一个新的T*(CPlayerData指针)
- {
- };
- bool DeleteObject(T* pData) //删除一个没用的T*,这不是真的删除了共享内存,只是将此块内存指针归还给free指针列表。
- {
- };
- int GetFreeObjectCount() //得到共享内存池中可用的空闲内存块的个数
- {
- }
- int GetUsedObjectCount() //得到共享内存池中已有的内存块得个数
- {
- };
- T* GetUsedObject(int nIndex) //根据ID得到相应的内存块指针
- {
- };
- const time_t GetObjectHeadTimeStamp(T* pData) //得到_SMBlock时间戳
- {
- };
- bool SetObjectHeadTimeStamp(T* pData) //修改指定的_SMBlock时间戳,只有DS会干
- {
- }
(2)MRU算法
CMapTemplate类的实现,这部分代码我改进了当初我写的MRU代码文章,添加了几个函数和扩展了一些函数参数来满足我的需求。具体可以参考MapTemplate.h
(3)对应我的PruenessScopeServer框架,我只需要创建一个dll工程,引用一些头文件就可以完全不用框架代码了。具体引用的头文件在IObject目录里面,这样,我可以无视PruenessScopeServer是否存在,只要专心开发我的业务逻辑即可。
当然,规范还是要有的,具体看看我的PlayerPool.cpp,里面90%的代码写法都是固定的。可以和开源框架中的Base的dll工程比较一下,呵呵,唯一不同的就是,我这个类需要支持以下处理方法。
#define COMMAND_PLAYINSERT 0x1010 //用户数据创建
#define COMMAND_PLAYUPDATE 0x1011 //用户数据更新
#define COMMAND_PLAYDELETE 0x1012 //用户数据删除
#define COMMAND_PLAYSEACH 0x1013 //用户查询
#define COMMAND_PLAYLOGIN 0x1014 //用户登陆
#define COMMAND_PLAYLOGOFF 0x1015 //用户离开
客户端会给我以上的调用,那么,我们来根据以上的方法去实现代码吧。
对应以上需求,我定义了:
- CPlayerData* Do_PlayerInsert(const char* pPlayerNick);
- bool Do_PlayerUpdate(CPlayerData* pPlayerData);
- bool Do_PlayerDelete(const char* pPlayerNick);
- CPlayerData* Do_PlayerSearch(const char* pPlayerNick);
- CPlayerData* Do_PlayerLogin(const char* pPlayerNick);
- bool Do_PlayerLogOff(const char* pPlayerNick);
以上的方法来实现对这些命令的处理。具体方法可以参考PlayerPoolCommand.cpp的实现。这里就不多说了。
(4)对于共享内存和介质之间的操作。
为了举例,我不用数据库,使用文件来说明,假设我的文件就是我的数据源。当然,你可以用你的数据库引擎替代这里的实现。
- bool DeletePlayer(const char* pPlayerNick); //删除一个用户数据文件
- bool SavePlayer(CPlayerData* pPlayerData); //保存创建用户的数据
- CPlayerData* GetPlayer(const char* pPlayerNick); //这里在IO里面查找,找到了就new一个CPlayerData对象出来,返回给上层,由上层用完负责删除
这里你可以填充你的代码。
(5)这里因为我用的是ACE的框架,所以自然也就用ACE的定时器,比较手熟,当然,也可以用你自己喜欢的定时器替换。
- typedef ACE_Thread_Timer_Queue_Adapter<ACE_Timer_Heap> ActiveTimer;
- //定时器处理类(处理定时数据更新)
- class CTimeHeart : public ACE_Event_Handler
- {
- public:
- CTimeHeart();
- ~CTimeHeart();
- void Init();
- virtual int handle_timeout(const ACE_Time_Value &tv, const void *arg);
- private:
- CSMPool<CPlayerData> m_UserPool; //共享内存池
- CIOData m_IOData; //IO数据接口
- bool m_blRunState; //处理是否正在运行
- SMKey m_key; //共享内存Key
- };
- class CTimeManager
- {
- public:
- CTimeManager(void);
- ~CTimeManager(void);
- void Init();
- bool Start(int nTimeIntervel);
- void KillTimer();
- private:
- ActiveTimer m_ActiveTimer;
- CTimeHeart m_TimeHeart;
- int m_nTimerID;
- };
代码很简单,其实是我最喜欢的,因为越简单的代码,出错机会越低。
以上完整代码如下:我在window7+VS2005下测试通过,linux版本还没测试,等有时间我在上面编译一下,我相信会很顺利的。
好了,把PlayerPool编译一下,生成dll,然后再框架的配置文件main.conf里面添加
ModuleString=PlayerPool.dll
行了,PurenessScopeServer框架启动就会加载PlayerPool模块,并把相关PlayerPool的消息给它。就这么简单,简单吧。
洋洋洒洒写了这么多,就是为了举个例子,当然,我希望你能够在我的例子上,加上你的想法,并把它改的更加高效,这才是进步。如果愿意,你也可以在这里分享给大家你的改进结果。
我一直认为,技术这种东西,是可以后天学习的。但是信仰,才会使我们走的更远。
学习就是这样,先走别人走过的路,然后根据自己的感悟和习惯,融合成属于自己的实现,这样的程序,才是优秀的程序,把你的思维和理解留在代码的字里行间,并让那些追逐梦想的后者,从中获益。只有敢于让别人踩在你的肩膀上,信念才会传承,而路也会越走越远,不是吗?
代码如下:
- 多级缓冲的服务器数据服务机制实现(二)
- 多级缓冲的服务器数据服务机制实现(二)
- 多级缓冲的服务器数据服务机制实现(一)
- 多级缓冲的服务器数据服务机制实现(一)
- 多级缓冲的服务器数据服务机制实现(一)
- 环形数据缓冲的实现
- SurfacView的使用及数据缓冲机制
- 缓冲技术之二:缓冲池BufferPool的简单实现
- 多级缓冲体系的构建(基于PSS)
- 【C语言】【unix c】malloc的实现机制(缓冲机制)
- Qt双缓冲机制:实现一个简单的绘图工具(纯代码实现)
- Hibernate的缓冲机制
- Printf的缓冲机制
- 一个简单的读文件缓冲机制实现
- linux编程who命令的简单实现(缓冲机制)
- PHP的buffer缓冲机制实现静态页面
- Dubbo/Dubbox的服务暴露(二)-扩展点机制
- 多级listview的实现
- SQL Server系统函数
- 多级缓冲的服务器数据服务机制实现(一)
- 判断多Frame网页是否真正加载完毕(CHtmlView)
- 深入理解JavaScript系列
- 平衡二叉树的 插入 删除 查找 等功能c语言实现 数据结构
- 多级缓冲的服务器数据服务机制实现(二)
- 一个java正规表达式工具类
- 让人爱不释手的13套精美 Web 应用程序图标素材
- Tomcat启动时自动加载Servlet
- xp安装office2007提示找不到officelr.cab
- js变量的作用域测试
- 书香与女人
- 盘点Linux内核源码中使用宏定义的若干技巧(1)
- UVA 10025