开发者

Help with memory allocation for multiplayer game server

开发者 https://www.devze.com 2022-12-20 20:49 出处:网络
I\'m kind of new to C++ development in linux and I\'m trying to make a multiplayer game. I know that it is a bit of complex program to start but I have some background on this type of program from oth

I'm kind of new to C++ development in linux and I'm trying to make a multiplayer game. I know that it is a bit of complex program to start but I have some background on this type of program from other languages so I guess the most difficult part is taming the language.

Although I'm programming a multiplayer game, my doubts are about the best way to handle the memory and avoid leaks in C++.

My doubts are about allocating memory for the client objects and for the game tables. For the client objects I've read that the std containers handle the memory allocation for me. I don't know if this memory is allocated on heap so I've decided to use a map of pointers (with the socket fd as key) to client object. This way, I have something like this when a client connect and disconnect:

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

The Client class is a simple class that has a virtual destructor, some client parameters (like IP, connected time, etc) and some methods (like send, etc). Is this the best way to keep track of clients without memory problems? I guess I still have to add exceptions handling on new Client() allocations...

The second part, and I guess the most difficult for me, is about the game tables. Clients can enter and leave tables of games. I have a table class with a lot of parameters, constants and methods. I'm creating all the game tables on start up on the same Daemon.cpp described above:

Daemon.cpp

GameTable *tables;

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

Some explanations: GameTable is the base class for all games. It is an interface with base parameters and virtual game functions (like doCommand, addClient, removeClient, etc). Chess class is the implementation of the chess game, it inheritance (sorry bad english) from GameTable. Questions:

1) Is this the best way (memory) to handle it? 2) Chess class has a lot of parameters, when I allocate the table list of Chess objects do the memory for all objects already allocated or I have to allocate and dealocate inside Chess class (with constructors and destructors)?

My third question is how to add and remove clients to/from tables. First I thought in creating a simple vector with clients like:

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

Soon I've noticed that when I remove the client its destructor was called. It must not happen! Than I thought in use a vector of pointers like:

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

Is this the best way to handle it? Thanks everybody for the help.


Everything that is dynamically allocated should have something that 'owns' the responsibility for deleting it - typically this should be an auto allocated struct/classs that uses RAII.

Use smart pointers such as std::auto_ptr and std::tr1::shared_ptr to store dynamically allocated objects, and use memory managing containers such as boost::ptr_vector and boost::ptr_map for storing multiple dynamically allocated objects in a single container.

Doing this kind of thing manually is error prone, difficult and, seeing as good solutions already exist, pointless.


This:

GameTable *tables;

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

Is extremely dangerous. An array of Chess cannot be used interchangeably with an array of GameTable. The compiler lets it pass because a pointer to Chess can be used as a pointer to a GameTable.

Arrays are contiguously packed - if size_of(Chess) is different to size_of(GameTable), indexing into the array will result in indexing into the middle of an object possibly followed by an access violation (that's the most likely scenario, you're actually invoking undefined behaviour).


Using smart pointers is a good way to avoid memory leaks

Consider boost ones: http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/smart_ptr.htm


A good strategy would be to set statically the maximum number of clients that can be managed by your server.

Then you build all Client objects required to manage all the clients from start (in an array or vector). You will then re-use Client objects when there is a new connection, end using one when the client disconnects.

That require that your Client object is made in a way that allow reuse : the initialization and "termination" of it's use have to be explicit functions (init() and end() for example, something similar).

When you can, allow from start all resources you will ever need and reuse the objects. That way you limit memory fragmentation and get faster to the "worst case".


Inside onDisconnect, I would recommend calling clientList[socketFd] = NULL; after the connection is destroyed. This would make sure you aren't keeping an already-freed pointer around which can open up the door for problems later. This may already be handled by your clientList.erase method, but I thought I'd mention it just in case.

There might be a problem with the way you declared your Chess array. The pointer tables is defined as a pointer-to-GameTable, but it is pointing to an array of Chess objects. If a Chess object is nothing more than a GameTable object with a different name, then this code should work. However, if the definition of Chess adds anything to itself after it inherits from GameTable then you will change the size of the object and you will not be able to iterate through the array with that pointer. For example, if sizeof(GameTable) is 16 bytes and sizeof(Chess) is 24 bytes (perhaps due to some added member data), then tables[1] will refer to a memory location in the middle of the first Chess object in the array, not the beginning of the second item in the array as intended. Polymorphism lets you treat a derived class as if it were an object of its parent's class for the sake of using inherited members, but it's not safe to cast a pointer to a derived type into a pointer to the parent type for the sake of accessing an array.

Regarding adding clients to tables, can a client be associated with more than one table at a time? If not, give each table a unique ID of some sort and give each client a field called (for instance) current_table. When the client joins a table, store that table's ID in the field. When the client leaves the table, zero out the value. If a client can join multiple tables, this field can be turned into an array (current_tables[MAX_TABLES_PER_CLIENT]) and treated similarly.

Alternatively, you can create something like:

struct mapping {
    clientId_t client_id;
    tableId_t table_id;
};

struct mapping client_table_map[MAX_NUM_CLIENT_TABLE_MAPS] = {0};

When a client joins a table, create a new mapping structure containing the unique IDs of the client and table and add it to the list. Delete the entry when the client disconnects from the table. Now, you will have a table of all current connections that you can cross-reference in either direction (find all clients using a table or find all tables in use by a client).

0

精彩评论

暂无评论...
验证码 换一张
取 消