목록Game Dev (263)
KoreanFoodie's Study

[C++ 게임 서버] 5-8. 패킷 자동화 #1 핵심 : 1. 효율적인 작업을 위해, 패킷이 변동될 때 빌드를 하면 관련 protoc 파일 등이 자동으로 업뎃되게 만들면 좋다. 배치파일과 .vcxproj 를 잘 수정해 보자. 2. 초기화 단계에서 각 패킷 별로 호출되어야 하는 함수를 Functor 로 만들어 각 패킷에 대응시켜 놓으면, 각 컨텐츠 담당자는 패킷이 추가될 때 해당 패킷에 대한 구현만 신경쓰면 된다. 저번 시간에는 Protobuf 를 세팅하면서, 패킷 작업을 어떻게 하면 되는지에 대해 배웠다. 그런데 사실 돌이켜 보면 불편한 점이 이곳 저곳에 산재한다는 느낌이 든다. 왜냐하면 결국 우리가 패킷을 만들거나 수정하면, .protoc 파일을 매번 새로 생성해 줘야 하는데, 심지어 이 작업을 Gam..

[C++ 게임 서버] 5-7. Protobuf 핵심 : 1. protobuf 는 구조화된 데이터를 직렬화하는 방식으로, 구글에서 제공하는 툴을 의미하기도 한다. 2. protobuf 를 사용하면 임시 객체에 대한 복사는 발생할 수도 있으나, 직렬화와 역직렬화 과정이 매우 편리해진다는 장점이 있다. 3. protobuf 는 인터페이스가 간단하고 명확하며, 다른 언어 및 OS 를 사용하는 타 서버와의 연동이 쉬워 협업에도 매우 유리하다. 이번 시간에는 구글에서 만든 데이터 직렬화 툴을 지금까지 만든 샘플 프로젝트에 적용해 볼 것이다. 이번에는 결과만 보여주는 것이 아니라, 설치 및 세팅하는 과정을 자세히 기록할 것이니, 추후 세팅에 어려움을 겪을 때 이 글이 도움이 되었으면 좋겠다. 🤗 먼저, protobu..

[C++ 게임 서버] 5-6. 패킷 직렬화 #3 핵심 : 1. 가변 데이터 안에 가변 데이터를 넣는 경우도, 결국 Offset 과 Count 를 이용해 버퍼에 데이터를 기록하는 방식으로 처리한다. 2. 추후에 소개할 ProtoBuf 를 이용하면, 패킷을 만들 때 가변 데이터를 일일히 코드로 넣는 귀찮음을 줄일 수 있다(다만 불필요한 복사가 있을 수는 있음). 저번에는 가변 데이터를 패킷에 넣어 보았는데... 만약 가변 데이터 안에 또 가변 데이터가 있다면 어떻게 해야 할까? 예를 들어, BuffListItem 이라는 구조체에 해당 버프의 대상들 정보(Victims 라고 하자)를 담는다면 어떻게 해야 할까? struct BuffsListItem { uint64 buffId; float remainTime;..

[C++ 게임 서버] 5-5. 패킷 직렬화 #2 핵심 : 1. 가변 데이터를 처리할 수 있는 PacketList 를 만들면, 버퍼를 읽을 때 임시 객체를 생성하지 않고 캐스팅을 통해 데이터를 효과적으로 읽어들일 수 있다. 자, 일단 저번 시간에는 가변 데이터 형식인 buffs 에 대해 패킷 직렬화를 수행했었다. 그런데 코드를 잘 보면, 조금 마음에 들지 않는 부분이 있다. vector buffs; buffs.resize(pkt.buffsCount); for (int32 i = 0; i > buffs[i]; 바로 buffs 데이터를 읽을 때 임시 객체가 생성되는 것인데... 생각해보면 굳이 그럴 필요가 있나 싶다. 🤔 이 부분을 최적화하기 위해, 이번 글에서..

[C++ 게임 서버] 5-4. 패킷 직렬화 #1 핵심 : 1. 패킷 직렬화란, 포인터 및 가변 데이터 정보를 패킷으로 주고 받을 때 이를 안전하게 저장하고 불러올 수 있도록 파싱하는 기법이다. 2. 가변 데이터의 경우, Offset 과 Count 를 이용해 직렬화를 쉽게 진행할 수 있다. 3. 직렬화된 데이터는 xml 또는 json 포맷으로 세이브 & 로드를 하며 관리하게 된다. xml 은 가독성이 좋을 수 있으나 복잡하며, json 은 형태가 간단하며 파싱 속도가 빠르다. 이번 글에서는 패킷 직렬화에 대해 알아보자. 영어로는 사실 Serialization 인데, 직렬화라고 하니 살짝 어색하게 느껴지기는 한다. 😅 언리얼에서도 Serialization 이라는 용어를 사용하는데, 그 때는 데이터를 안전하고..

[C++ 게임 서버] 5-3. Unicode 핵심 : 1. 문자열을 핸들링에는 2가지 요소가 있다. 하나는 Character Set 이고, 다른 하나는 Encoding 이다. 2. Unicode 는 다양한 문자열에 대응할 수 있는 Set 으로, 인코딩 방식에 따라 UTF-8 이나 UTF-16 등으로 나뉘게 된다. 3. MBCS 는 문자의 특징마다 사용하는 바이트가 다른 경우를 의미하며, WBCS 은 모든 문자열이 동일한 바이트를 사용하는 방식이다. 이번 글에서는 누구나 익숙하지만 물어보면 항상 헷갈리는 내용, 유니코드에 대해 다시 짚고 넘어가도록 하겠다. 일단 Unicode 에 대해 제대로 이해하려면 문자열 인코딩의 두 요소, 'Character Set' 과 'Encoding' 을 구분해야 한다. 먼저,..

[C++ 게임 서버] 5-2. PacketHandler 핵심 : 1. 다양한 패킷에 대한 처리를 대응하기 위해서, 패킷별로 enum 을 만들고, 각 enum 별로 패킷을 핸들링하는 함수를 만들어 주면 switch 문으로 한번에 처리할 수 있을 것이다. 2. 가변 데이터를 넣는 경우도 Primitive Type 을 읽는 경우와 거의 비슷하다. 벡터의 크기나 갯수 정보를 넣어주기만 하면 해결된다! 이전 시간에는 Buffer Helper 클래스를 만들어 버퍼를 조금 더 용이하게 읽고 쓸 수 있도록 했다. 하지만 아직 부족하다! 일일히 임의의 값으로 버퍼의 내용을 채워 패킷을 조립하고 분해하는 것보다, 좀 더 정형화된 형태로 패킷을 주고 받을 수 있는 것이 좋다. 그래서 이번 글에서는 PacketHandler ..

[C++ 게임 서버] 5-1. BufferHelper 핵심 : 1. Buffer 를 쉽게 읽고 쓸 수 있게 해 주는 Buffer Helper 클래스를 만들어 보자. 2. '>>' , '>' 를 오버로딩한 부분이다. 현재 버퍼에서 읽을 부분을 적절한 타입으로 캐스팅해 읽어낸 후, _pos 를 이동시키면, 연속해서 '>>' 연산을 수행할 수 있다. 실제 예시는 아래 부분에 DummyClient 클래스를 통해 확인할 수 있을 것이다 😄 BufferReader.cpp BufferReader::BufferReader() { } BufferReader::BufferReader(BYTE* buffer, uint32 size, uint32 pos) : _buffer(buffer), _size(size), _pos(po..

[C++ 게임 서버] 4-10. PacketSession 핵심 : 1. 간단하게 패킷을 만들어 보자. 사실 데이터에 size 와 id(프로토콜) 을 헤더로 붙이면, 그게 기본적인 패킷의 형태다. 2. Session 을 제거하고 넣을 때는 기존 컨테이너의 정합성이 깨지지 않도록 주의하자. 이를 회피하기 위해 클라가 종료될 때, 바로 세션을 제거하지 않고, 대신 Disconnect 이벤트를 등록만 한 다음 다른 쓰레드가 Dispatch 될 때 세션을 제거하게 만들 수도 있다. 저번까지는 버퍼에 우리가 보내려고 하는 문자열을 그냥 담아서 보냈다. 그런데 실제 게임에서는 이 데이터가 무슨 데이터인지에 대한 정보가 필요한데, 흔히 우리는 이것을 패킷이라고 한다. 즉, 해당 정보에 대한 메타 정보(흔히 헤더 부분에..

[C++ 게임 서버] 4-9. SendBuffer Pooling 핵심 : 1. SendBuffer 를 보낼 때마다 메모리 할당을 하지 말고, Memory Pooling 기법을 이용해서 큰 초기에 1 번만 할당을 하는 기법을 사용해 보자. 2. 실제 Session 에서는 SendBufferManager 를 이용해 버퍼를 사용할 준비(Open 함수)를 한다. SendBufferManager -> SendBufferChunk -> SendBuffer 순으로 계층이 잡혀 있다. 3. SendBufferChunkRef 타입의 인자를 각 쓰레드별로 TLS(Thread-Local Space)에 잡아 두면, 각 쓰레드 끼리 Chunk 에 접근하는 데 있어 경합을 벌일 필요가 없어 효율적이다. 또한 Lock-Free 설..