2010-02-23 75 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