只需一步,快速开始
PutMessage()方法就是我进入这个小世界的地方,这个小世界就像一个盒子,盒子上有一个口,可以往里面按顺序放东西,这就是这个入口。我可以把消息放入里面,驱动这个小世界运转,很酷吧。
当然ProcessMessage(),这个就是消息处理的地方,进入队列的消息,将在这里得到处理。那么,我们管它叫什么呢?CSenceManager好了,场景管理器。呵呵,聪明的你肯定会想,如果我有多个场景呢,那么一样,你只要多建立几个这样的场景类就可以的。这里为了测试我说的性能,我追加了m_u4ProCount和m_u4TimeCost来记录我的这个小世界的时间消耗,呵呵。为了提高性能,所有的消息在被创造的时候,都是从消息池里面拿出来的,这样减少了new和delete造成的时间消耗。m_objLuaFn就是我定义的Lua引擎,提供我这个小世界事件的启动,比如进入房间,离开房间,出牌等等。对应用户操作,该交给Lua处理的,就让它去处理吧。接下来,让我们看看,在这个小盒子里,我们如何切割若干个小房间呢?在设计房间的时候,我们必须有一个房间管理器,房间管理器中有一个房间,房间里面包含自己的属性,游戏的属性(德克萨斯扑克信息),以及玩家的属性。那么先看看,我们的玩家属性有什么//玩家信息struct _Player{ uint32 m_u4PlayerID; //玩家ID char m_szPlayerNick[MAX_BUFF_20]; //玩家昵称 uint32 m_u4Money; //玩家金钱 uint32 m_objCard[PLAYER_CODE_SIZE]; //玩家手上的牌 uint32 m_u4CardCount; //当前拥有牌的个数 uint32 m_u4TimerID; //当前玩家身上的定时器 _TimerInfo* m_pTimerInfo; //当前的玩家定时器附加信息 ACE_Time_Value m_tvUpdate; //当前玩家更新最后时间 _Player() { m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } void Clear() { m_u4PlayerID = PLAYER_INVALID_ID; m_u4Money = 0; m_u4CardCount = 0; m_szPlayerNick[0] = '\0'; m_tvUpdate = ACE_OS::gettimeofday(); for(int i = 0; i < PLAYER_CODE_SIZE; i++) { m_objCard[i] = CARD_INVALID_ID; } m_u4CardCount = 0; if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Player::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; }};复制代码上面的注释我想写的应该比较清楚了,呵呵,对于玩家,我需要一些列的属性,当然,你觉得不够的话,你可以添加。那么,Room是什么样的呢?//房间结构体struct _Room{ uint32 m_u4RoomID; //房间ID char m_szRoomName[MAX_BUFF_20]; //房间名称 char m_szRoomDesc[MAX_BUFF_200]; //房间信息描述 _Player m_objPlayer[ROOM_PLAYER_SIZE]; //最大房间内的玩家 _Player m_objVisitPlayer[ROOM_PLAYER_VISITOR_SIZE]; //最大房间参观者玩家 bool m_blState; //是否开放,true开放,false不开放 uint32 m_u4State; //房间状态,0为准备开始,1为已经开始 uint32 m_u4PlayerCount; //玩家个数 uint32 m_u4VisitorCount; //参观玩家个数 uint32 m_u4TimerID; //房间定时器ID _TimerInfo* m_pTimerInfo; //房间定时器的附加参数 ACE_Time_Value m_tvUpdate; //当前房间更新最后时间 _GameInfo m_objGameInfo; //房间游戏相关信息 _Room() { m_u4RoomID = ROOM_ERROR_NOEXIST; m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } //清理房间 void Clear() { for(int i = 0; i < ROOM_PLAYER_SIZE; i++) { m_objPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } for(int i = 0; i < ROOM_PLAYER_VISITOR_SIZE; i++) { m_objVisitPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Room::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; m_u4PlayerCount = 0; m_u4VisitorCount = 0; m_blState = true; m_u4State = 0; m_tvUpdate = ACE_OS::gettimeofday(); }};复制代码同理,和玩家一样过,在这里,我使用了数组而不是STL去解决用户个数的问题,原因是,我觉得在极少的个数面前,使用STL的容器可能显得比较臃肿,而且,我需要达成的是,内存是可控的。我可以控制我的内存大小。那么接下来,我们创建一个房间的管理器吧。struct _RoomManager{ _Room m_objRoom[ROOM_COUNT]; _RoomManager() { for(int i = 0; i < ROOM_COUNT; i++) { m_objRoom[i].m_u4RoomID = i; } }复制代码"。。。"是房间管理器的各种调用方法。提供给外层使用,代码太多了,就不贴在这里了,这里我只是简单的介绍一下架构。那么接下来,你可能会问,那么,用户登录登出,我怎么能把数据源和游戏服务器隔离呢?问的好,如果你能想到这个,说明你已经看懂了一大半我讲的东西了。那么,我们就来说说共享内存吧。我以前在PSS里面提供了一个共享内存跨进程访问的例子,可惜,那个例子可能看的不是很明白。在这里,我正式整合到我的这个德克萨斯扑克中去吧。好的,那么,首先,我们来想想,如何隔离数据源的访问呢?如果我有一个进程,提供数据的读取与写入,将从数据源获得的数据,写入一个公共的地方,当有用户访问的时候,我提供给这个进程一个消息,说我要获得一个指定的数据,这个进程先帮我在共享内存中寻找,如果有直接返回给我一个位置。如果没有,则从数据源填充到公共的地方,然后返回给我一个位置。就和看电影一样,电影院在门口买票,人们拿着票走到指定的座位上,这时候已经坐下看电影的人,不必等着拿票进来找坐位的人,而是可以直接观赏电影。这岂不是很美?恩,那么,我们需要一个这样的模式,来解决我们的数据问题。看吧,其实很简单,你只要造一个虚拟的电影院就可以了。那么,什么是共享内存呢?如果你刚刚接触这个,可能会对此有点晕,没关系,你可以在这里找到我关于共享内存的帖子,好好看一下。我这里使用的共享内存引擎,就是我以前帖子里的。那么,在消费者(电影院厅)进程中,我们只需要提供这样的类功能就可以了typedef map<uint32, _UserPlayer*> mapOnlineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key};#endif复制代码只有一个方法,那就是GetUserPlayer()顾名思义,就是,我有一张电影票,我拿着这个票能找到固定的座位。这个座位就是我的。那么,数据源维护进程是怎么做的呢?typedef map<string, _UserPlayer*> mapOnlineUserPlayer;typedef map<string, _UserPlayer*> mapOfflineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 bool ReadDBData(); //从数据库中填充玩家信息 int32 ReadDBData(const char* pPlayerNick); //从玩家昵称获得玩家信息 int32 ReadPlayerSMPos(const char* pPlayerNick); //获得玩家当前内存地址的偏移位置 bool LeaveDBData(const char* pPlayerNick); //玩家离开 bool WriteDBData(const char* pPlayerNick); //写入数据源玩家的数据 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针 void ReadSMPool(); //还原数据在线,离开和可以删除的数据列表private: uint32 GetRandNumber(); //获得一个随机数 bool DeleteUserPlayer(); //删除最后一个应该删除链表的指针 _UserPlayer* FindPlayerLeave(const char* pPlayerNick); //在离线列表中寻找是否已有数据 _UserPlayer* FindPlayerDelete(const char* pPlayerNick); //在可删除列表中寻找是否已有数据private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key mapOnlineUserPlayer m_mapOnlineUserPlayer; //在线玩家表 mapOfflineUserPlayer m_mapLeaveUserPlayer; //已经离开玩家表 mapOfflineUserPlayer m_mapDeleteUserPlayer; //可以删除的玩家表(用于高效查找) listDeleteUserPlayer m_listDeleteUserPlayer; //可以删除的玩家表(用于淘汰算法)};#endif复制代码呵呵,这里提供的方法比较多,可能看着有些头晕,但是实际上,你只需要看Public提供的方法就行了,那些Private方法提供的是为了LRU而设计的,保持内存的高效性,也就是说,如果用户退出了,且我已经会写成功了数据源,我并不会删除这个用户的数据,如果它下次登录上来,我会现在内存中查找,如果有就直接返回之。(在Linux的release下,每秒每个链接3000次访问稳定输出)如果共享内存满了,我会淘汰最后最不常用的回写成功的数据。这样尽量做到高效。我做的例子很简单,通过用户昵称去获得数据,当然,数据源我没有使用数据库,为了保证样例的完整性,我这里使用了一个简单的随机算法。当然,你有兴趣的话,完全可以替换我这块变成你的数据源访问代码。呵呵,注释写的很清楚了吧。有了这两样,我们就可以很轻松的分布数据请求了。也为下一步我们的动作做好了充足的准备。写到这里,我个人用了差不多2天的时间,我想聪明的你一定能够从中找到一些自己可以借鉴的东西。好了,下一章,我会讲,如何把以上这些应用串联起来。呵呵,让他们统一行动。
当然ProcessMessage(),这个就是消息处理的地方,进入队列的消息,将在这里得到处理。
那么,我们管它叫什么呢?CSenceManager好了,场景管理器。
呵呵,聪明的你肯定会想,如果我有多个场景呢,那么一样,你只要多建立几个这样的场景类就可以的。
这里为了测试我说的性能,我追加了m_u4ProCount和m_u4TimeCost来记录我的这个小世界的时间消耗,呵呵。
为了提高性能,所有的消息在被创造的时候,都是从消息池里面拿出来的,这样减少了new和delete造成的时间消耗。
m_objLuaFn就是我定义的Lua引擎,提供我这个小世界事件的启动,比如进入房间,离开房间,出牌等等。对应用户操作,该交给Lua处理的,就让它去处理吧。接下来,让我们看看,在这个小盒子里,我们如何切割若干个小房间呢?在设计房间的时候,我们必须有一个房间管理器,房间管理器中有一个房间,房间里面包含自己的属性,游戏的属性(德克萨斯扑克信息),以及玩家的属性。那么先看看,我们的玩家属性有什么//玩家信息struct _Player{ uint32 m_u4PlayerID; //玩家ID char m_szPlayerNick[MAX_BUFF_20]; //玩家昵称 uint32 m_u4Money; //玩家金钱 uint32 m_objCard[PLAYER_CODE_SIZE]; //玩家手上的牌 uint32 m_u4CardCount; //当前拥有牌的个数 uint32 m_u4TimerID; //当前玩家身上的定时器 _TimerInfo* m_pTimerInfo; //当前的玩家定时器附加信息 ACE_Time_Value m_tvUpdate; //当前玩家更新最后时间 _Player() { m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } void Clear() { m_u4PlayerID = PLAYER_INVALID_ID; m_u4Money = 0; m_u4CardCount = 0; m_szPlayerNick[0] = '\0'; m_tvUpdate = ACE_OS::gettimeofday(); for(int i = 0; i < PLAYER_CODE_SIZE; i++) { m_objCard[i] = CARD_INVALID_ID; } m_u4CardCount = 0; if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Player::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; }};复制代码上面的注释我想写的应该比较清楚了,呵呵,对于玩家,我需要一些列的属性,当然,你觉得不够的话,你可以添加。那么,Room是什么样的呢?//房间结构体struct _Room{ uint32 m_u4RoomID; //房间ID char m_szRoomName[MAX_BUFF_20]; //房间名称 char m_szRoomDesc[MAX_BUFF_200]; //房间信息描述 _Player m_objPlayer[ROOM_PLAYER_SIZE]; //最大房间内的玩家 _Player m_objVisitPlayer[ROOM_PLAYER_VISITOR_SIZE]; //最大房间参观者玩家 bool m_blState; //是否开放,true开放,false不开放 uint32 m_u4State; //房间状态,0为准备开始,1为已经开始 uint32 m_u4PlayerCount; //玩家个数 uint32 m_u4VisitorCount; //参观玩家个数 uint32 m_u4TimerID; //房间定时器ID _TimerInfo* m_pTimerInfo; //房间定时器的附加参数 ACE_Time_Value m_tvUpdate; //当前房间更新最后时间 _GameInfo m_objGameInfo; //房间游戏相关信息 _Room() { m_u4RoomID = ROOM_ERROR_NOEXIST; m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } //清理房间 void Clear() { for(int i = 0; i < ROOM_PLAYER_SIZE; i++) { m_objPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } for(int i = 0; i < ROOM_PLAYER_VISITOR_SIZE; i++) { m_objVisitPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Room::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; m_u4PlayerCount = 0; m_u4VisitorCount = 0; m_blState = true; m_u4State = 0; m_tvUpdate = ACE_OS::gettimeofday(); }};复制代码同理,和玩家一样过,在这里,我使用了数组而不是STL去解决用户个数的问题,原因是,我觉得在极少的个数面前,使用STL的容器可能显得比较臃肿,而且,我需要达成的是,内存是可控的。我可以控制我的内存大小。那么接下来,我们创建一个房间的管理器吧。struct _RoomManager{ _Room m_objRoom[ROOM_COUNT]; _RoomManager() { for(int i = 0; i < ROOM_COUNT; i++) { m_objRoom[i].m_u4RoomID = i; } }复制代码"。。。"是房间管理器的各种调用方法。提供给外层使用,代码太多了,就不贴在这里了,这里我只是简单的介绍一下架构。那么接下来,你可能会问,那么,用户登录登出,我怎么能把数据源和游戏服务器隔离呢?问的好,如果你能想到这个,说明你已经看懂了一大半我讲的东西了。那么,我们就来说说共享内存吧。我以前在PSS里面提供了一个共享内存跨进程访问的例子,可惜,那个例子可能看的不是很明白。在这里,我正式整合到我的这个德克萨斯扑克中去吧。好的,那么,首先,我们来想想,如何隔离数据源的访问呢?如果我有一个进程,提供数据的读取与写入,将从数据源获得的数据,写入一个公共的地方,当有用户访问的时候,我提供给这个进程一个消息,说我要获得一个指定的数据,这个进程先帮我在共享内存中寻找,如果有直接返回给我一个位置。如果没有,则从数据源填充到公共的地方,然后返回给我一个位置。就和看电影一样,电影院在门口买票,人们拿着票走到指定的座位上,这时候已经坐下看电影的人,不必等着拿票进来找坐位的人,而是可以直接观赏电影。这岂不是很美?恩,那么,我们需要一个这样的模式,来解决我们的数据问题。看吧,其实很简单,你只要造一个虚拟的电影院就可以了。那么,什么是共享内存呢?如果你刚刚接触这个,可能会对此有点晕,没关系,你可以在这里找到我关于共享内存的帖子,好好看一下。我这里使用的共享内存引擎,就是我以前帖子里的。那么,在消费者(电影院厅)进程中,我们只需要提供这样的类功能就可以了typedef map<uint32, _UserPlayer*> mapOnlineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key};#endif复制代码只有一个方法,那就是GetUserPlayer()顾名思义,就是,我有一张电影票,我拿着这个票能找到固定的座位。这个座位就是我的。那么,数据源维护进程是怎么做的呢?typedef map<string, _UserPlayer*> mapOnlineUserPlayer;typedef map<string, _UserPlayer*> mapOfflineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 bool ReadDBData(); //从数据库中填充玩家信息 int32 ReadDBData(const char* pPlayerNick); //从玩家昵称获得玩家信息 int32 ReadPlayerSMPos(const char* pPlayerNick); //获得玩家当前内存地址的偏移位置 bool LeaveDBData(const char* pPlayerNick); //玩家离开 bool WriteDBData(const char* pPlayerNick); //写入数据源玩家的数据 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针 void ReadSMPool(); //还原数据在线,离开和可以删除的数据列表private: uint32 GetRandNumber(); //获得一个随机数 bool DeleteUserPlayer(); //删除最后一个应该删除链表的指针 _UserPlayer* FindPlayerLeave(const char* pPlayerNick); //在离线列表中寻找是否已有数据 _UserPlayer* FindPlayerDelete(const char* pPlayerNick); //在可删除列表中寻找是否已有数据private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key mapOnlineUserPlayer m_mapOnlineUserPlayer; //在线玩家表 mapOfflineUserPlayer m_mapLeaveUserPlayer; //已经离开玩家表 mapOfflineUserPlayer m_mapDeleteUserPlayer; //可以删除的玩家表(用于高效查找) listDeleteUserPlayer m_listDeleteUserPlayer; //可以删除的玩家表(用于淘汰算法)};#endif复制代码呵呵,这里提供的方法比较多,可能看着有些头晕,但是实际上,你只需要看Public提供的方法就行了,那些Private方法提供的是为了LRU而设计的,保持内存的高效性,也就是说,如果用户退出了,且我已经会写成功了数据源,我并不会删除这个用户的数据,如果它下次登录上来,我会现在内存中查找,如果有就直接返回之。(在Linux的release下,每秒每个链接3000次访问稳定输出)如果共享内存满了,我会淘汰最后最不常用的回写成功的数据。这样尽量做到高效。我做的例子很简单,通过用户昵称去获得数据,当然,数据源我没有使用数据库,为了保证样例的完整性,我这里使用了一个简单的随机算法。当然,你有兴趣的话,完全可以替换我这块变成你的数据源访问代码。呵呵,注释写的很清楚了吧。有了这两样,我们就可以很轻松的分布数据请求了。也为下一步我们的动作做好了充足的准备。写到这里,我个人用了差不多2天的时间,我想聪明的你一定能够从中找到一些自己可以借鉴的东西。好了,下一章,我会讲,如何把以上这些应用串联起来。呵呵,让他们统一行动。
m_objLuaFn就是我定义的Lua引擎,提供我这个小世界事件的启动,比如进入房间,离开房间,出牌等等。对应用户操作,该交给Lua处理的,就让它去处理吧。
接下来,让我们看看,在这个小盒子里,我们如何切割若干个小房间呢?
在设计房间的时候,我们必须有一个房间管理器,房间管理器中有一个房间,房间里面包含自己的属性,游戏的属性(德克萨斯扑克信息),以及玩家的属性。
那么先看看,我们的玩家属性有什么
//玩家信息struct _Player{ uint32 m_u4PlayerID; //玩家ID char m_szPlayerNick[MAX_BUFF_20]; //玩家昵称 uint32 m_u4Money; //玩家金钱 uint32 m_objCard[PLAYER_CODE_SIZE]; //玩家手上的牌 uint32 m_u4CardCount; //当前拥有牌的个数 uint32 m_u4TimerID; //当前玩家身上的定时器 _TimerInfo* m_pTimerInfo; //当前的玩家定时器附加信息 ACE_Time_Value m_tvUpdate; //当前玩家更新最后时间 _Player() { m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } void Clear() { m_u4PlayerID = PLAYER_INVALID_ID; m_u4Money = 0; m_u4CardCount = 0; m_szPlayerNick[0] = '\0'; m_tvUpdate = ACE_OS::gettimeofday(); for(int i = 0; i < PLAYER_CODE_SIZE; i++) { m_objCard[i] = CARD_INVALID_ID; } m_u4CardCount = 0; if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Player::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; }};复制代码上面的注释我想写的应该比较清楚了,呵呵,对于玩家,我需要一些列的属性,当然,你觉得不够的话,你可以添加。
那么,Room是什么样的呢?
//房间结构体struct _Room{ uint32 m_u4RoomID; //房间ID char m_szRoomName[MAX_BUFF_20]; //房间名称 char m_szRoomDesc[MAX_BUFF_200]; //房间信息描述 _Player m_objPlayer[ROOM_PLAYER_SIZE]; //最大房间内的玩家 _Player m_objVisitPlayer[ROOM_PLAYER_VISITOR_SIZE]; //最大房间参观者玩家 bool m_blState; //是否开放,true开放,false不开放 uint32 m_u4State; //房间状态,0为准备开始,1为已经开始 uint32 m_u4PlayerCount; //玩家个数 uint32 m_u4VisitorCount; //参观玩家个数 uint32 m_u4TimerID; //房间定时器ID _TimerInfo* m_pTimerInfo; //房间定时器的附加参数 ACE_Time_Value m_tvUpdate; //当前房间更新最后时间 _GameInfo m_objGameInfo; //房间游戏相关信息 _Room() { m_u4RoomID = ROOM_ERROR_NOEXIST; m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; Clear(); } //清理房间 void Clear() { for(int i = 0; i < ROOM_PLAYER_SIZE; i++) { m_objPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } for(int i = 0; i < ROOM_PLAYER_VISITOR_SIZE; i++) { m_objVisitPlayer[i].m_u4PlayerID = PLAYER_INVALID_ID; } if(m_pTimerInfo != NULL) { //如果Timer附加的指针没有得到释放,则在这里提出警告 OUR_DEBUG((LM_ERROR, "[_Room::Clear]m_pTimerInfo is not NULL.\n")); } m_u4TimerID = TIMER_ERROR_IN; m_pTimerInfo = NULL; m_u4PlayerCount = 0; m_u4VisitorCount = 0; m_blState = true; m_u4State = 0; m_tvUpdate = ACE_OS::gettimeofday(); }};复制代码同理,和玩家一样过,在这里,我使用了数组而不是STL去解决用户个数的问题,原因是,我觉得在极少的个数面前,使用STL的容器可能显得比较臃肿,而且,我需要达成的是,内存是可控的。我可以控制我的内存大小。
那么接下来,我们创建一个房间的管理器吧。
struct _RoomManager{ _Room m_objRoom[ROOM_COUNT]; _RoomManager() { for(int i = 0; i < ROOM_COUNT; i++) { m_objRoom[i].m_u4RoomID = i; } }复制代码"。。。"是房间管理器的各种调用方法。提供给外层使用,代码太多了,就不贴在这里了,这里我只是简单的介绍一下架构。
那么接下来,你可能会问,那么,用户登录登出,我怎么能把数据源和游戏服务器隔离呢?
问的好,如果你能想到这个,说明你已经看懂了一大半我讲的东西了。
那么,我们就来说说共享内存吧。我以前在PSS里面提供了一个共享内存跨进程访问的例子,可惜,那个例子可能看的不是很明白。在这里,我正式整合到我的这个德克萨斯扑克中去吧。
好的,那么,首先,我们来想想,如何隔离数据源的访问呢?
如果我有一个进程,提供数据的读取与写入,将从数据源获得的数据,写入一个公共的地方,当有用户访问的时候,我提供给这个进程一个消息,说我要获得一个指定的数据,这个进程先帮我在共享内存中寻找,如果有直接返回给我一个位置。如果没有,则从数据源填充到公共的地方,然后返回给我一个位置。就和看电影一样,电影院在门口买票,人们拿着票走到指定的座位上,这时候已经坐下看电影的人,不必等着拿票进来找坐位的人,而是可以直接观赏电影。这岂不是很美?
恩,那么,我们需要一个这样的模式,来解决我们的数据问题。看吧,其实很简单,你只要造一个虚拟的电影院就可以了。
那么,什么是共享内存呢?如果你刚刚接触这个,可能会对此有点晕,没关系,你可以在这里找到我关于共享内存的帖子,好好看一下。我这里使用的共享内存引擎,就是我以前帖子里的。
那么,在消费者(电影院厅)进程中,我们只需要提供这样的类功能就可以了
typedef map<uint32, _UserPlayer*> mapOnlineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key};#endif复制代码只有一个方法,那就是GetUserPlayer()顾名思义,就是,我有一张电影票,我拿着这个票能找到固定的座位。这个座位就是我的。
那么,数据源维护进程是怎么做的呢?
typedef map<string, _UserPlayer*> mapOnlineUserPlayer;typedef map<string, _UserPlayer*> mapOfflineUserPlayer;typedef list<_UserPlayer*> listDeleteUserPlayer;class CSMDataManager{public: CSMDataManager(void); ~CSMDataManager(void); bool Init(); //打开共享内存 bool ReadDBData(); //从数据库中填充玩家信息 int32 ReadDBData(const char* pPlayerNick); //从玩家昵称获得玩家信息 int32 ReadPlayerSMPos(const char* pPlayerNick); //获得玩家当前内存地址的偏移位置 bool LeaveDBData(const char* pPlayerNick); //玩家离开 bool WriteDBData(const char* pPlayerNick); //写入数据源玩家的数据 _UserPlayer* GetUserPlayer(uint32 u4Pos); //根据偏移获得数据指针 void ReadSMPool(); //还原数据在线,离开和可以删除的数据列表private: uint32 GetRandNumber(); //获得一个随机数 bool DeleteUserPlayer(); //删除最后一个应该删除链表的指针 _UserPlayer* FindPlayerLeave(const char* pPlayerNick); //在离线列表中寻找是否已有数据 _UserPlayer* FindPlayerDelete(const char* pPlayerNick); //在可删除列表中寻找是否已有数据private: CSMPool<_UserPlayer> m_objUserPlayerPool; //玩家用户池 SMKey m_objKey; //共享内存key mapOnlineUserPlayer m_mapOnlineUserPlayer; //在线玩家表 mapOfflineUserPlayer m_mapLeaveUserPlayer; //已经离开玩家表 mapOfflineUserPlayer m_mapDeleteUserPlayer; //可以删除的玩家表(用于高效查找) listDeleteUserPlayer m_listDeleteUserPlayer; //可以删除的玩家表(用于淘汰算法)};#endif复制代码呵呵,这里提供的方法比较多,可能看着有些头晕,但是实际上,你只需要看Public提供的方法就行了,那些Private方法提供的是为了LRU而设计的,保持内存的高效性,也就是说,如果用户退出了,且我已经会写成功了数据源,我并不会删除这个用户的数据,如果它下次登录上来,我会现在内存中查找,如果有就直接返回之。(在Linux的release下,每秒每个链接3000次访问稳定输出)
如果共享内存满了,我会淘汰最后最不常用的回写成功的数据。这样尽量做到高效。
我做的例子很简单,通过用户昵称去获得数据,当然,数据源我没有使用数据库,为了保证样例的完整性,我这里使用了一个简单的随机算法。当然,你有兴趣的话,完全可以替换我这块变成你的数据源访问代码。
呵呵,注释写的很清楚了吧。
有了这两样,我们就可以很轻松的分布数据请求了。也为下一步我们的动作做好了充足的准备。
写到这里,我个人用了差不多2天的时间,我想聪明的你一定能够从中找到一些自己可以借鉴的东西。
好了,下一章,我会讲,如何把以上这些应用串联起来。呵呵,让他们统一行动。
使用道具 举报
本版积分规则 发表回复 回帖并转播 回帖后跳转到最后一页
Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )
GMT+8, 2024-11-23 21:33 , Processed in 0.017863 second(s), 5 queries , Redis On.
Powered by Discuz! X3.5
© 2001-2023 Discuz! Team.