找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 8468|回复: 3

共享内存实用研究(一)

[复制链接]
发表于 2010-4-21 13:14:54 | 显示全部楼层 |阅读模式
由于最近一段时间较忙,事情较多,一直没有时间发布新帖。因为以前做过一些游戏服务器的需求,所以希望在这里把自己的一些游戏服务器设计经验一点点共享出来,供大家参考,当然,对于我个人而言,我想借助这些文章,沉淀一下自己的知识,抽象一下以前的代码。当然,可能有的并不完善,欢迎大家提出自己的意见和想法。
由于有些代码是商业的,不能公开,所以我以我的理解重写了,或许有些问题的地方,还请各位指正。
以下代码我都会以附件的形式贴出来。这里只写一些主要的部分。
我觉得共享内存,在服务器应用中是很有用的一个东西,尤其在跨进程访问的时候,比如,我从数据库读取一些用户信息,预先放进共享内存,然后,另外的一个进程通过这个结构去访问这些数据。这样做的好处是,就算其中一个服务器宕机,只要另外的服务器还在运行,共享内存就会得以保存,当宕机的服务器重启之后,能还原到宕机那一时刻的数据。在网络游戏中应用尤其多,但是同样,共享内存是一个双刃剑,难处理的是跨进程的读写操作,容易造成一些领崩溃的问题。不过,这些困难,是可以通过设计减少它的发生概率的。
呵呵,废话少说了,先从最基本的开始吧。
由于windows和linux下的共享内存不太一样,所以写一个跨平台的共享内存模块是有一定意义的。于是,在这里,先写一个这样的东东吧。
windows的共享内存是指,
其实网上写的很多,不过还是自己写了一个,毕竟觉得自己写也不难,其实也没多少代码,关键在思路。
  1. namespace ShareMemoryAPI
  2. {
  3.         //创建一个ShareMemory对象指针
  4.         SMHandle CreateShareMemory(SMKey Key, int nSize);
  5.         //打开一个已经存在的ShareMemory对象指针
  6.         SMHandle OpenShareMemory(SMKey pKey, int nSize);
  7.         //得到指定的ShareMemory对象指针映射
  8.         char* MapShareMemory(SMHandle handle);
  9.         //关闭指定的ShareMemory对象指针映射
  10.         void UnMapShareMemory(char* pData);
  11.         void CloseSharememory(SMHandle handle);
  12. };
复制代码
首先封装一下简单的调用API,因为windows下用的句柄,而 linux下用的是一个序号。要想完全兼容有些麻烦,所以我设计了SMHandle对象和SMKey对象,用于兼容在windows和linux下的统一性。
其实看上面的API,就四个,不多,其实这么多也就够用了。在复杂的共享内存应用,大多也都是它们衍生的。
  1. //如果是windows,定义对象
  2. #ifdef _WIN32
  3.         #define INIT_HANDLE 0xFFFFFFFFFFFFFFFF
  4.         typedef HANDLE         SMHandle;
  5.         typedef int                 SMKey;
  6. #endif
  7. //如果是linux,定义对象
  8. #ifdef  _LINUX
  9.         typedef int     SMHandle;
  10.         typedef key_t SMKey;
  11. #endif
复制代码
这里,我约定了所有的ShareMemory的标记都是int,为了统一,当在windows的时候,我会把这个标记转化为char*。

然后,我建立了一个类,负责记录用户创建的ShareMemory对象。
  1. class CSMAccessObject
  2. {
  3. public:
  4.         CSMAccessObject(void);
  5.         ~CSMAccessObject(void);
  6.         //创建对象
  7.         bool Create(SMKey key, int nSize);
  8.         //删除对象
  9.         void Destory();
  10.         //检测对象是否存在,如果存在则不再创建,直接打开
  11.         bool Open(SMKey key, int nSize);
  12.         //得到指定长度偏移的指针位置
  13.         char* GetData(int nSize);
  14.         //得到此对象的总长度
  15.         int GetSize();
  16. private:
  17.         SMHandle m_SMHandle;
  18.         SMKey    m_Key;
  19.         int      m_nSize;
  20.         char*    m_pData;
  21. };
复制代码
这个对象的主要目的是,创建或者维护一个块指定长度的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的类,负责创建,打开,映射共享内存地址。
  1. #include "ShareMemoryAPI.h"
  2. class CSMAccessObject
  3. {
  4. public:
  5.         CSMAccessObject(void);
  6.         ~CSMAccessObject(void);
  7.         //创建对象,创建的内存为实际内存+头描述
  8.         bool Create(SMKey key, int nSize, int nHeadSize);
  9.         //检测对象是否存在,如果存在则不再创建,直接打开
  10.         bool Open(SMKey key, int nSize, int nHeadSize);
  11.         //删除对象
  12.         void Destory();
  13.         //得到指定长度偏移的指针位置
  14.         char* GetData(int nSize);
  15.         //得到头描述的位置
  16.         char* GetHeadData();
  17.         //得到此对象的总长度
  18.         int GetDataSize();
  19.         //得到此对象的头总长度
  20.         int GetHeadDataSize();
  21. private:
  22.         SMHandle m_SMHandle;
  23.         SMKey    m_Key;
  24.         int      m_nSize;
  25.         int      m_nHeadSize;
  26.         char*    m_pHeadData;
  27.         char*    m_pData;
  28. };
复制代码
这是一个指定的共享内存对象,这里做了一个m_pHeadData和m_pData两个指针对象,m_pHeadData用于记录此共享内存的内存分配信息(下面会说明这段内存存的是什么),而m_pData记载的是共享内存头的指针位置。比如,当我申请一个内存的时候,实际是m_nHeadSize + m_nSize。一个头信息块+要使用的内存大小。
有了这些,我就可以封装一个共享内存的模板了。因为大多数时候,我需要一些不同的共享内存对象,最简单的方法就是提供类似new和delete的支持功能,就像C++本身那样,可以随意的申请和"释放"(这里的释放不是真的释放,只是标记一下)。
本着这个原则,我创建了一个模板类。
  1. #ifndef _SMPOOL_H
  2. #define _SMPOOL_H
  3. #include "SMAccessObject.h"
  4. #include "Serial.h"
  5. #include <map>
  6. using namespace std;
  7. template<typename T>
  8. class CSMPool
  9. {
  10. public:
  11.         CSMPool()
  12.         {
  13.                 m_pSMAccessObject = NULL;
  14.         };
  15.         ~CSMPool()
  16.         {
  17.                 if(m_pSMAccessObject != NULL)
  18.                 {
  19.                         delete m_pSMAccessObject;
  20.                         m_pSMAccessObject = NULL;
  21.                 }
  22.         };
  23.         bool Init(SMKey key, int nMaxCount)
  24.         {
  25.                 bool blRet = false;
  26.                 //初始化序列化头
  27.                 m_Serial.Init(nMaxCount*sizeof(_SMBlock));
  28.                 m_pSMAccessObject = new CSMAccessObject();
  29.                 if(NULL == m_pSMAccessObject)
  30.                 {
  31.                         return false;
  32.                 }
  33.                 //首先尝试打开,看看是否存在已有,如果不存在,则创建新的。
  34.                 blRet = m_pSMAccessObject->Open(key, nMaxCount*sizeof(T), nMaxCount*sizeof(_SMBlock));
  35.                 if(false == blRet)
  36.                 {
  37.                         printf("[Init]Create.\n");
  38.                         //如果没有,则新建。
  39.                         blRet = m_pSMAccessObject->Create(key, nMaxCount*sizeof(T), nMaxCount*sizeof(_SMBlock));
  40.                         if(false == blRet)
  41.                         {
  42.                                 return false;
  43.                         }
  44.                         //开始划分共享内存空间
  45.                         m_mapSMBlock.clear();
  46.                         m_mapFreeSMBlock.clear();
  47.                         m_mapUsedSMBlock.clear();
  48.                         for(int i = 0; i < nMaxCount; i++)
  49.                         {
  50.                                 _SMBlock* pSMBlock = new _SMBlock();
  51.                                 if(pSMBlock == NULL)
  52.                                 {
  53.                                         return false;
  54.                                 }
  55.                                 pSMBlock->m_nID = i;
  56.                                 pSMBlock->m_pT  = reinterpret_cast<T*>(m_pSMAccessObject->GetData(i*sizeof(T)));
  57.                                 if(NULL == pSMBlock->m_pT)
  58.                                 {
  59.                                         return false;
  60.                                 }
  61.                                 m_mapSMBlock.insert(typename mapSMBlock::value_type(i, pSMBlock));
  62.                                 m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(i, pSMBlock));
  63.                                 m_Serial.Serial(pSMBlock, sizeof(_SMBlock));
  64.                         }
  65.                         m_nMaxCount = nMaxCount;
  66.                         //写入消息头
  67.                         WriteSMHead();
  68.                 }
  69.                 else
  70.                 {
  71.                         printf("[Init]Open.\n");
  72.                         //如果存在则读取
  73.                         char* pHeadData = m_pSMAccessObject->GetHeadData();
  74.                         if(NULL == pHeadData)
  75.                         {
  76.                                 return false;
  77.                         }
  78.                         //printf("[Init]Open pHeadData = 0x%08x.\n", pHeadData);
  79.                         //读取共享内存中关于数据的判定
  80.                         m_Serial.Serial(pHeadData, nMaxCount*sizeof(_SMBlock));
  81.                         //还原回vector对象
  82.                         m_mapSMBlock.clear();
  83.                         m_mapFreeSMBlock.clear();
  84.                         m_mapUsedSMBlock.clear();
  85.                         for(int i = 0; i < nMaxCount; i++)
  86.                         {
  87.                                 _SMBlock* pSMBlock = new _SMBlock();
  88.                                 if(pSMBlock == NULL)
  89.                                 {
  90.                                         return false;
  91.                                 }
  92.                                 m_Serial.GetData(pSMBlock, sizeof(_SMBlock));
  93.                                 pSMBlock->m_pT  = reinterpret_cast<T*>(m_pSMAccessObject->GetData(i*sizeof(T)));
  94.                                 m_mapSMBlock.insert(typename mapSMBlock::value_type(pSMBlock->m_nID, pSMBlock));
  95.                                 //分类
  96.                                 if(pSMBlock->m_blUse == true)
  97.                                 {
  98.                                         //放入正在使用的列表
  99.                                         m_mapUsedSMBlock.insert(typename mapUsedSMBlock::value_type(pSMBlock->m_pT, pSMBlock));
  100.                                 }
  101.                                 else
  102.                                 {
  103.                                         //放入没有使用的列表
  104.                                         m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(i, pSMBlock));
  105.                                 }
  106.                         }
  107.                         m_nMaxCount = nMaxCount;
  108.                 }
  109.                 return true;
  110.         };
  111.         void Close()
  112.         {
  113.                 typename mapSMBlock::iterator b = m_mapSMBlock.begin();
  114.                 typename mapSMBlock::iterator e = m_mapSMBlock.end();
  115.                 for(b; b != e; b++)
  116.                 {
  117.                         _SMBlock* pSMBlock = (_SMBlock* )b->second;
  118.                         if(pSMBlock != NULL)
  119.                         {
  120.                                 delete pSMBlock;
  121.                         }
  122.                 }
  123.                 m_mapSMBlock.clear();
  124.                 m_pSMAccessObject->Destory();
  125.         }
  126.         T* NewObject()
  127.         {
  128.                 //在m_mapSMBlock中查找一个空余的对象。
  129.                 if(m_mapFreeSMBlock.size() > 0)
  130.                 {
  131.                         typename mapFreeSMBlock::iterator b = m_mapFreeSMBlock.begin();
  132.                         _SMBlock* pSMBlock = (_SMBlock* )b->second;
  133.                         if(NULL == pSMBlock)
  134.                         {
  135.                                 return NULL;
  136.                         }
  137.                         else
  138.                         {
  139.                                 m_mapFreeSMBlock.erase(b);
  140.                                 pSMBlock->m_blUse = true;
  141.                                 m_mapUsedSMBlock.insert(typename mapUsedSMBlock::value_type(pSMBlock->m_pT, pSMBlock));
  142.                                 m_Serial.WriteData(pSMBlock->m_nID*sizeof(_SMBlock), pSMBlock, sizeof(_SMBlock));
  143.                                 //写入消息头
  144.                                 WriteSMHead();
  145.                                 return pSMBlock->m_pT;
  146.                         }
  147.                 }
  148.                 else
  149.                 {
  150.                         return NULL;
  151.                 }
  152.         };
  153.         bool DeleteObject(T* pData)
  154.         {
  155.                 typename mapUsedSMBlock::iterator f = m_mapUsedSMBlock.find(pData);
  156.                 if(f == m_mapUsedSMBlock.end())
  157.                 {
  158.                         return false;
  159.                 }
  160.                 else
  161.                 {
  162.                         _SMBlock* pSMBlock = (_SMBlock* )f->second;
  163.                         m_mapUsedSMBlock.erase(f);
  164.                         pSMBlock->m_blUse = false;
  165.                         m_mapFreeSMBlock.insert(typename mapFreeSMBlock::value_type(pSMBlock->m_nID, pSMBlock));
  166.                         m_Serial.WriteData(pSMBlock->m_nID*sizeof(_SMBlock), pSMBlock, sizeof(_SMBlock));
  167.                         //写入消息头
  168.                         WriteSMHead();
  169.                         return true;
  170.                 }
  171.         };
  172.         int GetFreeObjectCount()
  173.         {
  174.                 return (int)m_mapFreeSMBlock.size();
  175.         }
  176.         int GetUsedObjectCount()
  177.         {
  178.                 return (int)m_mapUsedSMBlock.size();
  179.         };
  180.         T* GetUsedObject(int nIndex)
  181.         {
  182.                 if(nIndex >= (int)m_mapUsedSMBlock.size())
  183.                 {
  184.                         return NULL;
  185.                 }
  186.                 typename mapUsedSMBlock::iterator b = m_mapUsedSMBlock.begin();
  187.                 typename mapUsedSMBlock::iterator e = m_mapUsedSMBlock.end();
  188.                 int nPos = 0;
  189.                 for(b; b != e; b++)
  190.                 {
  191.                         if(nPos == nIndex)
  192.                         {
  193.                                 _SMBlock* pSMBlock = (_SMBlock* )b->second;
  194.                                 if(NULL != pSMBlock)
  195.                                 {
  196.                                         return pSMBlock->m_pT;
  197.                                 }
  198.                                 else
  199.                                 {
  200.                                         return NULL;
  201.                                 }
  202.                         }
  203.                         else
  204.                         {
  205.                                 if(nPos > nIndex)
  206.                                 {
  207.                                         return NULL;
  208.                                 }
  209.                                 nPos++;
  210.                         }
  211.                 }
  212.         }
  213. private:
  214.         void WriteSMHead()
  215.         {
  216.                 char* pHeadData = m_pSMAccessObject->GetHeadData();
  217.                 if(NULL == pHeadData)
  218.                 {
  219.                         return;
  220.                 }
  221.                 //将头信息写入共享内存头
  222.                 memcpy(pHeadData, m_Serial.GetBase(), m_nMaxCount*sizeof(_SMBlock));
  223.         };
  224. private:
  225.         //记录每个队列的数据容器
  226.         struct _SMBlock
  227.         {
  228.                 T*   m_pT;
  229.                 int  m_nID;
  230.                 bool m_blUse;
  231.                 _SMBlock()
  232.                 {
  233.                         m_pT    = NULL;
  234.                         m_nID   = 0;
  235.                         m_blUse = false;
  236.                 }
  237.         };
  238.         typedef map<int, _SMBlock*> mapSMBlock;
  239.         typedef map<T*, _SMBlock*>  mapUsedSMBlock;
  240.         typedef map<int, _SMBlock*> mapFreeSMBlock;
  241. private:
  242.         CSMAccessObject* m_pSMAccessObject;
  243.         mapSMBlock       m_mapSMBlock;
  244.         mapUsedSMBlock   m_mapUsedSMBlock;
  245.         mapFreeSMBlock   m_mapFreeSMBlock;
  246.         CSerial          m_Serial;
  247.         int              m_nMaxCount;
  248. };
  249. #endif
复制代码
代码有点多,实际多的那部分,是关于头标记的部分。
因为很多时候,程序会因为各种原因而崩溃。而再次重启的时候,可能有些共享内存对象已经被分配出去了。这时候如果我再new出来,可能就会覆盖原有的数据,所以我把所有的内存块都存成一个数据结构,放在共享内存的头里面,当下次程序启动,我会预读这个头,并还原内存使用列表,最大限度的保证数据获取与写入的正确。
使用的话么,呵呵,其实很简单。
  1. struct _Data   //一个测试的对象
  2. {
  3.         int  m_nData;
  4.         char m_szData[20];
  5.         _Data()
  6.         {
  7.                 m_nData     = 0;
  8.                 m_szData[0] = '\0';
  9.         }
  10. };
  11. #ifdef _WIN32
  12.         int _tmain(int argc, _TCHAR* argv[])
  13. #else
  14.   int main(int argc, char* argv[])
  15. #endif
  16. {
  17.         CSMPool<_Data> UserPool;   
  18.         SMKey key;
  19.        
  20. #ifdef _WIN32       
  21.         key = 1111;
  22. #else
  23.     key = ftok("/home/freeeyes/SMData/", 0);
  24. #endif
  25.         printf("[Test]%d.\n", key);
  26.         UserPool.Init(key, 100);
  27.         _Data* pData1 = UserPool.NewObject();
  28.         if(NULL == pData1)
  29.         {
  30.                 printf("[main]Get Data1 NULL.\n");
  31.         }
  32.         else
  33.         {
  34.                 pData1->m_nData = 1;
  35.                 sprintf(pData1->m_szData, "shiqiang");
  36.                 printf("[main]Free Count = %d.\n", UserPool.GetFreeObjectCount());
  37.                 printf("[main]Get Data1 OK[%d] - [%s].\n", pData1->m_nData, pData1->m_szData);
  38.         }
  39. }
复制代码
呵呵,一个很简单使用共享内存的例子。
其实,要想真正把共享内存做的更完美一些,需要一些手段,下一讲我将着重介绍如何使用共享内存的一些简单策略,比如如何防止不同的进程同时写。数据的合理化规划等等,希望抛砖引玉,大家需要用到的时候,这篇文章可以给你帮助。
以上代码在VS2005下和64位linux运行测试通过。(可以修改我的Make文件,一般直接make即可编译)

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?用户注册

×
发表于 2011-12-14 20:14:06 | 显示全部楼层
在没人回复呢,讲解的多好啊。
发表于 2012-1-12 14:33:24 | 显示全部楼层
特来学习~~
发表于 2012-1-27 19:24:08 | 显示全部楼层
特来学习~~ 很好,谢谢! , 期待下一节
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-11-22 07:29 , Processed in 0.018247 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表