대신 다음 회는 좀 빨리 쓰겠습니다.
그리고,
양성평등을 위해 이번 사과의 짤은 남자 사진으로 올려드립니다.
저는 이렇듯 녀성부와 셧다운제를 찬성하는 녀성님들을 존중합니다.
1. C/S의 생명주기
Scott Meyers의 명저서, Effective C++을 한마디로 정의하지면 객체의 잘 생성해서 잘 써먹다가, 필요없는때는 잘 제거하는 차가운 도시남자의 객체 관리법이라고 할수 있습니다. 그리고 이 객체라는 것은 C++ 클래스의 객체 만이 아닌 한 인스턴스(Instance) 그 자체라고도 볼수 있습니다.
흔한 올바른 객체 제거 방법. 비단 컴퓨터 내부 뿐만 아니라 현실에도 제거를 잊은 객체들이 많습니다.
따라서 인스턴스는 나름의 객체 수명이 있습니다.
이 객체 수명은 인스턴스 자기가 결정하는 것이 아니라 OS나 자신을 Embeding하는 컨테이너에 의해 결정됩니다.
Win32 API를 처음 배울 때부터 우리는 이것을 알게 모르게 배워왔습니다.
RegisterClassEx()로 윈도우를 등록하고 CreateWindow()로 창을 생성하고 각종 메시지를 받아 해당 작업을 하다가 WM_DESTROY 메시지가 날라올때 new로 만들어진 객체들을 지워주어야 합니다.
이런 것은 비단 Win32만이 아닙니다, 아래 이미지는 아이폰 앱의 수명 주기의 일부분인데 대부분의 인스턴스의 생명주기는 이와 같은 구조를 지닙니다.
출처 : https://developer.apple.com
뻔한 이야기를 왜 하냐면 Clinet/Server의 평등한 관계라는 시각에서 볼때,
클라이언트와 서버도 이같은 네트워크 생명주기를 지닌다는 점을 강조하기 싶기 때문입니다.
다시 말해, 게임룸에 들어가기전, 로비(Lobby)에서 C/S에서 필요로 하는 객체의 초기화 과정이 필요합니다.
게임 서버가 아닌 로비 서버에서 클라이언트에 필요한 객체를 초기화 및 업데이트를 하고 올바른 정보를 갖고 게임룸으로 들어가야 합니다.
분산 서버이기 때문에 이와 같은 과정은 로비에서만 일어나고 게임룸에서는 오직 게임에만 신경쓰도록 해야합니다.
잘 돌아가고 개발이 잘되는 프로젝트는 이같이 뻔한 것들이 잘 지켜지는 프로젝트들입니다.
반도의 흔한 네트워크 게임 프로세스
위의 그림은 간단한 네트워크 게임의 프로세스를 다시 한번 간략히 그려본 것입니다.
이 그림위에 수명 주기를 다시 체크해보죠.
네트워크 역시 다른 곳에서 자주보던 그림이 나왔습니다.
비밀인데 아무 것도 안하면서 일한 척 할려면 그림을 잘그리면 됩니다.
이러한 그림 연습이 코딩하는데 도움이 되냐고 반문할수 있지만 점점 복잡해져가는 네트워크 구성에 큰 도움이 된다고 확신합니다.
이런 설계가 있으면 언제 어디서 클라이언트와 서버에 각각 정보를 채울 것인지 확연해집니다.
물론 호출되는 함수는 send() 함수와 recv() 뿐이겠지요.
이런 식의 그림 연습은 앞으로도 종종 해볼 것입니다.
4년 내내 다른 친구들 장학금 받는데 큰 기여를 한 성적이라 대학 수업은 잘 모르지만,
대학에서 OOP을 배우는 이유는 C++이나 JAVA같은 언어 자체를 배우기 위한 시간이 아니라고 생각됩니다.
하도 공부를 못해 4학년 졸업할 때 너는 프로그래밍을 못하니 일년 더 다녀보라는 말을 들을지라
제 말이 맞는지는 잘 모르겠습니다만, 제 생각은 이렇습니다.
위의 그림들에서 클라이언트와 각 서버들은 한 인스턴스 속에 들어있는 다섯개의 객체(혹은 클래스)라고 봐도 무방합니다.
Client 객체가 Path Server, Load Balance Server, Lobby Server, Game Server라는 네개의 객체에게 질의를 하는 구조이며
서버인 Lobby Server와 Game Server의 객체는 Database에 의해 전역 데이터, 혹은 공유 데이터를 교환할 것입니다.
각 객체들은 자신의 역활만을 해주고 있고 수명도 존재합니다.
즉, OOP의 고급(?)이라 할수 있는 디자인 패턴 역시도 네트워크에서 사용해볼수 있지 않을까요?
...은 지금은 너무 떡밥이 세니 본론으로 돌아가 지난 시간의 아바타 채팅을 다시 변경시켜봅시다.
2. 로비 서버의 제작, 그러나 할말이 많다.
지난 시간에 서버와 클라이언트 이외의 데이터는 로긴을 위한 사용자 테이블 뿐이었습니다.
위의 채팅서버에서 로비서버로 바뀐다면 변경할 포인트가 뭐가 있을까요?
지난 시간처럼 명세서를 꾸며봅시다.
1) 클라이언트가 로비 서버에 입장하면 자신의 정보(닉네임, 전적)를 받는다.
2) 같은 채널내 다른 유저의 정보를 받는다.
3) 다른 유저와 채팅을 할수 있다.
4) 게임룸 리스트를 볼수 있다.
4-1) 자신의 등급이 맞지 않는 룸에는 입장이 불가능하다.
5) 게임룸을 직접 만들수 있다.
5-1) 자신의 등급이 맞는 않은 룸은 만들 수 없다.
...만들어야 할 항목들이 죄다 나왔습니다.
이중 가장 중요한 것은 클라이언트 접속시 다른 유저들과 게임룸 리스트를 클라이언트에게 보여준다는 것입니다.
결론부터 말하자면 이게 생각만큼 쉽지는 않습니다.
..............
..........................................
....................................................................................
....................................................................그냥 수식이나 포스트 하고 나머지는 직접 짜보세요~ 하면서 인공지능 연재할껄.... .
지금 기분
...그래도 강의 연재 역시 "만들기"라고 본다면 시작했으면 기어이 끝장을 봐야겠죠?
먼저 필요한 것은 클라이언트와 서버에 존재할 CGameUser와 같은 단일 사용자 정의 클래스입니다.
서버는 접속 들어온 사람들만큼 이 CGameUser를 자료구조로 저장하고 있어야 합니다.
이때 속력을 위해 굳히 DBMS에 현재 로비에 접속된 유저를 저장하지는 않습니다.
당연히 로비서버 안에 저장하고 있습니다.
사용자 리스트를 뿌려줄려면 어떻게 해야 할까요?
...네 말도 안되죠. 사용자 정보는 UDP가 아닌 TCP로 보낼 중요한 데이터입니다.
줄곧 보낼수도 없거니와 새로 들어온 사용자와 나간 사용자를 다시 결정해야 합니다.
몇명 안되는 게임이라면 이 방법도 가능하겠지만 실제로 코딩해보면 상당히 지저분해 질수밖에 없습니다.
따라서 다른 방법으로,
가 있습니다.
엄청 멋있지만 척 들어도 어렵게 느껴지죠? 만약 CGameUser를 STL map에 저장하고 있다면,
send(UserMap, sizeof(UserMap)); 식으로 보낸다는 이야기입니다.
당연히 서버와 클라이언트는 사용자 관리에 같은 콜렉션을 써야 합니다.
그래서 클라이언트 코드라도 이런 부분은 서버쪽에서 만들기도 합니다.
하지만 콜렉션, 컨테이너를 통채로 보내는 것은 결코 쉬운 방법이 아니며 코딩 길이도 상당해집니다.
네, 맞습니다, 각 게임회사에는 이 방법이 자사 서버 엔진의 아주 중요한 요소중 하나입니다.
바로 객체 직렬화(Object Serialization)입니다.
2-1. 직렬화
직렬화는 서버가 아니더라도 한번쯤 들어보셨을 것입니다.
MFC를 공부하면서 "<<"로 객체를 그대로 파일에 저장하는 것이라던가, JAVA에서 ObjectOutputStream() 같은 함수죠.
단순 구조체라면 쉽습니다만 핵심은 역시 자료구조를 통채로 옮기는 것입니다.
1) XML
XML은 텍스트 기반으로 이뤄진 자료구조입니다.
CGameUser 객체 10개라도, 100개라도 XML로 쉽게 표현할 수 있습니다.
게임에서는 네트워크 프로토콜로 XML을 잘 사용하지는 않습니다. 일단 너무 큰게 문제겠지요.
대신 XML은 HTTP에 실려져 SOAP(http://www.w3.org/TR/soap/)을 구현하는데 많이 사용되며
Action Script와 맞물려 플래시 네트워크 게임에서도 많이 사용됩니다.
저는 일단 비추! 합니다.
단, 게임 말고 Facebook이나 Twitter 정보 받아 올때는 JSON보다 XML을 좋아합니다.
JSON은 사람이 읽기에는 너무 헛갈리거든요.
정말이지 XML은 사람을 위한 언어이고 JSON은 기계를 위한 언어입니다.
2) Protocol Buffer
직렬화는 만만치 않은 작업이기에 구글에서는 무료로 쓸수 있는 Protocol Buffer라는 것을 내놓았습니다.
(http://code.google.com/intl/ko-KR/apis/protocolbuffers/)
그러나 시기를 잘못 탔습니다. 구글이 Protocol Buffer를 내놓았을 때는 이미 스마트폰 열기가 시작되었고,
스마트폰에서의 평가는 좋지 못했습니다.
무엇보다 구글에서 나온 멋진 물건(?)이라는 기대와 달리 컨테이너를 자동으로 직렬화해주는 기능이 없었습니다.
SOAP이나 플래시에서 사용하는 XML보다 사실 별로 나은게 없는거죠.
(혹시 Protocol Buffer를 이용해 재미를 본 분이 있으면 반론과 경험담을 부탁드립니다.)
3) JSON
최근 인기를 가장 끌고 있는 것은 역시 JSON(JavaScript Object Notation)입니다.
(http://json.org/)
JSON은 원래 JavaScript용으로 나온 경량 데이터 교환 포맷이나 XML보다 가볍고 파서 역시 가벼워 PC와 스마트 디바이스를 가리지 않고 위엄을 떨치고 있습니다.
트위터, 페이트스북 같은 유명 SNS들은 XML과 JSON을 모두 지원하며 앱스토어 역시도 JSON을 프로토콜로 사용합니다.
게임에서 역시 마찬가지죠, 실시간 게임 뿐만 아니라 특히 SNG들은 대부분 JSON으로 작동된다고 보셔도 됩니다.
어떤 과학의
프 로 토 콜
...드립 실패했다는 거 알고 있으니 댓글로 지적하진 말아주세요.
4) boost::serialization
XML과 JSON은 그 바탕에 이기종 통신용이라는 탄생 목적을 갖고 있습니다.
윈도우, 유닉스, C++, JAVA 등 서로 다른 OS와 언어끼리 손쉽게 데이터를 교환하는 것이 목적입니다.
그래서 텍스트(UTF-8)형식으로 만들어졌죠.
아이폰 앱이라도 서버까지 Objective-C로 짜지는 않습니다, 안드로이드 앱이라도 서버까지 JAVA로 짤 필요는 없지요.
어쩌면 이런 성격이 스마트 디바이스에서 JSON을 최고의 스타로 만들어준 것인지도 모릅니다.
하지만, 같은 C++끼리라면 어떨까요?
굳히 텍스트 기반을 들고 가야할 필요는 없을 것입니다.
PC 온라인 게임이라면 서버와 클라이언트 모두 C++로 가능하고 C++가 가장 많이 사용되는 환경일 것입니다.
이런 경우에는 boost::serialization 가 깔끔한 솔루션을 제공합니다.
(http://www.boost.org/doc/libs/1_49_0/libs/serialization/doc/index.html)
boost::serialization는 STL 콜렉션들을 그대로 직렬화 시켜주는 정말 멋진 라이브러리입니다.
바로 이 부분이 많은 게임회사 서버팀의 비밀중 하나였지요.
그리고 boost::serialization로 그 C++ STL 직렬화의 비밀을 누구나 사용할 수 있게 되었습니다.
2-2. 패킷 생성기, 혹은 컴파일러
이제까지 적은 내용을 한줄로 요약하면,
입니다.
하지만 아직 해결해야 할 부분이 있습니다.
지난 시간의 소스를 다시 봅시다.
//
// MFCClientDlg.cpp
// 로긴 데이터 보내는 부분
//
CLOGIN loginPack;
ZeroMemory(&loginPack, sizeof (loginPack));
loginPack.head = MSG_LOGIN;
memcpy((char *)&loginPack.id, m_strID, m_strID.GetLength());
memcpy((char *)&loginPack.pwd, m_strPWD, m_strPWD.GetLength());
CDataMessage msg;
msg.body_length(sizeof(CLOGIN));
memcpy(msg.body(), &loginPack, sizeof(CLOGIN));
msg.encode_header();
m_pClientSocket->Send( &msg,msg.length());
//
// 채팅 메시지 보내는 부분
//
CMESSAGE messagePack;
ZeroMemory(&messagePack, sizeof (messagePack));
messagePack.head = MSG_MESSAGE;
messagePack.avatar = m_iMyAvatar;
memcpy((char *)&messagePack.id, m_strMyID, m_strMyID.GetLength());
memcpy((char *)&messagePack.message, strSendData, strSendData.GetLength());
CDataMessage msg;
ZeroMemory(&msg, sizeof(CDataMessage));
msg.body_length(sizeof(CMESSAGE));
memcpy(msg.body(), &messagePack, sizeof(CMESSAGE));
msg.encode_header();
m_pClientSocket->Send( &msg, msg.length());
무언가 코드가 상당히 지저분하지요, 그리고 중복되는 코드가 많습니다.
이는 분명 완전히 자동화 할수 있습니다.
처음부터 위의 코드를 만들지 않고,
SendLogin([in] DWORD head,[in] string id, [in] stl::string pwd);
SendChat([in] DWORD head,[in] DWPRD avatiar, [in] strl::string message);
SendLogin()과 SendChat()이라는 이름을 가진 함수까지 만들어서요.
그리고 수신부에도 이 두 패킷을 받아 처리하는 함수 원형을 만들어 줄수도 있겠죠, OnRecvLogin(), OnRecvChat() 같은 식으로 말입니다.
그런 역활을 해주는 것이 흔히 패킷 생성기(Packet Generator)라고 불리우는 외부 컴파일러입니다.
이에 대한 힌트는 네트워크 프로그램에서 나온 것만은 아닙니다.
CORBA, COM 같은 분산 프로그래밍에서 흔히 사용되는 방법이며 멀리서 찾을 필요없이
"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\midl.exe"이 바로 이런 역할을 합니다.
물론 midl.exe은 MS의 IDL 컴파일러로 COM 프로그래밍시 IDL 파일을 빌드시켜주는 컴파일러입니다.
(IDL : Interface Definition Language)
이것을 네트워크 프로그램용으로 만드는 것이 패킷 생성기죠, 네트워크 직렬화에 이어 각 개발사의 핵심 보물 중 하나입니다.
부르는 명칭은 달라도 하는 역활은 비슷합니다, 어느 개발사에서 사용하지는 알수 없지만 ADL.exe이라는 훌륭한 컴파일러도 있죠,^--^
다행히 http://mastercho.tistory.com/9 같은 훌륭한 개발자분을 통해 제작 기법은 많이 알려져 있습니다.
최근에는 스크립트 언어를 이용한 방법도 많이 공개가 되었는데 보다 쉽게 직렬화 + 패킷 노가다 하는 일을 줄일수가 있게 되었습니다.
아래 이미지는 imays님의 국내 서버 엔진인 넷텐션 프라우드넷의 IDL 컴파일러 장착(?)모습입니다.
이런 짤 올려도 되나요? ㅎㄷㄷ;;; 수위가 너무 센것 같으니 문제 있다면 내리겠습니다.
Custom Build Tool 옵션은 이럴때 써먹는거죠, MFC나 ATL로 ActiveX 컨트롤을 제작하더라도 IDL에 기본적으로 midl.exe가 Custom Build Tool로 붙어있는 것을 볼수 있습니다.
(참고로 해외에선 DDoS 테스트 클라이언트툴을 패킷 생성기라 부릅니다. 이런 IDL 컴파일러를 패킷 생성기라고 부르는 것이 정확한 명칭인지는 잘 모르겠습니다.)
3. 분량 조절을 실패했습니다.
네, 실제 로비 제작은 다음 시간에 하겠습니다. (-_-);;
너무 세게 떄리진 말아주세요...
이제까지 소개한 것들을 다시 정리해 보죠.
2) 서버든 클라이언트든 I/O 관련은 비동기 함수를 이용한다.
3) C/S 모델이나 P2P 모델이나 listen() accept()/connect(), send()/recv(), close()의 배치로 구현할 수 있다.
4) 서버에는 반드시 DBMS가 따라다니며 서버와 DBMS의 통신 시간 역시 네트워크 수행시간에서 C/S의 통신 이상 중요한 요소이다.
5) 분산서버 아키텍쳐는 결국 객체간 통신과 객체 수명주기와 같다.
6) 실제 게임을 만들려면 STL 컨테이너같은 콜렉션을 주고 받아야 하고 이것을 직렬화(serialization)라 부르는데 몇가지 방법이 있다.
7) 보다 뽀대나고 전문적으로 할려면 패킷을 외부 파일에 정의하면 자동으로 이벤트 함수를 만들어 주는 외부 컴파일러가 있으면 참 좋다.
휴우, 여기까지 설명하니까 이제서야 백그라운드로 이해해야할 네트워크의 기초 지식이 끝났습니다.
다음 연재에 실제 로비를 만들겠습니다.
뭘로 만들까요? 과연 IDL 컴파일러를 붙여야 할까요?
...그냥 JSON 정도로 용서해 주시면 안될까요? 멀티플랫폼이 대세니까 용서해주세요.
4. 더 생각해볼꺼리
모르시는 분들에게는 한도 끝도 없는 이야기...로 느껴질수도 있겠습니다.
각 링크는 다 클릭해보시고 모르는 용어는 구글에서 검색해 보시길 바랍니다.
직렬화 + 패킷 생성기를 직접 구현하는 것만 하더라도 서버 엔진의 절반이라 볼수 있습니다.
또한 이 작업을 보다 멋있게 구현하는게 서버 엔진 개발자들의 끝나지 않는 로망이기도 합니다.
경우에 따라서는 내부용(서버간 통신) 패킷과 외부용(클라이언트 연결) 패킷 생성기 두개를 만들 때도 있습니다.
내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/ )를 명시해주세요.
'프로그래밍' 카테고리의 다른 글
나... 나도 병렬 프로그래밍 할래!! - #4 (7) | 2012.03.10 |
---|---|
일리히트엔진 해부학 4강 - Mesh 기본개념이해 (3) | 2012.03.06 |
나... 나도 병렬 프로그래밍 할래!! - #3 (11) | 2012.03.01 |
코딩 스탠다드, 정말 이리 빡셀 필요 있나? (28) | 2012.03.01 |
표준 rand()함수보다 유용한 랜덤 생성 알고리즘 – MT, WELL (23) | 2012.02.27 |