Serializer 제작

게임 서버 프로그래밍/윈도우 소켓 프로그래밍

2020. 9. 15. 15:00

jacking75님의 IOCP 기반의 채팅 프로그램 튜토리얼을 따라 하던 중

User마다 데이터를 쌓아 놓는 버퍼 함수들을 보고 떠올리기 싫은 직렬화 클래스를 구현해야겠다고 마음먹었다.

( 채팅방 포폴 만들 때 이해 능력이 달려서.. 그냥 외우자는 마인드로 썼었다.)

 

이번에도 역시 게임 서버 프로그래밍 입문 (저자 : 김동성) 책을 아주 조금 참고했다. (아주 조금...)

맨땅의 헤딩은 아니었지만 (한 번 다뤄봐서..) 제대로 다루지 못했던 기억이 떠올라서 급히 만들었다.

처음엔 역시나 제대로 데이터가 들어가지 않아 진땀 좀 빼고 위의 책을 참고했었다.

이제야 어떻게 돌아가는지.. 데이터들이 어떻게 쓰이고 읽히는지 완벽히 이해한듯하다.

 

먼저, 클래스는 두 가지로 나누었다. Serializer (직렬화 클래스) , Packet (네트워크 로직에서 쓰일 실질적인 데이터 구조)

 

Serializer

기본적으로 직렬화/역직렬화가 같은 클래스에서 이용되는데

이는 네트워크 로직상 양쪽 방향으로 하나의 패킷이 다 쓰이는 경우가 없을 것 같기 때문이다.

물론 저번엔 수신한 패킷의 내용을 다 빼먹은 후 거기에 다시 초기화한 후 송신 패킷으로 사용했었지만..

패킷을 꾸릴 때마다 그냥 패킷을 만들어주는 게 더 괜찮을 거라 생각했다.

(패킷 생성자에 패킷 헤더 초기화 관련..)

 

아무튼.. 기본적으로 매개 변수 생성자를 default로 해서 처음 버퍼 사이즈를 잡아준다.

그리고, 대입과 복사 생성을 지원한다.

 

 

Serialize/DeSerialize 함수를 통해서 직렬화/역직렬화도 할 수 있고

operator >> , << 연산을 통해서도 직렬화/역직렬화를 할 수 있다.

string 때문에 애좀 먹었는데.. 간단한 원리만 알면 쉬이 구현해 낼 수 있다.

 

중요한 것은 원시 자료형이 아닌 String, Vector 등의 STL 컨테이너들은 

먼저 컨테이너의 크기를 직렬화/역직렬화를 해야 한다는 것이다.

멀티플레이어 게임 프로그래밍 책에서도 나온 얘기지만 크기를 알아야 

직렬화 한 개수만큼 역직렬화를 할 수 있다.

 

직렬화 개념에서 우리는 순서를 맞추어 데이터를 차곡차곡 쌓지만

순서는 오롯이 우리가 결정한다는 것. 즉, 순서대로 넣고 빼지 않으면

데이터를 아예 읽지 못하거나 이상한 문자를 출력하는 현상을 볼 수 있다.

 

 

그리고, string 같은 경우에도 데이터를 읽을 때는 바이트 배열로 읽어 들이기에

마지막에 null 문자를 넣어서 해당 값의 끝을 기록해줘야 한다는 것이 포인트다.

 

 

공통적으로 메모리를 복사하는 과정은 WRITE/READ 템플릿 함수로 묶어서 뺐다.

<게임 서버 프로그래밍 입문> 책에서는 매크로로 처리했으나 Effective C++에서 봤듯이

되도록 매크로를 쓰지 않는 쪽을 택했다. 

 

여기까지가 직렬화 클래스이다. 여타의 STL에 대해서는 필요할 때 구현하려고 만들지는 않았다.

map, hash_map, queue 등도 사실 vector와 다르지 않게 구현할 수 있다.

그리고, 저번에 보니.. 딱히 다른 자료구조는 잘 사용되지가 않았다. 어차피 전체 복사가 목적이니

vector만 한 게 없는 것 같기도 하다.

 

Packet

 

 

허접한 패킷 헤더이다. 저번 포폴에서 기를 쓰고 다른 것들도 넣어서 꾸며보려 했지만

개인이 만드는 간단한 서버가 들어가는 프로그램에 저것 이상의 데이터가 필요할까 싶다..

(능력이 부족해서 뭐가 더 있어야 할지도 모르겠다 ㅜㅜ)

 

기본적으로

  • 패킷 ID (어떤 종류의 패킷인지 식별)
  • 세션 ID (어떤 클라이언트가 패킷을 보냈는지/ 어떤 클라이언트에게 패킷을 보낼지 식별)
  • IO DIR ( 클라 -> 서버로 패킷을 보내는지 서버 -> 클라로 패킷을 보내는지 )
    • 요것은 나중에 다중 서버로 서로 데이터 주고받을 때를 대비해 Server <->Server도 필요할 듯하다

 

별 것은 없다. 그저 패킷은 Serializer을 상속했다는 점. 처음에는 멤버 변수로 Serializer를 들고 있으려 했으나

결국 Wrapper 클래스 역할만 하기에.. (심지어 operator 활용도 못함.. 하려면 전부 다시 구현)

깔끔하게 상속해왔다. 굉장히 만족 중..

 

그리고, Serialize 네임 스페이스 또한 effective c++에서 적극 추천한 파일 분리 방식을 그대로 가져왔다.

Serializer 클래스는 정말 기본적인 원시 자료형 및 자료구조에 대해서만 직렬화/역직렬화를 지원하는데

패킷 ID나 방향처럼 Enum으로 이루어진 특이 경우 자료형에 대해서는 그 자료형이 사용되는 클래스에서

구현하도록 했다. 이렇게 해서 저 연산들이 필요 없는 클래스에서 데이터 크기도 줄이고 캡슐화도 적절히

이루어질 수 있다.

 

 

마지막으로 해당 결과이다.

operator 연산을 통해  PacketHeader/Vector를 직렬화/역직렬화 하였다.

 

 

아주 만족스럽다.. 

확장할 수 있는 기능들이 몇 개 남아있긴 하다. 패킷 사이즈를 정해두고는 있지만 사용하지는 않은 점.

그리고, 혹여나 Serializer 버퍼의 크기가 모자랄 때 버퍼의 크기를 늘린다는 기능은 지원하고 있지 않다.

나중에 포폴에 쓰일 게임의 패킷이 어떤 데이터를 담을지는 모르겠으나

저번 포폴에서 확인한 결과, 한 번에 보내는 패킷 사이즈가 500 바이트를 넘기도 힘들었다.

(물론 전부 텍스트라 그런 듯하다.. 영상을 보낸다면 그에 맞는 패킷 구조를 따로 만들어야 할 듯하다.)

 

다음에 여기서 더 추가된다면 Packet Manager를 통해서 함수를 실행하는 RPC 기능의 구현이나

User/Session 등 통신과 로직을 위한 클래스를 만든 후 마지막으로 서버에서 동작하는지 실행해 봐야겠다.

(이러다가 이게 포폴이 되는 건 아닌지?)

 

[git]

github.com/sanghun219/MySerializer

 

sanghun219/MySerializer

Contribute to sanghun219/MySerializer development by creating an account on GitHub.

github.com

 

'게임 서버 프로그래밍 > 윈도우 소켓 프로그래밍' 카테고리의 다른 글

패킷과 직렬화  (0) 2021.02.15
client , server 연습 ..  (0) 2020.02.22
getaddrinfo()  (0) 2020.02.22