2010-02-23 42 views
2

我是一种新的C++开发在Linux中,我试图做一个多人游戏。我知道这是一个复杂的程序开始,但我从其他语言的这种类型的程序有一些背景,所以我认为最困难的部分是驯服语言。帮助多人游戏服务器的内存分配

尽管我正在编写多人游戏,但我的疑惑是处理内存和避免C++漏洞的最佳方法。

我的疑惑是关于为客户端对象和游戏表分配内存。对于我读过的客户端对象,std容器为我处理内存分配。我不知道这个内存是否分配在堆上,所以我决定使用指针映射(以套接字fd作为键)到客户端对象。这样一来,我有这样的事情,当一个客户端连接和断开:

Daemon.cpp

map<int,Client*> clientList; 

//Do server stuff 

//Add connected client to list 
void onConnect(int socketFd) { 
clientList[socketFd] = new Client(); 
} 

//remove connected client from list 
void onDisconnect(int socketFd) { 
delete clientList[socketFd]; 
clientList.erase(socketFd); 
} 

Client类是一个简单的类,它有一个虚析构函数,某些客户端参数(如IP,连接时间等)和一些方法(如发送等)。这是跟踪没有内存问题的客户的最佳方式吗?我想我仍然需要在新的Client()分配上添加异常处理......

第二部分,我想对我来说最困难的是关于游戏桌。客户可以进入和离开游戏桌面。我有一个包含大量参数,常量和方法的表类。我创建的所有游戏桌在启动时在同一Daemon.cpp上述:

Daemon.cpp

GameTable *tables; 

int main() { 
tables = new Chess[MAX_NUMBER_OF_TABLES]; 
} 

几点说明:GameTable是所有游戏的基类。它是一个基本参数和虚拟游戏功能的接口(如doCommand,addClient,removeClient等)。国际象棋类是国际象棋游戏的实施,它从GameTable继承(对不起坏英语)。问题:

1)这是处理它的最佳方式(内存)吗? 2)国际象棋类有很多的参数,当我分配国际象棋对象的表列表做的内存已经分配的所有对象或我必须分配和分配在国际象棋类(与构造函数和析构函数)?

我的第三个问题是如何添加和删除客户端/从表中。首先,我在与客户一样创建一个简单的载体认为:

GameTable.h

vector <Client> clientInTable; 

Chess.cpp

//Add client to table 
void addClient(Client &client) { 
clientInList.push_back(client); 
} 

//remove client from table 
void removeClient(Client &client) { 
//search client on list, when found get position pos 
clientList.erase(pos); 
} 

不久,我注意到,当我删除客户端的析构函数是调用。它一定不会发生!比我在使用中想过像指针的向量:

GameTable.h

vector <Client*> clientInTable; 

Chess.cpp

//Add client to table 
void addClient(Client *client) { 
clientInList.push_back(client); 
} 

//remove client from table 
void removeClient(Client *client) { 
//search client on list, when found get position pos 
clientList[pos] = NULL; 
} 

这是处理它的最佳方式?感谢大家的帮助。

回答

0

一个好的策略是静态设置,可以通过你的服务器来管理客户端的最大数量。

然后,您将构建从开始(在数组或向量中)管理所有客户端所需的所有Client对象。 然后,您将在有新连接时重新使用Client对象,并在客户端断开连接时使用Client对象结束。

这要求你的Client对象是以允许重用的方式创建的:它的初始化和“终止”使用必须是显式函数(init()和end(),例如类似的东西)。

如果可以,请启动所有您需要的资源并重新使用这些对象。这样你可以限制内存碎片,并加快到“最坏情况”。

+0

非常有趣的建议!谢谢 – Akira 2010-02-23 17:29:45

+0

确实。与确定性作为构造参数的类一起工作,比如“玩家数量”等等,并且在玩游戏的时候保持等级化。 – 2010-02-23 17:35:26

3

所有动态分配的东西都应该有'拥有'删除它的责任 - 通常这应该是auto已分配的使用RAII的结构/类。

使用智能指针如std::auto_ptrstd::tr1::shared_ptr存储动态分配的对象,并使用存储器管理的容器,如boost::ptr_vectorboost::ptr_map用于在单个容器中存储多个动态分配的对象。

手动做这种事情很容易出错,很困难,而且看到已经存在的良好解决方案毫无意义。


此:

GameTable *tables; 

int main() { 
tables = new Chess[MAX_NUMBER_OF_TABLES]; 
} 

是极其危险的。 Chess的数组不能与GameTable数组互换使用。编译器允许它通过,因为指向Chess的指针可以用作指向GameTable的指针。

数组连续包装 - 如果size_of(Chess)size_of(GameTable)不同,索引到阵列将导致索引到可能跟随的访问冲突(这是最有可能的情况一个对象的中间,你实际上调用未定义行为)。

+0

我不想说这是危险的;这是不对的。如果你使用表格数组,你最终会遭到损坏。 – JonM 2010-02-23 17:25:21

+0

那么,我该怎么做?如何创建分配的Chess对象(从GameTable继承)的列表(数组,矢量,任何)? – Akira 2010-02-23 17:28:04

+0

@Akira:将GameTable *表更改为'Chess * tables;'。或者,不是将对象存储在数组中,而是让数组仅存储指向对象的指针。 – bta 2010-02-23 17:31:16

0

onDisconnect里面,我建议在连接被破坏后调用clientList[socketFd] = NULL;。这将确保你没有保留一个已经释放的指针,这个指针可以在后面打开问题的大门。这可能已经由您的clientList.erase方法处理,但我想我会提及它以防万一。

您声明Chess数组的方式可能有问题。指针tables被定义为指针到GameTable,但它指向一个Chess对象的数组。如果Chess对象只不过是具有不同名称的GameTable对象,则此代码应该可以工作。但是,如果Chess的定义在从GameTable继承后会自行添加任何内容,那么您将更改该对象的大小,并且将无法使用该指针遍历该数组。例如,如果sizeof(GameTable)是16字节,并且sizeof(Chess)是24字节(可能是由于某些添加的成员数据),那么tables[1]将引用阵列中第一个Chess对象中间的内存位置,而不是第二个项目的开始按照预期排列在阵列中。为了使用继承的成员,多态可让您将派生类视为其父类的对象,但为了访问某个派生类,将派生类型的指针转​​换为父类型的指针并不安全阵列。

关于将客户端添加到表中,客户端可以同时与多个表关联吗?如果没有,给每个表一个唯一的某种ID,并给每个客户一个名为(例如)current_table的字段。当客户端加入一个表时,将该表的ID存储在该字段中。客户离开表格时,将值清零。如果客户端可以连接多个表格,则可以将该字段转换为数组(current_tables[MAX_TABLES_PER_CLIENT])并进行相似处理。

或者,你可以创建类似:

struct mapping { 
    clientId_t client_id; 
    tableId_t table_id; 
}; 

struct mapping client_table_map[MAX_NUM_CLIENT_TABLE_MAPS] = {0}; 

当客户端连接表,创建一个包含客户端和表的唯一ID的新映射结构,并将其添加到列表中。当客户端从表中断开时删除条目。现在,您将拥有一个可以在任一方向交叉引用的所有当前连接的表格(查找使用表格的所有客户端或查找客户端使用的所有表格)。

+0

感谢您的输入。事实上客户只能与一个表关联。我已经想过在客户端对象中添加表ID,但是,在这种情况下,要将聊天文本发送给一个表中的每个人,我必须通过每个连接的客户端来测试他是否在特定表中。我宁愿有一个表中的每个客户端的列表,并调用表[ID] .send(“text”)来发送文本聊天。 (对不起英文不好) – Akira 2010-02-23 17:37:47