전체 글 51

7. Protobuf#1

지금까지는 패킷을 만들고 직렬화하는 일련의 과정을 알아보았다. 이제부터는 실제 프로젝트에 사용할 Protobuf를 연동해볼 것이다. 꽤 복잡하지만 한 번만 연동하면 된다고 하니 잘 따라가보자. 프로토버프는 구글에서 만든 라이브러리로 꽤 많은 게임들이 이를 채택하여 개발되었다. 그만큼 많이 사용하는 라이브러리이기 때문에 포트폴리오를 만들거나 프로젝트를 할 때 사용하는 것도 나쁘지 않다. 일단 프로토버프를 설치해야되는데 이 부분은 구글링해보면 쉽게 찾을 수 있으므로 스킵하도록 하겠다... 우리는 xml 파일로 패킷을 정의하고 있었다. 이후 xml로 정의한 패킷을 소스코드로 변환하는 자동화 툴을 만들어 사용할텐데 이러한 툴을 우리는 컴파일러라고 부른다. 프로토버프도 마찬가지이다. 나중에 프로토파일을 작성하게 ..

6. 패킷 직렬화 #3

이번에는 보내는 쪽에서 바로 버퍼에 입력하도록 수정할 것이다. ServerPacketHandler.h#pragma once#include "BufferReader.h"#include "BufferWriter.h"enum{ S_TEST = 1};templateclass PacketIterator{public: PacketIterator(C& container, uint16 index) : _container(container), _index(index) {} bool operator!=(const PacketIterator& other) const { return _index != other._index; } const T& operator*() const { return _container[_index]..

5. 패킷 직렬화 #2

지금까지는 버퍼에 데이터를 넣기 위해서 임시로 객체를 만든 다음에 데이터를 받은 후 버퍼에 데이터를 넣고 있다.이 방법에 문제가 있는 것은 아니다. 코드를 작성하거나 가독성 측면에서는 오히려 이러한 방법이 효율적이라고 볼 수 있다. 하지만 당연하게도 임시 객체를 만들게 되면 불필요한 복사 비용이 들게 된다. 이번에는 그러한 과정을 건너뛰고 버퍼에 바로 데이터를 넣고 바로 읽어서 쓰는 방법에 대해 알아볼 것이다. ClientPacketHandler.cppPKT_S_TEST* pkt = reinterpret_cast(buffer);//PKT_S_TEST pkt;//br >> pkt;기존에 새로운 패킷을 만들어 버퍼에서 복사하던 것을 이렇게 버퍼를 포인터 형식으로 캐스팅해서 복사하지 않고 그냥 사용할 수 있도..

4. 패킷 직렬화 #1

우리가 데이터를 저장하거나 누군가에게 보낼 때 일반적인 데이터는 그냥 보내면 되지만 포인터나 동적할당된 데이터는 그냥 보낼 수 없다. 주소값으로 저장이 되는 이런 데이터는 프로그램을 실행할 때마다 달라지게 되는데 이걸 그냥 보내는 건 의미가 없기 때문이다. 패킷 직렬화는 이러한 데이터를 전송가능한 형태로 바꾸는 것을 말한다. 간단한 게임을 만든다면SendBufferRef ServerPacketHandler::Make_S_TEST(uint64 id, uint32 hp, uint16 attack, vector buffs){ SendBufferRef sendBuffer = GSendBufferManager->Open(4096); BufferWriter bw(sendBuffer->Buffer(), sendBuf..

3. Unicode

패킷을 주고받다보면 숫자뿐만 아니라 문자를 보내야 될 때도 있다. 그리고 문자를 보내는 대부분의 경우는 가변적인 데이터이다. 문자를 표현하는 방법에는 여러가지가 있는데char sendData1[] = "가";char sendData2[] = u8"가";WCHAR sendData3[] = L"가";TCHAR sendData4[] = _T("가");한 번 살펴보자. 실제로 저장된 값을 살펴보면 1번 문자열에는 b0, a1이라는 값이 저장 되었고 2번 문자열은 ea, b0, 80이라는 값, 3, 4번 문자열은 ac00이라는 값이 저장 되어 있다. 이렇게 표현하는 방법에 따라 저장되는 값이 다르고 바이트 수도 다르다. 그렇기 때문에 클라이언트와 통신을 할 때에도 이런 규약들은 숙지하고 있어야 원활한 통신을 할..

2. Packet Handler

저번에 버퍼 헬퍼 클래스를 2개로 나눠서 만들었는데 뭔가 좀 애매한 부분이 있다.데이터를 파싱할 때 순서를 맞춰야 한다는 것이다. 이름, 체력, 공격력 순서대로 파싱을 했다면 받을 때도 같은 순서로 받아야 한다.순서가 바뀌게 된다면 이상한 값이 들어오게 될 것이다. 다음 문제점은 패킷의 종류별로 나누지 않았다는 것이다. 실제로 서버를 돌리게 되면 여러 패킷이 올 텐데 패킷의 id에 맞춰 기능을 수행하도록 만들어야 한다. virtual int32 OnRecvPacket(BYTE* buffer, int32 len) override{ BufferReader br(buffer, len); PacketHeader header; br >> header; uint64 id; uint32 hp; uint16 attac..

1. Buffer Helpers

네트워크 라이브러리가 얼추 완성 되었다. 네트워크 라이브러리는 사실 한 번 만들어 놓으면 수정할 일이 드물다.실제로도 대부분의 코드 작업들은 패킷에 관련된 부분에서 다 일어날 것이다. 버퍼를 통해 데이터를 보낼 때 헤더에 정보를 기입하고 헤더 크기를 제외한 만큼의 데이터를 memcpy 해서 보내는 과정은 굉장히 복잡하고 실수가 생길 수 있다. 그래서 이를 도와줄 버퍼 헬퍼를 만들어 보자.BufferReader.h#pragma once//----------------// BufferReader//----------------class BufferReader{public: BufferReader(); BufferReader(BYTE* buffer, uint32 size, uint32 pos = 0); ~Bu..

10. Packet Session

TCP의 특성상 내가 보낸 패킷이 한 번에 도착한다는 보장이 없다. 그래서 전체 패킷이 도착했는지 확인하기 위한 수단이 필요하다.그리고 이를 위해서는 프로토콜을 정의해야 하는데 어떤 식으로 데이터의 끝이라는 것을 알 수 있을까? 단순히 생각하면 끝에 특수문자를 보내서 그 문자가 나오면 끝이다라고 하면 될 거 같다. 0x0001이라는 데이터를 패킷의 끝이라고 정의했다고 가정해보자. 운 나쁘게 데이터를 보내면서 0x0001이라는 데이터가 보내지게 되면 결국 이상하게 동작하게 된다. 그래서 당연하게도 이 방법은 사용할 수 없다. mmo에서 가장 정석이라고 볼 수 있는 방법은 패킷헤더를 사용하는 것이다. 패킷을 보낼 때 헤더에 정보를 담아 추가해서 보내는 것이다. Session.h//--------------..

9. SendBuffer Pooling

현재 버퍼를 만들 때 여유공간을 넉넉히 두고 생성을 한다.  len을 파라미터로 받는다는 것은 보낼 데이터의 크기를 알고 있다는 것인데 그럼 len 크기 만큼 버퍼를 만들면 되지 않을까? 지금 코드를 보면 그렇지만 후에는 크기가 얼마인지 모르는 경우가 생기기 때문에 이를 위해서 미리 넉넉하게 버퍼를 만들어 두는 것이다. 하지만 고작 1 바이트 크기의 데이터를 보내는데 4096 바이트를 할당하는 것은 비효율적으로 보인다. 따라서 이번에 할 것은 SendBufferChunk라는 것을 만들어 처음에 큰 공간을 미리 할당받은 다음에 내가 필요한 만큼만 잘라서 사용하는 식으로 만들 것이다. SendBuffer.h#pragma onceclass SendBufferChunk;//---------------// Sen..

8. SendBuffer

현재 Send의 코드를 보자 SendEvent* sendEvent = Xnew(); sendEvent->owner = shared_from_this(); // ADD_REF sendEvent->buffer.resize(len); ::memcpy(sendEvent->buffer.data(), buffer, len);이 부분을 보면 memcpy를 하게 되는데 이때 복사 비용이 들어간다는 문제점이 있다. 후에 가면 send를 할 일이 굉장히 많이 생길 것이다. 예를 들어 몬스터가 생성되었을 때 주변의 모든 플레이어들에게 해당 정보를 전송 해줘야 하는데 그럴 때마다 복사를 해서 보낸다는 것은 듣자마자 비효율적이라는 것을 느낄 수 있다. 가장 먼저 직관적이고 간단한 방법으로 SendBuffer를 만들어 볼 것이다..