|
由于最近一段时间较忙,事情较多,一直没有时间发布新帖。因为以前做过一些游戏服务器的需求,所以希望在这里把自己的一些游戏服务器设计经验一点点共享出来,供大家参考,当然,对于我个人而言,我想借助这些文章,沉淀一下自己的知识,抽象一下以前的代码。当然,可能有的并不完善,欢迎大家提出自己的意见和想法。
由于有些代码是商业的,不能公开,所以我以我的理解重写了,或许有些问题的地方,还请各位指正。
以下代码我都会以附件的形式贴出来。这里只写一些主要的部分。
我觉得共享内存,在服务器应用中是很有用的一个东西,尤其在跨进程访问的时候,比如,我从数据库读取一些用户信息,预先放进共享内存,然后,另外的一个进程通过这个结构去访问这些数据。这样做的好处是,就算其中一个服务器宕机,只要另外的服务器还在运行,共享内存就会得以保存,当宕机的服务器重启之后,能还原到宕机那一时刻的数据。在网络游戏中应用尤其多,但是同样,共享内存是一个双刃剑,难处理的是跨进程的读写操作,容易造成一些领崩溃的问题。不过,这些困难,是可以通过设计减少它的发生概率的。
呵呵,废话少说了,先从最基本的开始吧。
由于windows和linux下的共享内存不太一样,所以写一个跨平台的共享内存模块是有一定意义的。于是,在这里,先写一个这样的东东吧。
windows的共享内存是指,
其实网上写的很多,不过还是自己写了一个,毕竟觉得自己写也不难,其实也没多少代码,关键在思路。
- namespace ShareMemoryAPI
- {
- //创建一个ShareMemory对象指针
- SMHandle CreateShareMemory(SMKey Key, int nSize);
- //打开一个已经存在的ShareMemory对象指针
- SMHandle OpenShareMemory(SMKey pKey, int nSize);
- //得到指定的ShareMemory对象指针映射
- char* MapShareMemory(SMHandle handle);
- //关闭指定的ShareMemory对象指针映射
- void UnMapShareMemory(char* pData);
- void CloseSharememory(SMHandle handle);
- };
复制代码 首先封装一下简单的调用API,因为windows下用的句柄,而 linux下用的是一个序号。要想完全兼容有些麻烦,所以我设计了SMHandle对象和SMKey对象,用于兼容在windows和linux下的统一性。
其实看上面的API,就四个,不多,其实这么多也就够用了。在复杂的共享内存应用,大多也都是它们衍生的。- //如果是windows,定义对象
- #ifdef _WIN32
- #define INIT_HANDLE 0xFFFFFFFFFFFFFFFF
- typedef HANDLE SMHandle;
- typedef int SMKey;
- #endif
- //如果是linux,定义对象
- #ifdef _LINUX
- typedef int SMHandle;
- typedef key_t SMKey;
- #endif
复制代码 这里,我约定了所有的ShareMemory的标记都是int,为了统一,当在windows的时候,我会把这个标记转化为char*。
然后,我建立了一个类,负责记录用户创建的ShareMemory对象。- class CSMAccessObject
- {
- public:
- CSMAccessObject(void);
- ~CSMAccessObject(void);
- //创建对象
- bool Create(SMKey key, int nSize);
- //删除对象
- void Destory();
- //检测对象是否存在,如果存在则不再创建,直接打开
- bool Open(SMKey key, int nSize);
- //得到指定长度偏移的指针位置
- char* GetData(int nSize);
- //得到此对象的总长度
- int GetSize();
- private:
- SMHandle m_SMHandle;
- SMKey m_Key;
- int m_nSize;
- char* m_pData;
- };
复制代码 这个对象的主要目的是,创建或者维护一个块指定长度的CSMAccessObject对象。 这里的方法都是用ShareMemoryAPI下的命令处理的。
这里要说明的是在windows下,SMHandle的类型是Handle类型,一个句柄指针。而在linux下,是int类型,一个位置。当然,在64位的系统下,这个int要注意它的实际长度。而key关键字就比较麻烦了,在wondiws下,key可以是一个char*,一个字符串,而在linux下,这个key是一个key_t类型(实际是一个int类型),而这个key必须通过匹配获得。这个匹配是指定一个linux的共享内存路径,比如ftok("/home/freeeyes/src/SMData/", 0);这将会返回一个key类型。如果返回的是-1表示获取唯一ID失败,这和windows返回句柄为NULL(0)是不同的。
windows的共享内存机制是,当有一个程序在使用它的时候,它的计数器就会+1,当有进程离开的时候,计数器-1,当计数器为0的时候,系统释放共享内存对象。而在linux下是当共享内存建立,就会一直存在,直到有一个进程给它释放关闭的命令,它才会释放。
有了这些API,在上面我手动封装了一个AccessObject的类,负责创建,打开,映射共享内存地址。- #include "ShareMemoryAPI.h"
- class CSMAccessObject
- {
- public:
- CSMAccessObject(void);
- ~CSMAccessObject(void);
- //创建对象,创建的内存为实际内存+头描述
- bool Create(SMKey key, int nSize, int nHeadSize);
- //检测对象是否存在,如果存在则不再创建,直接打开
- bool Open(SMKey key, int nSize, int nHeadSize);
- //删除对象
- void Destory();
- //得到指定长度偏移的指针位置
- char* GetData(int nSize);
- //得到头描述的位置
- char* GetHeadData();
- //得到此对象的总长度
- int GetDataSize();
- //得到此对象的头总长度
- int GetHeadDataSize();
- private:
- SMHandle m_SMHandle;
- SMKey m_Key;
- int m_nSize;
- int m_nHeadSize;
- char* m_pHeadData;
- char* m_pData;
- };
复制代码 这是一个指定的共享内存对象,这里做了一个m_pHeadData和m_pData两个指针对象,m_pHeadData用于记录此共享内存的内存分配信息(下面会说明这段内存存的是什么),而m_pData记载的是共享内存头的指针位置。比如,当我申请一个内存的时候,实际是m_nHeadSize + m_nSize。一个头信息块+要使用的内存大小。
有了这些,我就可以封装一个共享内存的模板了。因为大多数时候,我需要一些不同的共享内存对象,最简单的方法就是提供类似new和delete的支持功能,就像C++本身那样,可以随意的申请和"释放"(这里的释放不是真的释放,只是标记一下)。
本着这个原则,我创建了一个模板类。- #ifndef _SMPOOL_H
- #define _SMPOOL_H
- #include "SMAccessObject.h"
- #include "Serial.h"
- #include <map>
- using namespace std;
- template<typename T>
- class CSMPool
- {
- public:
- CSMPool()
- {
- m_pSMAccessObject = NULL;
- };
- ~CSMPool()
- {
- if(m_pSMAccessObject != NULL)
- {
- delete m_pSMAccessObject;
- m_pSMAccessObject = NULL;
- }
- };
- bool Init(SMKey key, int nMaxCount)
- {
- bool blRet = false;
- //初始化序列化头
- m_Serial.Init(nMaxCount*sizeof(_SMBlock));
- m_pSMAccessObject = new CSMAccessObject();
- if(NULL == m_pSMAccessObject)
- {
- return false;
- }
- //首先尝试打开,看看是否存在已有,如果不存在,则创建新的。
- blRet = m_pSMAccessObject->Open(key, nMaxCount*sizeof(T), nMaxCount*sizeof(_SMBlock));
- if(false == blRet)
- {
- printf("[Init]Create.\n");
- //如果没有,则新建。
- blRet = m_pSMAccessObject->Create(key, nMaxCount*sizeof(T), nMaxCount*sizeof(_SMBlock));
- if(false == blRet)
- {
- return false;
- }
- //开始划分共享内存空间
- m_mapSMBlock.clear();
- m_mapFreeSMBlock.clear();
- m_mapUsedSMBlock.clear();
- for(int i = 0; i < nMaxCount; i++)
- {
- _SMBlock* pSMBlock = new _SMBlock();
- if(pSMBlock == NULL)
- {
- return false;
- }
- pSMBlock->m_nID = i;
- pSMBlock->m_pT = reinterpret_cast<T*>(m_pSMAccessObject->GetData(i*sizeof(T)));
- if(NULL == pSMBlock->m_pT)
- {
- return false;
- }
- m_mapSMBlock.insert(typename mapSMBlock::value_type(i, pSMBlock));
- m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(i, pSMBlock));
- m_Serial.Serial(pSMBlock, sizeof(_SMBlock));
- }
- m_nMaxCount = nMaxCount;
- //写入消息头
- WriteSMHead();
- }
- else
- {
- printf("[Init]Open.\n");
- //如果存在则读取
- char* pHeadData = m_pSMAccessObject->GetHeadData();
- if(NULL == pHeadData)
- {
- return false;
- }
- //printf("[Init]Open pHeadData = 0x%08x.\n", pHeadData);
- //读取共享内存中关于数据的判定
- m_Serial.Serial(pHeadData, nMaxCount*sizeof(_SMBlock));
- //还原回vector对象
- m_mapSMBlock.clear();
- m_mapFreeSMBlock.clear();
- m_mapUsedSMBlock.clear();
- for(int i = 0; i < nMaxCount; i++)
- {
- _SMBlock* pSMBlock = new _SMBlock();
- if(pSMBlock == NULL)
- {
- return false;
- }
- m_Serial.GetData(pSMBlock, sizeof(_SMBlock));
- pSMBlock->m_pT = reinterpret_cast<T*>(m_pSMAccessObject->GetData(i*sizeof(T)));
- m_mapSMBlock.insert(typename mapSMBlock::value_type(pSMBlock->m_nID, pSMBlock));
- //分类
- if(pSMBlock->m_blUse == true)
- {
- //放入正在使用的列表
- m_mapUsedSMBlock.insert(typename mapUsedSMBlock::value_type(pSMBlock->m_pT, pSMBlock));
- }
- else
- {
- //放入没有使用的列表
- m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(i, pSMBlock));
- }
- }
- m_nMaxCount = nMaxCount;
- }
- return true;
- };
- void Close()
- {
- typename mapSMBlock::iterator b = m_mapSMBlock.begin();
- typename mapSMBlock::iterator e = m_mapSMBlock.end();
- for(b; b != e; b++)
- {
- _SMBlock* pSMBlock = (_SMBlock* )b->second;
- if(pSMBlock != NULL)
- {
- delete pSMBlock;
- }
- }
- m_mapSMBlock.clear();
- m_pSMAccessObject->Destory();
- }
- T* NewObject()
- {
- //在m_mapSMBlock中查找一个空余的对象。
- if(m_mapFreeSMBlock.size() > 0)
- {
- typename mapFreeSMBlock::iterator b = m_mapFreeSMBlock.begin();
- _SMBlock* pSMBlock = (_SMBlock* )b->second;
- if(NULL == pSMBlock)
- {
- return NULL;
- }
- else
- {
- m_mapFreeSMBlock.erase(b);
- pSMBlock->m_blUse = true;
- m_mapUsedSMBlock.insert(typename mapUsedSMBlock::value_type(pSMBlock->m_pT, pSMBlock));
- m_Serial.WriteData(pSMBlock->m_nID*sizeof(_SMBlock), pSMBlock, sizeof(_SMBlock));
- //写入消息头
- WriteSMHead();
- return pSMBlock->m_pT;
- }
- }
- else
- {
- return NULL;
- }
- };
- bool DeleteObject(T* pData)
- {
- typename mapUsedSMBlock::iterator f = m_mapUsedSMBlock.find(pData);
- if(f == m_mapUsedSMBlock.end())
- {
- return false;
- }
- else
- {
- _SMBlock* pSMBlock = (_SMBlock* )f->second;
- m_mapUsedSMBlock.erase(f);
- pSMBlock->m_blUse = false;
- m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(pSMBlock->m_nID, pSMBlock));
- m_Serial.WriteData(pSMBlock->m_nID*sizeof(_SMBlock), pSMBlock, sizeof(_SMBlock));
- //写入消息头
- WriteSMHead();
- return true;
- }
- };
- int GetFreeObjectCount()
- {
- return (int)m_mapFreeSMBlock.size();
- }
- int GetUsedObjectCount()
- {
- return (int)m_mapUsedSMBlock.size();
- };
- T* GetUsedObject(int nIndex)
- {
- if(nIndex >= (int)m_mapUsedSMBlock.size())
- {
- return NULL;
- }
- typename mapUsedSMBlock::iterator b = m_mapUsedSMBlock.begin();
- typename mapUsedSMBlock::iterator e = m_mapUsedSMBlock.end();
- int nPos = 0;
- for(b; b != e; b++)
- {
- if(nPos == nIndex)
- {
- _SMBlock* pSMBlock = (_SMBlock* )b->second;
- if(NULL != pSMBlock)
- {
- return pSMBlock->m_pT;
- }
- else
- {
- return NULL;
- }
- }
- else
- {
- if(nPos > nIndex)
- {
- return NULL;
- }
- nPos++;
- }
- }
- }
- private:
- void WriteSMHead()
- {
- char* pHeadData = m_pSMAccessObject->GetHeadData();
- if(NULL == pHeadData)
- {
- return;
- }
- //将头信息写入共享内存头
- memcpy(pHeadData, m_Serial.GetBase(), m_nMaxCount*sizeof(_SMBlock));
- };
- private:
- //记录每个队列的数据容器
- struct _SMBlock
- {
- T* m_pT;
- int m_nID;
- bool m_blUse;
- _SMBlock()
- {
- m_pT = NULL;
- m_nID = 0;
- m_blUse = false;
- }
- };
- typedef map<int, _SMBlock*> mapSMBlock;
- typedef map<T*, _SMBlock*> mapUsedSMBlock;
- typedef map<int, _SMBlock*> mapFreeSMBlock;
- private:
- CSMAccessObject* m_pSMAccessObject;
- mapSMBlock m_mapSMBlock;
- mapUsedSMBlock m_mapUsedSMBlock;
- mapFreeSMBlock m_mapFreeSMBlock;
- CSerial m_Serial;
- int m_nMaxCount;
- };
- #endif
复制代码 代码有点多,实际多的那部分,是关于头标记的部分。
因为很多时候,程序会因为各种原因而崩溃。而再次重启的时候,可能有些共享内存对象已经被分配出去了。这时候如果我再new出来,可能就会覆盖原有的数据,所以我把所有的内存块都存成一个数据结构,放在共享内存的头里面,当下次程序启动,我会预读这个头,并还原内存使用列表,最大限度的保证数据获取与写入的正确。
使用的话么,呵呵,其实很简单。- struct _Data //一个测试的对象
- {
- int m_nData;
- char m_szData[20];
- _Data()
- {
- m_nData = 0;
- m_szData[0] = '\0';
- }
- };
- #ifdef _WIN32
- int _tmain(int argc, _TCHAR* argv[])
- #else
- int main(int argc, char* argv[])
- #endif
- {
- CSMPool<_Data> UserPool;
- SMKey key;
-
- #ifdef _WIN32
- key = 1111;
- #else
- key = ftok("/home/freeeyes/SMData/", 0);
- #endif
- printf("[Test]%d.\n", key);
- UserPool.Init(key, 100);
- _Data* pData1 = UserPool.NewObject();
- if(NULL == pData1)
- {
- printf("[main]Get Data1 NULL.\n");
- }
- else
- {
- pData1->m_nData = 1;
- sprintf(pData1->m_szData, "shiqiang");
- printf("[main]Free Count = %d.\n", UserPool.GetFreeObjectCount());
- printf("[main]Get Data1 OK[%d] - [%s].\n", pData1->m_nData, pData1->m_szData);
- }
- }
复制代码 呵呵,一个很简单使用共享内存的例子。
其实,要想真正把共享内存做的更完美一些,需要一些手段,下一讲我将着重介绍如何使用共享内存的一些简单策略,比如如何防止不同的进程同时写。数据的合理化规划等等,希望抛砖引玉,大家需要用到的时候,这篇文章可以给你帮助。
以上代码在VS2005下和64位linux运行测试通过。(可以修改我的Make文件,一般直接make即可编译) |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?用户注册
×
|