전체 글 49

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를 만들어 볼 것이다..

7. RecvBuffer

Recv같은 경우는 멀티스레드 환경을 고려하지 않아도 된다. 한 번에 한 스레드만 ResisterRecv와 ProcessRecv로 들어올 것이기 때문이다. 낚시대가 1개라고 생각하면 된다. 그럼에도 현재의 코드와 같이 RecvBuffer를 그냥 사용하는 것은 문제가 있다. 지금 TCP서버를 만들고 있는데 TCP의 경우는 패킷과의 바운더리가 없다. 이는 상대방이 100바이트를 보내더라도 그것이 잘려서 20바이트만 넘어올 수 있다는 얘기이다. 그럼 뭐가 문제냐? 대부분의 경우 패킷의 완전체를 받아야 처리를 할 수 있을 것이다. 그렇기 때문에 지금처럼 패킷을 덮어쓰면 안되는 것이다. 그럼 이제 Recv 패킷을 관리하는 클래스를 만들어보자. 이 클래스에서 끊어져서 온 패킷을 합치기도 하도 이것저것 작업을 할 것..

6. Session#3

현재는 세션을 생성할 때 소켓도 함께 생성하고 연결이 끊기면 소켓을 닫아준다. 하지만 소켓을 만드는 작업에 꽤 많은 비용이 들어가기 때문에 소켓을 재사용하는 코드로 바꿔줄 것이다.  Session.h#pragma once#include "IocpCore.h"#include "IocpEvent.h"#include "NetAddress.h"class Service;//------------// Session//------------class Session : public IocpObject{ friend class Listener; friend class IocpCore; friend class Service;public: Session(); virtual ~Session();public: /* 외부에서 ..