2010-02-23 4 views
2

저는 리눅스에서 C++ 개발에 새로운 것이므로 멀티 플레이어 게임을 만들려고합니다. 나는이 프로그램이 시작 하기엔 다소 복잡한 프로그램이라는 것을 알고 있지만, 다른 언어의 프로그램에서 이런 유형의 배경 지식을 가지고 있기 때문에 가장 어려운 부분은 언어를 길들인 것이라고 생각합니다.멀티 플레이어 게임 서버용 메모리 할당에 대한 도움

멀티 플레이 게임을 프로그래밍하고 있지만, 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); 
} 

을 클라이언트 클래스는 연결 IP 같은 (가상 소멸자, 일부 클라이언트 매개 변수가 간단한 클래스입니다 : 클라이언트가 연결하고 연결을 끊을 때이 방법으로,이 같은 뭔가를 시간 등) 및 몇 가지 방법 (예 : 보내기 등)이 포함됩니다. 이것이 메모리 문제없이 클라이언트를 추적하는 가장 좋은 방법입니까? 나는 여전히 새로운 Client() 할당에 대한 예외 처리를 추가해야한다고 생각한다. ...

두 번째 부분은 나에게 가장 어렵다고 생각하는 것은 게임 테이블에 관한 것이다. 클라이언트는 게임 테이블에 입장하거나 떠날 수 있습니다. 많은 매개 변수, 상수 및 메서드가있는 테이블 클래스가 있습니다.

Daemon.cpp

GameTable *tables; 

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

일부 설명을 : GameTable는 모든 게임의 기본 클래스입니다 저도 같은 Daemon.cpp에서 시작 위에서 설명한 모든 게임 테이블을 만드는거야. 기본 매개 변수 및 가상 게임 기능 (예 : 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; 
} 

이 그것을 처리하는 가장 좋은 방법이 있나요? 모두에게 도움을 주셔서 감사합니다.스마트 포인터를 사용

답변

0

좋은 전략은 정적 서버에서 관리 할 수있는 클라이언트의 최대 수를 설정하는 것입니다.

그런 다음 모든 클라이언트를 관리하는 데 필요한 모든 클라이언트 개체를 시작 (배열 또는 벡터)에서 작성합니다. 그런 다음 새 연결이있을 때 Client 개체를 다시 사용하고 클라이언트가 연결을 끊을 때 연결을 사용하여 끝냅니다.

클라이언트 객체는 재사용을 허용하는 방식으로 만들어야합니다. 초기화 및 "종료"는 명시 적 함수 (예 : init() 및 end())와 비슷해야합니다.

가능한 경우 시작할 때부터 필요한 모든 리소스를 허용하고 개체를 다시 사용하십시오. 그렇게하면 메모리 조각화를 제한하고 "최악의 경우"로 빠르게 진행할 수 있습니다.

+0

아주 재미있는 제안! 감사합니다 – Akira

+0

실제로. 확실한 확신을 선수 수 등의 생성자 인수로 사용하는 클래스와 함께 작업하고 게임을 진행할 때 계층 적으로 계속 수행하십시오. –

3

동적으로 할당되는 모든 것은 삭제 책임을 '소유'하는 것이어야합니다. 보통 autoRAII을 사용하는 할당 된 구조/클래스 여야합니다.

사용 스마트 포인터는 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

+0

음, 그럼 내가 어떻게해야합니까? 할당 된 Chess 객체 (GameTable에서 상속)의 목록 (배열, 벡터, 기타)을 만드는 방법은 무엇입니까? – Akira

+0

@Akira :'GameTable * tables;을'Chess * tables; '로 변경하십시오. 또는 배열에 객체를 저장하는 대신 배열에 객체에 대한 포인터 만 저장하도록합니다. – bta

0

onDisconnect 내부에는 연결이 끊어진 후 clientList[socketFd] = NULL;으로 전화하는 것이 좋습니다. 이렇게하면 이미 해제 된 포인터를 유지하지 않아서 나중에 문제를 일으킬 수 있습니다. 이것은 이미 귀하의 clientList.erase 방법에 의해 처리 될 수 있습니다,하지만 나는 그것을 위해 언급 할 것이라고 생각했습니다.

Chess 배열을 선언하는 방식에 문제가있을 수 있습니다. tables 포인터는 GameTable에 대한 포인터로 정의되어 있지만 Chess 개체의 배열을 가리 킵니다. Chess 개체가 다른 이름을 가진 GameTable 개체 일 뿐인 경우이 코드가 작동해야합니다.그러나 Chess의 정의가 GameTable에서 상속 된 후 자체에 항목을 추가하면 개체의 크기가 변경되므로 해당 포인터로 배열을 반복 할 수 없습니다. 예를 들어, sizeof(GameTable)이 16 바이트이고 sizeof(Chess)이 24 바이트 인 경우 (일부 추가 된 구성원 데이터로 인해), tables[1]은 배열의 첫 번째 Chess 개체 중간에있는 메모리 위치를 참조하며 두 번째 항목의 시작 부분은 참조하지 않습니다 배열로. 다형성 (Polymorphism)은 파생 클래스를 상속 된 멤버를 사용하기 위해 부모 클래스의 객체 인 것처럼 취급하지만, 상속 된 멤버에 액세스하기 위해 파생 된 유형의 포인터를 상위 유형의 포인터로 캐스트하는 것은 안전하지 않습니다. 정렬.

테이블에 클라이언트를 추가하는 것과 관련하여 클라이언트를 한 번에 둘 이상의 테이블과 연결할 수 있습니까? 그렇지 않은 경우 각 테이블에 일종의 고유 한 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

관련 문제