说说游戏服务器
这是一个纯技术贴,以我做的mansterSLS作为基础,讲述该如何做一个mmorpg游戏服务器。最近心情不好写到重新入职为止。
所有最新的代码可以在https://sourceforge.net/projects/monstersls/ 目录code,svn下载。
说到网游服务器是最近火起来的,但最初的网游服务器几乎和单机游戏一样的古老那就是mudos。mudos是一个通过文字交流的古老的逻辑系统,有点类似聊天机器人,可以根据不同的客户端命令做出相应的回复,使用户维持在一个自我虚幻的环境,跟着故事情节的发展得到许多不同的命运体验。
游戏服务器处理游戏的逻辑部分,为啥不是在客户端呢?原因是游戏是一个完整的世界,所有事件都发生在这个世界里。既然是一个完整的世界那么这个世界发生的事件就要持续完整。这样所有事件都要记录在服务器供理论上所有客户端去查询。另一原因就是防止客户端篡改游戏世界的数据。请注意wow的客户没有加壳和任何通常的防止黑客的手段为什么呢?因为所有游戏世界的数据和行为都是发生在服务器。客户端的指令要经过逻辑判断,不合逻辑的指令会被判失败。一个简单的例子普通攻击指令执行过程大致是,客户端通过服务器查询看到某人,给服务器发送攻击指令,服务器计算攻击的结果,给客户端和他攻击的目标发送结果,并在服务端保存对用的数据。这里无论通过任何客户端都可以给服务器发送指令甚至是模拟器。服务器会先判断一系列的先决条件,例如攻击距离,对方是否在线等等。通过判断后计算攻击结果,这里一系列的过程客户端是无法更改的全部在服务端完成,并把这些数据共享给对应的客户端。这里我们可以看到在客户修改这些数值没有任何意义,服务判断的依据不是客户端上保存的数据。
到这里我们了解了游戏服务器就是一个大的数据库,这个数据库的特征描述如下:
1,高度并发的读取和写入。
2,数据字段不固定,可能在任何时刻增加或减少。
3,不需要模糊查询的操作,所有数据查询都是对象为基础。
等等剩下的还没有想到。
不过单从上面几点商用数据库可以pass了,即使号称对象数据库的PostgreSQL也不行,数据库的一大弊病就是为了模糊查询需要所有对象共享数据字段,这样即使某个数据不使用某个字段也要共享。会浪费大量的数据空间。游戏里面对象之间的字段也完全没有共性可言。
例如npc是一个对象,椅子也是一个对象,如果按hp,mp排序,要买椅子是hp,mp是零,要么是一个非常大的数值。因为椅子就没有hp,mp。如果npc和椅子放在数据库一个表里面椅子就要浪费mp,hp的两个段值。
typedef struct dobject { map<string, string> data; ///for share, only net object using uintptr_t index; intptr_t x,y,z; size_t stamp; list<uintptr_t> view; void* psp; }DOBJECT, *PDOBJECT;
typedef struct tobject {; public: uintptr_t id; PDOBJECT data; pthread_mutex_t hmutex; }TOBJECT, *PTOBJECT;
uintptr_t OBJECT::ObNew(char* obid, uintptr_t index){ PDOBJECT ob = new DOBJECT; ob->index = index; ob->psp = 0; ob->stamp = 0; ///add ob to g_obmap uintptr_t nob=0, no=0; for (size_t cursor = 0; cursor < g_maxobj; cursor++) { LOCK(g_obmap[cursor].hmutex, OBJECT::ObNew); if (NULL == g_obmap[cursor].data) { g_obmap[cursor].data = ob; nob = (uintptr_t)&(g_obmap[cursor]); sprintf(obid, "%p#%u", &g_obmap[cursor], ++g_obmap[cursor].id); no = 1; } UNLOCK(g_obmap[cursor].hmutex, OBJECT::ObNew); if (no)break; } return (uintptr_t)nob;}
///if lock or unlock fail all is unacceptable#define LOCK(mutex, fun) if(pthread_mutex_lock(&mutex) != 0)\{\ Logf(#fun "lock fail\r\n");\ exit(0);\}#define UNLOCK(mutex, fun) if(pthread_mutex_unlock(&mutex) != 0)\{\ Logf(#fun "unlock fail\r\n");\ exit(0);\}
sprintf(obid, "%p#%u", &g_obmap[cursor], ++g_obmap[cursor].id);
int OBJECT::ObDelete(const char* obid){ PTOBJECT tob = GET_OBJ(obid); int ret = 0; LOCK(tob->hmutex, OBJECT::ObDelete); if(NULL != tob->data && tob->id == GET_IND(obid)) { if (tob->data->index) CloseIndex(tob->data->index); delete tob->data; tob->data = NULL; ret = 1; } UNLOCK(tob->hmutex, OBJECT::ObDelete); return ret;}