帮助多人游戏服务器的内存分配

时间:2010-02-23 17:05:26

标签: c++

我对Linux中的C ++开发有点新意,我正在尝试制作多人游戏。我知道这是一个复杂的程序,但我对其他语言的这类程序有一些背景知识,所以我猜最困难的部分是驯服语言。

虽然我正在编写多人游戏,但我怀疑是处理内存和避免C ++泄漏的最佳方法。

我怀疑是为客户端对象和游戏表分配内存。对于客户端对象,我已经读过std容器为我处理内存分配。我不知道这个内存是否在堆上分配,所以我决定使用指针映射(以socket 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,连接时间等)和一些方法(如send等)。这是跟踪没有内存问题的客户端的最佳方法吗?我想我仍然需要在新的Client()分配上添加异常处理......

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

Daemon.cpp

GameTable *tables;

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

一些解释:GameTable是所有游戏的基类。它是一个具有基本参数和虚拟游戏功能的界面(如doCommand,addClient,removeClient等)。国际象棋类是国际象棋游戏的实施,它从GameTable继承(对不起英语)。问题:

1)这是处理它的最佳方法(记忆)吗? 2)Chess类有很多参数,当我分配Chess对象的表列表时,为已经分配的所有对象做内存,或者我必须在Chess类中分配和处理(使用构造函数和析构函数)?

我的第三个问题是如何在表中添加和删除客户端。首先,我想创建一个简单的向量,其客户端如下:

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;
}

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

4 个答案:

答案 0 :(得分: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)不同,索引到数组将导致索引到对象的中间,可能后跟访问冲突(这是最可能的情况,你实际上是在调用未定义的行为。)

答案 1 :(得分:1)

使用智能指针是避免内存泄漏的好方法

考虑提升者: http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/smart_ptr.htm

答案 2 :(得分:0)

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

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

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

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

答案 3 :(得分:0)

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

您声明Chess数组的方式可能有问题。指针tables被定义为指向GameTable的指针,但它指向一个Chess个对象的数组。如果Chess对象只是具有不同名称的GameTable对象,则此代码应该有效。但是,如果Chess的定义在继承自GameTable之后向其自身添加任何内容,那么您将更改对象的大小,并且您将无法使用该指针迭代数组。例如,如果sizeof(GameTable)是16个字节而sizeof(Chess)是24个字节(可能是由于某些添加的成员数据),那么tables[1]将引用第一个Chess中间的内存位置{ {1}}数组中的对象,而不是数组中第二个项目的开头。多态性允许您将派生类视为其父类的对象,以便使用继承成员,但为了访问父类型,将指向派生类型的指针强制转换为指向父类型的指针是不安全的。阵列。

关于向表添加客户端,客户端是否可以一次与多个表关联?如果没有,请为每个表提供某种类型的唯一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的新映射结构,并将其添加到列表中。客户端与表断开连接时删除该条目。现在,您将拥有一个可以在任一方向交叉引用的所有当前连接的表(使用表查找所有客户端或查找客户端使用的所有表)。