으음, 본의 아니게 5일이나 늦었습니다.
백수 주제에 아파치 웹서버 설정 문제로 골치를 앓아 시간을 소비했습니다.
대신 오늘 포스트는 가장 실용적인 포스트와 예제가 될 것입니다.

여친 만들기는 여전히 노력조차 못하고 있는데, 도대체 왜 이렇게 사나... 생각중입니다.
최근 고등학교때 동기, 후배분, 선생님을 뵙고 오니 더 복잡한 생각이 듭니다.

이번엔 다리 휘었다는 댓글은 안달리길 바라며.

이번엔 다리 휘었다는 댓글은 안달리길 바라며.

오늘의 주제는 데이터베이스(Database)와 그것을 이용한 프로젝트입니다.
그런데 이상하게도 어느 게임 관련 서적에도 DBMS에 대한 이야기는 거의 나오질 않습니다.
먹고 사는데 중요한 정보는 쉽게 노출되지 않아서 일까요?
하지만 게임 업계에 근무하시는 분들은 이 DBMS야말로 핵심 요소라는 것을 잘 알고 계실 겁니다.

최근 KGC에서 N문사의 강산아님이 DBMS와 SQL 최적화에 대한 이야기를 해주시고 있는 것은,
그런 의미에서 아주 좋은 발전이라 생각합니다(서평을 바라신다면 한권 보내주시길 바랍니다. ^^).

열심히 읽어드리겠음.

열심히 읽어드리겠음.


자, 그럼 DBMS는 게임에서 어디에 쓰일까요?

1) 멤버쉽 전체 - 가입, 탈퇴, 로긴, 친구, 길드, 아바타...
2) 게임 컨텐츠 - 경험치, 레벨, 아이템, 공격력, 전적...
3) 몹정보 - 몹의 경험치, 레벨, 아이템, 공격력, 전적...
4) 사용자 패턴 분석
5) 그외 "정보"라고 할수 있는 모든 것을 담을 때

네, 다시 말해 게임과 서비스의 모든 것을 담고 있는 곳이 DBMS입니다.

또한 DBMS는 다양한 서버들에 있어 가장 쉬운 서버간 다리(brige)와 동기화 객체 역활을 해줍니다.

보통 회원 가입은 웹페이지(웹서버)에서 받습니다만, 게임 정보는 게임 서버를 이용합니다.
별것 아닌 단순한 형태이지만, 이미 한 DB에게 웹서버와 게임서버라는 역활이 다른 두개의 서버가 연결이 되었고
아무 쉬운 방법으로 두 서버가 한 사용자에 대한 정보를 공유한 것입니다.

그리고 이 웹서버와 게임 서버를 각각의 쓰레드 객체라고 생각하고 DB를 한 유저에 대한 유저 객체라고 놓고 본다면,
유저 객체에서 트랜잭션(transaction)을 이용해 쓰레드 동기화를 구현하고 있는 셈입니다.
이런 관점이 아키텍트로 나가는 첫 시점이라 생각합니다.

1. DBMS 기초
DB에 대해 익숙하신 분도 있고, 익숙하지 않은 분들도 많을 것입니다.
특히 학생 때에 DB에 대해 관심을 가지는 분들이 적습니다. 아마 비쥬얼한 것이 없고 DB를 사용하는 프로젝트도 없기 때문일 것입니다.
그래서 설명을 하긴 해야하는데...




.....




..................





......네, 코드도 재활용하는데 글이라고 재활용 못하겠습니까?
억지로 찾은 아래의 링크로 DB와 DBMS를 이용하는 법에 대한 내용은 날로 먹겠습니다.

날로 먹으니 더 맛있네요~

날로 먹으니 더 맛있네요~

1편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039130809
2편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039131033
3편 http://www.zdnet.co.kr/news/news_view.asp?artice_id=00000039131490

그러고 보니;;;;;;;;;;


날로 먹은 위의 글은, 당시 테마가 MFC라서 MFC로 되어져있지만, MFC가 아니더래도 똑같습니다.

만약 여기에 더 추가한다면 ADO.NET과 요즘 유행(?)하는 NoSQL에 대한 이야기를 적어볼까 합니다.
그러나 ADO.NET은 DataSet을 비롯한 강력한 기능에도 불구하고 닷넷 프레임워크를 필요로 한다는 점이 결정의 걸림돌이 됩니다.
즉 ADO.NET을 이용한다면 서버도 C#을 써야 합니다. Managed C++ 따윈 잊으십시요.

또한 NoSQL은 트랜잭션 처리와 보안과 안정성에 있어서 연구(그리고 직접 뜯어고치는 재빌드 필수)가 더 필요하다고 봅니다.
BLOB을 대체하기 위한 방법으로는 좋으나 마냥 NoSQL이 진리다~!! 라고 생각하는 것은 아니라고 봅니다.
주위에 NoSQL이 뜬다고 우리도 걍 NoSQL로 개발해~ NoSQL로 뒤짚어~~라는 분 있으면 이 문장을 보여주세요.
그래도 말 안들으면 딴 회사 가세요~~~
참고로 저는 mongoDB을 DB계의 UDP라고 생각합니다. 현존 관계형 DBMS가 TCP라면 mongoDB은 딱 UDP 같은 느낌입니다.

본격 Native C++ 강좌인 이 연재에서는 ADO를 이용하여 DB를 연결하겠습니다.

2. Winsock과 ADO를 이용한 아바타 채팅 시스템
간만에 실전 코드로 돌아가봅시다.
우리가 만들 것은 소켓과 DB를 이용한 로그인 + 아바타 채팅 시스템입니다.
로긴에 성공하면 DB에 저장된 자신의 아바타가 화면에 나오며 상대와 채팅을 할때면 대화를 보낸 사람의 아바타가 표시됩니다.

프로젝트 명세서

1) DB에는 ID와 PWD, 그리고 사용자 이름과 아바타가 있다.
2) 클라이언트에는 서버에게 ID와 PWD를 보낸다.
3) 서버는 클라이언트가 보낸 ID와 PWD를 DBMS에 질의 한다. 이때 DB 연결 방식은 ADO를 이용한다.
4) 로긴에 실패하면 실패 패킷을, 성공하면 사용자의 추가 데이터(이름, 아바타)를 보낸다.
5) 클라이언트가 보내는 채팅 메시지에는 아바타 정보가 포함된다.


명세서가 나왔으니 가장 먼저 DB를 만들어 봅시다.
MS-SQL Server 같은 것이 있으면 좋겠지만 대부분 무리일 것이니 위의 강좌처럼 MS-Access를 이용하겠습니다.
Access는 Excel에게 밀려 Office를 설치하며 잊으시는 분도 많을 것입니다.
그러나 Access는 MS-SQL Server를 대신하여 공부하기에 정말 좋은 개인용 DBMS입니다.
MS-SQL Server 로 마이그레이션도 아주 훌륭합니다.

이런 이야기를 왜 드리냐면, 수많은 게임 회사들은 전담 DBA가 없는 경우가 많기 때문입니다.
초기 개발단계나 규모가 작은 개발사라면 서버 개발자가 DBMS를 만질 수 밖에 없는 안타까움이 있습니다(...가끔 SE질도 합니다.).
신입 서버 개발자라면 이력서에 자신있는 DBMS 하나를 꼭 적어보시길 바랍니다.

2,1 Database 설계
넹, 딸랑 한 테이블입니다.
JOIN이고 머고 필요없습니다. 이 강좌가 SQL 강좌는 아니니까요.

꼴랑 필드 4개

꼴랑 필드 4개


 

ID와 PWD를 소켓으로 받아 NAME과 AVATRA를 내려보냅니다.

ID와 PWD를 소켓으로 받아 NAME과 AVATRA를 내려보냅니다.


별다른 설명이 없어도 어떻게 사용될지 보입니다.
서버에서 요청할 SQL문은 SELECT NAME, AVATAR FROM USER WHERE ID='test1' AND PWD ='aaaa' 뿐입니다.
BOF랑 EOF가 같다면 당연히 로긴 실패가 되는 것이고요.
회원 가입과 아바타 설정은 웹프로그래머분이 웹서버에서 받아주세요~

아, 그리고 가급적 DBMS에 질의는 생SQL문이 아니라 반드시 Stored Procedure를 써서 접근하는 습관이 중요합니다.
그런데 저는 이미 습관화 되어 있으므로 이번 예제에는 걍 생SQL문을 날렸습니다.
이는 단순히 SP가 빠르다는 이유가 아니라,
정기적으로 DBMS의 성능을 테스트(Query Profile)하는데 생SQL문을 쓰면 제대로 분석하기 힘들기 때문입니다.

2.2 ADO를 이용한 DB접근
이번 회 예제에는 가장 기본적인 형태의 ADO 클래스가 들어있습니다.
이미 다이렉트X를 날고 기는 독자분들에게 COM 컴포넌트 인터페이스 얻어오는 법은 장난일꺼라 생각되어 회원정보를 질의하는 부분만 보겠습니다.

if (S_OK == rad.RAConnect(_T("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=..\\DB\\MyDB.mdb;")))
{
oRs = rad.RAGetDisconnectedRs(strQueryLogon.c_str());
CComVariant val;
if (oRs)
{
while (!oRs->EndOfFile)
{
// Get Name
val = oRs->Fields->Item[_variant_t(_T("NAME"))]->Value;
if (val.vt != (VT_NULL))
{
printf("NAME : %s\n",(char*)_bstr_t(val));
m_strName = (char*)_bstr_t(val);
}

// Get Avatar
val = oRs->Fields->Item[_variant_t(_T("AVATAR"))]->Value;
if (val.vt != (VT_NULL))
{
printf("AVATAR : %s\n",(char*)_bstr_t(val));
val.ChangeType( VT_I4 );
m_iAvatar = val.lVal;
}
bIsFind = TRUE;

oRs->MoveNext();
}
oRs->Release();
oRs->Close();
}
}

참 쉽죠?

ADO 연결 문자셋으로 MDB 파일에 접근한 후, Recordset 객체를 다시 만들어 SELECT의 결과값을 획득합니다.
이때 귀찮은 것이 COM 변수를 일반 변수로 변환하는 것일 겁니다.
NAME은 문자형이므로 _bstr_t를 그대로 변환하였는데 AVATAR는 INT형이라 CComVariant 에서 제공하느 ChangeType()으로 변환했습니다.
COM 변수를 변환할때는 atoi()같은 고전 함수보다 COM에서 제공하는 방법을 사용하는 것이 안전합니다.

...사실 요즘 COM 공부하시는 분들 별로 없으리라 생각됩니다.
예전에 게임 아카데미에서 COM 강의를 해보니 학생 분들 모두 전멸을 하여 아주 행복했습니다.
하지만 윈도우용 함수는 크게 WIn32 API와 COM으로 구분됩니다. SH로 시작하는 쉘함수들 역시 COM의 한 부분입니다.
닷넷 프레임워크 역시 COM과 XML의 조합입니다.
이중 디스패치 구현해가며 COM 컴포넌트를 직접 만드는건 시대에 뒤떨어지는 일이라 하더라도
기존 COM 컴포넌트를 이용하는 것이 어려우면 막강한 윈도우 OS를 반 밖에 사용하지 못하는 것이라 생각됩니다.

2.3 패킷 설계
DB와 ADO를 극복하고 가장 재밌는 부분으로 돌아왔습니다.
먼저 이 프로젝트에 사용될 패킷을 적어봅시다.

//
// MyPacket.h
//
#define MSG_LOGIN 1000
#define MSG_LOGINFAIL 1001
#define MSG_LOGINSUCC 1002
#define MSG_MESSAGE 1003

// 헤드 검출용 C, S
class CGENERIC
{
public:
DWORD head;
};

// 로긴 패킷 C->S
class CLOGIN : public CGENERIC
{
public:
char id[20];
char pwd[20];
};

// 로긴 실패 S->C
class CLOGFAIL : public CGENERIC
{
public:
};

// 로긴 성공 S->C
class CLOGSUCC : public CGENERIC
{
public:
char id[20];
DWORD avatar;
char name[20];
};

// 메시지 C->S->C
class CMESSAGE : public CGENERIC
{
public:
char id[20];
DWORD avatar;
char message[500];
};

전부 고정 패킷입니다. 아직 string이나 vector를 그래도 송수신하게 해주는 패킷 생성기를 만들지 않았으니까요.
누군가 패킷 생성기 대신 포스팅 해주실 분 없나요?
아직 패킷수도 많지 않으니까 패킷 클래스 이름에 송신용, 수신용, 알림용 네이밍 규격을 붙이지 않았습니다.

점점 포스트가 읽기 어려워지고 있습니다.

점점 포스트가 읽기 어려워지고 있습니다.

모든 패킷은 CGENERIC을 상속받고 있습니다.
일차적으로 모든 패킷은 CHAR에서 CGENERIC으로 캐스팅 되어 어떤 패킷인지를 분석하게 됩니다.
그런데 CGENERIC에 패킷 판독용 DWORD만 있고 실제 데이터 크기를 담은 정보는 없군요?
1회 포스팅 때 나갔던 소스를 보시면 CDataMessage라는 버퍼용 클래스가 있고 그것에서 앞부분 4Byte를 패킷 사이즈용으로 사용하고 있습니다. 즉, 모든 패킷은 CDataMessage로 다시 감싸는 구조로 되어있습니다.

명확한 설명을 위해 그림으로 다시 그려보죠.

CLOGIN 패킷으로 접속과 함께 로긴 정보인 ID, PWD를 보냅니다.

CLOGIN 패킷으로 접속과 함께 로긴 정보인 ID, PWD를 보냅니다.


 

DB 질의 결과에 따라 성공, 실패를 보냅니다. 성공시 NAME과 AVATAR를 보내며 실패면 클라이언트는 접속을 끊습니다.

DB 질의 결과에 따라 성공, 실패를 보냅니다. 성공시 NAME과 AVATAR를 보내며 실패면 클라이언트는 접속을 끊습니다.

이제 다른 클라이언트B가 접속을 합니다.

이제 다른 클라이언트B가 접속을 합니다.


 

누군가 접속을 성공하면 서버는 모두에게 CLOGINSUCC을 보냅니다. 이미 연결되어 있던 클라이언트는 이 정보를 누군가 입장했다고 판단합니다.

누군가 접속을 성공하면 서버는 모두에게 CLOGINSUCC을 보냅니다. 이미 연결되어 있던 클라이언트는 이 정보를 누군가 입장했다고 판단합니다.


 

서버는 단순히 CMESSAGE 패킷을 중계만 합니다.

서버는 단순히 CMESSAGE 패킷을 중계만 합니다.


2.4 클라이언트 구현
아래는 클라이언트의 핵심 구현부 입니다.
중요한 것은 로긴 성공과 메시지에 있어 같은 패킷을 이용하여 클라이언트에서 판정하고 있다는 것입니다.
패킷 처리 부분은 이미 서버와 클라이언트가 비슷하므로 서버는 별다른 설명이 필요 없을 것입니다.

// 일단 받은 패킷의 종류를 알아본다.
CGENERIC* pGenericPack = (CGENERIC*)szRecvData;


// 해당 패킷 처리로 따로따로 보낸다.
switch( pGenericPack->head )
{
case MSG_LOGINFAIL :
{
pMainWnd->OnCloseToServer(TRUE);
pMainWnd->AddToLog(_T("ID나 비밀번호를 체크하세요."));
}
break;

case MSG_LOGINSUCC :
{
CLOGSUCC* pLoginSucc = (CLOGSUCC *)szRecvData;
pLoginSucc->avatar;
pLoginSucc->name;
pLoginSucc->id;
CString strUserName, strID;

if(pMainWnd->m_strMyID.IsEmpty())
{
strUserName = pLoginSucc->name;
strUserName = strUserName + " 님 어서오세요.(By Server).";
pMainWnd->AddToLog(strUserName);

pMainWnd->m_iMyAvatar = pLoginSucc->avatar;
TRACE("%d\n", pMainWnd->m_iMyAvatar);

pMainWnd->m_strMyID = pLoginSucc->id;
pMainWnd->DrawMyAvatar();
}
else
{
strUserName = pLoginSucc->name;
strUserName = strUserName + " 님이 입장하셨습니다.(By Server).";
pMainWnd->AddToLog(strUserName);
}
}
break;

case MSG_MESSAGE:
{
CMESSAGE* pMessagePack= (CMESSAGE*)szRecvData;
CString strRecvMessage, strID;
strRecvMessage.Format("%s : %s", pMessagePack->id, pMessagePack->message);
strID = pMessagePack->id;

pMainWnd->AddToLog(strRecvMessage);

if ( pMainWnd->m_strID != strID)
{
pMainWnd->m_iOtherAvatar = (int)pMessagePack->avatar;
pMainWnd->DrawOtherAvatar(); // 상대 캐릭터
}

}break;



3. 인증샷, 하지만...
자, 이렇게 완성되었습니다.

덕내쩌는 클라이언트라 덕후가 아니면 받지 못함 ㅋㅋㅋㅋㅋㅋㅋ

덕내쩌는 클라이언트라 덕후가 아니면 받지 못함 ㅋㅋㅋㅋㅋㅋㅋ


이 포스팅을 읽으며 한가지 새로운 사실을 알수가 있는데,
서버에서는 별다른 연산이 없었지만 나름 아바타가 포함된 그럴싸한 채팅 시스템이 만들어졌습니다.
이는 DB라는 자료구조가 존재하기 때문이죠.
사실 게임 서버에서 타게팅, 넌타게팅, 3D 위치값 같은 게임 냄새 물씬 나는 요소는 결코 절대적인 비중을 차지 하지 않습니다.
개발자의 시선에서 게임 서버는 DBMS의 정보를 클라이언트에게 보내주고, 또한 클라이언트가 보낸 정보를 DBMS에 쌓기 위해 존재합니다.

3회만에 이 프로젝트를 포스팅 할 수 있어 무척 기쁩니다.
소스를 잘 만들었다는 이야기가 아니라, DB가 투하된 실전용 클라이언트/서버의 핵심을 선보였다는 것이며
그와 동시에 클라이언트/서버의 모든 문제점을 고스란히 알수 있는 프로젝트이기 때문입니다.

문제점이야 밤새도록 적을 수 있겠지만 우선 몇가지만 적어보죠.

4. 더 생각해볼 꺼리
오늘은 더 생각해볼 꺼리가 참 많네요.

1) 서로 채팅을 할때 NAME 대신 ID가 보여지고 있습니다. 기껏 만든 NAME을 자기만 보고 있는 셈이죠.
이를 ID와 NAME이 동시에 보여지도록 해봅시다.
또한 그렇게 할때 어떻게 하면 가장 작은 사이즈의 패킷을 보낼 수 있을까요?

2) 이 프로젝트에는 현재 치명적인 버그(?)가 있습니다!
한 클라이언트가 로긴을 실패하면 CLOGINFAIL 패킷을 받아 모든 클라이언트가 접속이 끊어지고 맙니다.
이 문제를 어떻게 해결할수 있을까요?
클라이언트에서 고치는 방법(패킷 수정 포함)과 서버에서 해결하는 방법, 각각 두가지 방법을 생각해봅시다.

3) DB 연결
현재 서버는 CLOGIN 패킷을 받을때 ADO로 DBMS에 연결과 질의, 그리고 닫기를 수행하고 있습니다.
만약 MMORPG처럼 다수의 사용자가 접속을 한다면,
아니 지금 프로젝트에서도 모든 사용자의 채팅을 서버에 저장한다면(법적인 문제를 떠나) DBMS는 뻗게 됩니다.
어떻게 하면 DBMS에게 무리를 주지 않으며 편안한 서비스를 할 수 있을까요?
단순히 DBMS 폴링도 좋은 방법이지만 그 규모조차 넘어서면 어떻게 할까요?
또한 게임 서버뿐만 아니라 웹서버에서도 사용자 정보를 갱신한다면(닉네임 변경) 어떤 문제와 해결점이 있을까요?

아, 그리고 지금 현재에도 이 프로젝트에서 DB질의 시간이 가장 많은 시간을 소모하고 있을 것입니다.

4) 서버와 월드
이 프로젝트가 인기가 좋아 동접 20만명(...)의 프로젝트가 되었다면 합시다.
그래서 서버를 늘립니다.
서버를 늘렸을때 각각 다른 서버에 있는 사용자들끼리 채팅을 하게 해야할까요?
아니면 깔끔하게 포기할까요?

전자라면 서버"들"을 어떻게 구성해야 할까요?
후자라면 그게 바로 한 월드가 되며 다른 월드의 친구와는 게임을 당연히 할수 없을 것입니다.
그럼에도 불구하고 다른 친구에게 쪽지라도 보내고 싶다는 고갱의 건의가 왔고 기획팀에서 해달라고 하면 어떻게 할까요?

5) 지난 시간 소개해드린 WireShark로 보면 ID와 PWD는 그대로 드러납니다.
오픈하자마자 중국계 IP들의 놀이터가 되겠군요.
웹에서 로긴이 아닌 게임에서 로긴이라면 어떤 방식으로 숨겨야 할까요?

1), 2)는 네트워크 공부를 처음하는 분이라도 해결할수 있는 부분이고
나머지는 현업을 준비하는 분들에게 알맞은 토픽입니다.

이것으로 이번 포스팅을 마치도록 하겠습니다.
늦어져서 다시 한번 죄송하고요, 이번 포스팅의 서버에서는 ADO 질의 이외에는 일체의 로직이 없었습니다.
다음 시간에는 서버에 로직과 자료구조를 투척해보겠습니다. 바로 게임으로 들어가는 관문인 로비(Lobby)를 만들어볼까 합니다.

언젠간 이런 날이... .

언젠간 이런 날이... .

내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/ )를 명시해주세요.

 

 

 

출처 :  http://rhea.pe.kr/500

본의 아니게 업계에 알려진 Rhea君의 건강은 지금은 꽤나 좋아졌습니다.
각종 디스크 상담이 쇄도하고 있습니다.
그러나 아직 아래 사진과 같은 선물을 줄 여자친구를 만들지 못했으므로 조금은 더 쉬어야합니다.
진정한 남자로 선택을 받는 시간이 한달도 채 남지 않았습니다.

진정한 남자로 선택을 받는 시간이 한달도 채 남지 않았습니다.

많은 게임 업체분들이 백수인 저를 위해 사심없고 우정 어린, 밥과 술을 사주셨는데,
현재 삼성동 사옥 돋는 N모사만 밥과 술과 향응을 제공하지 않았습니다.
여전히 성장기라 배가 고픕니다. 참고로 저는 소고기보다 물고기류를 더 좋아합니다(삼성동 오징어세상에서 오징어 통찜이나.).
그리고 백수를 갈취한 선릉역 N모사와 오리역 N모사 직원느님을 고발합니다(...뻥).

1. 네트워크 아키텍쳐
익히 알려져 있는대로, 소켓 속에는 주요 함수가 몇개 없습니다.
클라이언트를 기준으로 하면 연결을 위한 connect(), 주고받는 send()/recv(), 연결해제를 위한 close()뿐입니다.
그러나 많은 게임들은 이 몇안되는 단순한 함수들로 클라이언트/서버(이하 C/S)과 Peer-To-Peer(이하 P2P)를 오가는
마법과 같은 네트워크들을 보여줍니다.

1.1 모든 것은 서버로 C/S
C/S 모델은 100% 서버 지향 네트워크 아키텍쳐입니다.
사용자는 게임 클라이언트 실행시 서버에 의해 게임 플레이가 결정됩니다.

가장 좋은 예는 MMORPG와 맞고/포커같은 카드류 게임입니다.
C/S 모델은 MMORPG가 발전한 우리 나라에서 크게 발전된 모델입니다(기술뿐만 아니라 운영도 포함해서).

C/S 모델의 장점은 보안에 강력하다는 점입니다.
모든 결정이 서버에 의해 판가름나므로 해킹에 강합니다.
만약 해킹을 통해 어떤 짓을 했다고 하더라도 다음날 아침이면 들통납니다.
대부분의 온라인 게임 회사들일 경우, 출근하자마자 볼수 있는 것이 어제 급격히 레벨이 오른 유저의 전적입니다.
당장 제재가 가해지지는 않지만 어김없이 들키고 맙니다.
...나머지는 서버개발자가 아닌, 이름은 친절하지만 사실은 무서운 아저씨들인 고객만족팀에서 해결해주시곘죠.

여기서 한가지 명확하게 짚고 넘어갈 것이 있습니다.

우선 "클라이언트/호스트"와 "클라이언트/서버"의 개념입니다.
우선 우리가 익숙한 PC통신이나 UNIX를 생각해봅시다.
PC통신화면이나 텔넷으로 접속한 UNIX 화면은 100% 서버에서 던져주는 TEXT정보입니다.
이 당시에는 "서버"라는 용어 대신 "호스트(host)"라는 용어를 썼으며 이때는 반드시 로그인(Log in)이라는 용어를 썼습니다.

이 의미는 클라이언트에는 0.1%의 할일도 없으며 모든 것을 서버에서 전해주는 것을 받아 그 안에서 작업한다는 것입니다.
돌이켜보면 PC통신에 사용된 프로그램을 가르켜 단말기 예뮬레이터(Terminal Emulator)라고 불렀습니다.
PC가 아닙니다. 단말기라는 것은 PC가 아니라 Host에 접속해서 Host가 보내주는 화면과 텍스트 기반의 요청을 보내는 것 뿐입니다.

따라서 클라이언트는 Host 속으로 들어간다는 의미에서 로그인(Log in)이라 불렀습니다.

저승 로긴하시는 마미성님

저승 로긴하시는 마미성님


그리고 단말기는 사라지고 PC가 흔해진 시대에 들어서며 클라이언트/서버의 시대가 왔습니다.
클라이언트/서버의 의미는 클라이언트와 서버가 "평등"하다는 의미이며 하나의 작업을 처리하기 위해
클라이언트 컴퓨터와 서버 컴퓨터가 협력해서 작업을 한다는 의미입니다.

그래서 로그온(Log on)이라 부릅니다.

윈도우 역시 Log on/Log off 라는 표현을 씁니다.
이는 커널이라는 서버와 애플리케이션이라는 클라이언트가 협업한다는 의미입니다.
오래전 윈도우95가 등장할때, "본격 32비트 클라이언트/서버 OS"라고 했는데
일부 사람들은 윈도우95에 소켓이 내장되어 그렇게 부른다고 했지만 그게 아니라 커널과 애플리케이션이 통신한다는 의미입니다.

네트워크에서 클라이언트/서버 역시 마찬가지입니다.
웹페이지를 볼까요?
흔히보는 홈페이지 가입할때, 특정 항목을 제대로 적지 않았으면 다시 적으라고 알려줍니다.
이는 서버에서 검사할 수도 있고, 클라이언트에서 자바스크립트로 검사할 수도 있습니다.
어느 쪽이든 가능합니다.

하지만 서버 측에서 검사할려면 일단 데이터를 서버로 넘겨야 합니다.
트래픽 측면을 본다면 자바스크립트로 처리하는게 효율성에서 맞습니다.
그런데 또 문제가 있습니다.

비쥬얼 스튜디오같은 자바스크립트 디버깅 툴만 있으면 자바스크립트에서 처리하는 양식 검사 따위는 아주 쉽게 무시해 버릴수 있습니다!
이렇게 되면 효율성을 무시하고 서버에서 검사를 해야 합니다.
도대체 뭐가 맞는 것일까요?

홈페이지 이야기를 하니 헛갈린다면 게임으로 넘어옵시다.
C/S기반의 슈팅 게임을 있다고 할때 공격 성공 판정을 서버에서 해야할까요, 클라이언트에서 해야 할까요?
서버에서 하자니 매 키입력마다 네트워크를 타야 합니다.
클라이언트에서 하자니 검증이 불가능합니다.

정답은 그때그때 다르다이고 서버에서 처리할 것과 클라이언트에서 처리할 것을 알맞게 나눠야 한다는 것입니다.
앞으로 게임을 만들며 이 문제를 실전에서 다시 짚을 것입니다.

1.2 우리끼리 놀자 P2P
P2P는 유저들끼리 데이터를 주고 받으며 게임을 진행하는 방식입니다. P2P의 대표적인 게임이라면 스타크래프트와 FPS게임들일 것입니다.
그러나 MMORPG 위주의 우리 나라에서는 꽤나 오랜 시간 P2P 기술이 홀대를 받았습니다.
보안에 취약하고 지금처럼 FPS가 유행하기 전까지는 P2P에 어울리는 게임이 없었기 때문입니다.

P2P의 장점은 로비를 제외하면 대용량 서버를 구축할 일이 없다는 것입니다.
단점은 클라이언트가 결국 서버와 클라이언트, 모두를 맡아야 하므로 네트워크 측면에서 만들기가 좀더 어렵다는 점입니다.
특히 P2P는 (플레이어수-1)((플레이어수-1)-1) /2 만큼 연결을 가져야 하므로 사용자가 늘어날때마다 디버깅이 힘들어집니다.
그리고 절대 잊을수 없는 문제가 있지요, 바로 NAT(Network Address Translator)이라고 불리는 공유기 문제입니다.
공유기 아래에 있는 Peer는 서버로 작동할 수 없습니다. 그래서 홀펀칭(hole punching)이라는 방법이 거론됩니다.

그러나 현실적으로 P2P만으로 게임 서비스를 만들기는 불가능합니다.
친구들을 모으고 채팅을 하며 어떤 방이 있는지 목록을 보기 위해서는 반드시 중앙에 서버가 있어야 합니다.
스타크래프트로 치면 바로 배틀넷(battle.net)이죠.

구분 장단점 주 프로토콜 게임 스타일
C/S 장점 : 보안에 유리
단점 : 높은 트래픽, 대용량 서버 기술 필요
TCP가 주로 사용되나 UDP도 사용 온라인 게임
P2P 장점 : 대용량서버 필요없음
단점 : 복잡성 증가, NAT 홀펀칭 필요
서버연결은 TCP,
P2P에는 UDP가 주로 사용
네트워크 게임
<표1. C/S와 P2P 정리>

온라인 게임과 네트워크게임의 차이도 이번에 짚고 넘어가죠.
온라인 게임은 사용자의 대부분의 MMORPG처럼 접속이 없어도 서버 시간이 흐르는 게임을 의미합니다. 내가 서버에 접속하지 않아도 서버는 이벤트가 진행되고 밤낮이 바뀝니다. 네트워크 게임은 방을 만들어 진행하는 단판승부 위주의 게임을 의미합니다. 스타크래프트, 맞고 같은 게임이 대표적이죠.
이 강좌의 제목은 아무 생각없이 네트워크 게임 튜터리얼이라 지었는데, 어떤 예제가 나오더라도 그냥 무시해주시길 바랍니다. ㅠㅠ

2. 네트워크 아키텍쳐의 실전 사례 분석

주의!
이 포스트에 언급된 게임들은 반드시 그렇게 작동된다고 책임지지 않습니다.
특히 해당 IP와 Port로 접속을 거는 것도 결코 책임지지 않습니다.

오래 전부터 네트워크를 분석하는 툴은 많았습니다.
기본 프로그램인 netstat도 훌륭한 도구이며(그 유명한 TCP/IP Illustrated 도 netstat로 실습하고 있습니다.)
약간의 관심만 가진다면 어떤 유명 게임이라도 클라이언트 상에서 어떤 과정을 거치는지 쉽게 확인할수 있습니다.
악용하지 않는다면 정말 좋은 공부가 됩니다.

이런 도구들이 있는데 유명 게임들의 네트워크 아키텍쳐를 전혀 모른다는 말은 개발자로써 관심이 없다는 말일 겁니다.
가끔 면접 볼때 무슨 게임에 몇년을 투자했다는 열정에 대한 자랑을 듣지만,
그 게임의 라이팅이나 네트워크를 짐작도 못하고 있으면 공돌이로썬 안타깝게 느껴집니다.

진짜 울고 싶은건 질문하는 사람이라구!!

진짜 울고 싶은건 질문하는 사람이라구!!


이 강좌에서는 비쥬얼스튜디오 이외에 앞으로 두가지 툴을 자주 쓰겠습니다.

1) TCPView
http://technet.microsoft.com/en-us/sysinternals/bb897437
오래전 MS에게 인수된 sysinternals의 TCPView입니다.
netstat과 거의 흡사하지만 GUI라서 보다 편리합니다.
예전에 날잡아 서버 개발자 PC를 검사하며 이것이 안 설치되어 있으면 갈구시던 어떤 실장님이 생각나네요(제가 아닙니다.).
그외 FileMon, RegMon도 클라이언트 플랫폼 개발자들에게는 필수 도구입니다.

2) WireShark
http://www.wireshark.org/
pcap 라이브러리를 사용하는 패킷 검사툴입니다.
사실 이것과 노가다만 있으면 어떤 게임이라도 프리서버를 만들어 내는 일은 가능합니다.
불가능 하다고요?
우리가 게임을 만들듯, 수백명이 한사무실에 모여 각자 팀별로 패킷만 분석한다면 어떨까요?
대륙의 판타지는 이렇게 만들어집니다.

2.1 C/S 모델
여기서 말하고픈 것은 C/S 아키텍쳐라도 절대로 서버 1대로만 접속하지 않는다는 점입니다.
MMORPG도 그 수많은 종류만큼 다양한 네트워크 아키텍쳐가 있습니다만, 간단한 카드류 게임으로 서버에 어떻게 접속하는지 보겠습니다.

로비에 접속

로비에 접속


 

로비서버로 짐작되는 XXX.XXX.XXX.214에 접속

로비서버로 짐작되는 XXX.XXX.XXX.214에 접속


 

로비에서 게임룸으로 들어왔습니다.

로비에서 게임룸으로 들어왔습니다.


 

게임서버로 짐작되는 XXX.XXX.XXX.172에 접속

게임서버로 짐작되는 XXX.XXX.XXX.172에 접속


이렇듯 C/S 모델이라도 최소한 두개의 서버를 번갈아 접속했다는 사실을 알수 있습니다.
그리고 TCP 이외에 UDP도 사용하고 있고요.
UDP는 연결지향이 아니니까 책에서 본대로 접속은 이뤄지지 않았습니다.

물론 서버는 이 두개 뿐만이 아닙니다.
직접 해보시면 서버 주소가 다를 것이고 테스트해볼때마다 다를 수도 있습니다.
이 말은 실제 각 로비서버와 각 게임서버는 여러 대가 있다는 의미입니다.
그리고 다른 서버로 접속했다 하더라도 같은 방에 있어야 합니다.

이를 위해서는

1) 서버간 통신을 해주는 서버가 필요
2) 어떤 서버로 연결할 것인지 알려주는 서버가 필요

라는 결론이 나옵니다. 그 구조를 클라이언트에서 알수는 없습니다만, 아키텍쳐는 나온 것이죠.
이러한 유추를 통해 각기 다른 월드나 지역으로 구분하는데도 유용하게 사용할수 있습니다.

2.2 P2P 모델

P2P의 대표 모델인 스타크래프트 배틀넷을 분석해보죠.
스타크래프트의 P2P 모델은 DirectPlay의 P2P 모델과 일치합니다.
DirectPlay를 그대로 가져다 쓴 것이라 여겨집니다.

P2P에서는 크게 자신이 세션(방)을 만드는 것과 다른 사람이 만든 세션을 검색해서 들어가는 두가지로 나뉩니다.
각 단계에서 소켓에서는 어떤 함수가 불려지는지 정리해보지요.

1) 내가 방을 만들어 접속하는 경우
1-1) TCP 소켓 생성

Winsock은 TCP/IP 이외에 다른 프로토콜도 사용가능합니다. Battle.net을 선택하는 순간 TCP/IP를 위한 준비를 합니다.

Winsock은 TCP/IP 이외에 다른 프로토콜도 사용가능합니다. Battle.net을 선택하는 순간 TCP/IP를 위한 준비를 합니다.

pSocket->Create(TCP); 와 같은 형태로 함수가 호출될 겁니다.

1-2) Connect()

로긴

로긴

버전업 체크 서버와 버전을 확인을 한후 로긴을 합니다.
이때 pSocket->Connect(배틀넷, 스타크래프트_확장팩); 형식으로 접속을 하게 됩니다.

1-3) recv()

배틀넷 서버에 접속

배틀넷 서버에 접속


배틀넷 서버에 접속했습니다.
recv() 함수가 작동되며 공지사항이 날라옵니다.

1-4) 로컬에 서버 소켓 생성

방을 만들면...

방을 만들면...


 

P2P를 위해 로컬에 서버가 만들어집니다.

P2P를 위해 로컬에 서버가 만들어집니다.

세션이 만들어졌습니다.
이때가 무척 중요합니다.
내가 세션을 만드는 것도 중요하지만 다른 유저들이 내가 만든 방을 검색하기 위해서
배틀넷 서버에게 내 세션의 정보를 알려줘야 합니다.
가장 중요한 것은 "서버인 나의 IP"입니다.

1-5) 세션 호스트로써 게임 시작
게임을 시작합니다.
이때 배틀넷과 접속을 끊습니다.
P2P답게 다른 유저들과 접속이 이뤄지고 있을 뿐 배틀넷과의 접속은 의미가 없기 때문이죠.

UDP 막 날라다님

UDP 막 날라다님


만약 세션 호스트(방장)이 게임에서 나가게 되면 세션 마이그레이션(session migration)이라는 과정을 거치며
P2P 세션 호스트의 자리를 다른 사용자에게 물려주게 됩니다.
안정적으로 이것을 수행하는 것이 꽤나 난이도 있는 일이며 홀펀칭과 함께 P2P 라이브러리가 해주는 중요한 역활입니다.
스타크래프트에서 일반 유저보다 방장이 나갈때 잠시 랙이 걸리는 이유입니다.

1-6) P2P close(), 배틀넷 connect()
방을 나간 사람은 P2P 세션에서 나가며 다시 배틀넷에 Connect를 하게 됩니다.
이때 이겼다, 졌다의 정보를 갖고 배틀넷에 send()를 합니다.
이때가 P2P가 보안에 취약할 때입니다. 실제 이겼는지 졌는지 배틀넷은 모릅니다.
져놓고서 이겼다는 패킷을 날려도 배틀넷은 알지 못합니다.

2) 다른 사람의 만든 방에 접속할 경우
2-1) 세션 목록 recv()
1-3) 까지는 똑같습니다.
가장 중요한 것은 배틀넷이 현재 접속을 요청하는 세션 목록을 나타내는 것입니다.

P2P 서버가 해주는 가장 중요한 일

P2P 서버가 해주는 가장 중요한 일


2-2) 세션에 참여 P2P connect
배틀넷과 접속을 끊고 해당 세션 호스트의 IP로 접속을 하게 됩니다.

배틀넷과 접속을 끊고 P2P 세션에 connect()

배틀넷과 접속을 끊고 P2P 세션에 connect()


2-3) 게임 시작
세션 호스트라는 것이 아니면 네트워크 상으로는 똑같습니다.

마땅한 짤이 없어...지만 이건 스타2인데...

마땅한 짤이 없어...지만 이건 스타2인데...



2-4) P2P close(), 배틀넷 connect()
게임을 나가게 되면 P2P 세션을 닫고 배틀넷으로 접속합니다.
이 역시 1-6)과 같습니다.

3. 홀펀칭
P2P는 세션 마이그레이션 이외에도 NAT이 가장 골치꺼리입니다.
NAT 뒤에 있는 IP는 실제 IP가 아니기 때문에 밖에서 들어올 수 없는 것이죠.
공유기에 따라 특정 IP를 밖으로 노출 시키는 경우도 있고 아닌 경우도 있습니다.
어찌되었건 게임 서비스를 하는 입장에서는 NAT 특성에 관계없이 연결을 만들어줘야 합니다.

다행히도 이러한 홀펀칭에 대해서는 denoil님께서 멋지게 정리를 해주셔서 (http://gamedevforever.com/47)
해당 포스트를 참조하시면 될 것 같습니다. ^^

홀펀칭 역시 제 블로그(http://rhea.pe.kr/)에서도 2-1 편으로 다시 정리하겠습니다.
UDP 예제도 이번 포스트 소스로 넣어야겠군요...

자, 이번 포스트를 통해 소켓 사용법 이외에 네트워크 게임 개발자로써 고민해야할 1차적인 아키텍쳐를 이야기해봤습니다.
강조하고픈 것은 아키텍쳐에 게임을 맞추지 말고,
게임에 따라 유연한 아키텍쳐를 생각해내고 업데이트에 따라 유연하고 쉽게 바뀔 수 있는 자세를 가져야 한다는 것입니다.
좋아하는 게임이 있으면 어떤 네트워크 아키텍쳐를 갖고 있는지 최대한 유추해보시길 바랍니다.
그리고 PC게임 뿐만 아니라 스마트폰 게임이나 콘솔게임기를 추적해보는 것도 좋은 공부가 됩니다.

다음 시간에는 서버와 절대로 떼어놓을 수 없는 Database를 살펴보겠습니다.
아직도 게임을 만들 시기는 아닙니다.

4. 더 생각해볼꺼리
1) 오늘 예제로 등장한 C/S게임과 스타크래프트의 네트워크 연결 순서도를 그려보세요.
물론 소켓 함수도 함께 적어보세요.

2) 자세히 보시면 스타크래프트1의 배틀넷은 DirectDraw surface가 아니라 일반 Dialog입니다.
(커서가 윈도우 커서로 바뀌죠?)
저도 그랬지만 Surface 위에 Dialog 나 윈도우 stuff들을 올리는 것은 쉽지 않았습니다.
이는 지금도 마찬가지인데 각자 UI 컨트롤을 위해 노가다 헀던 경험담들을 풀어봅시다.
선수끼리 숨기지말고요~

내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/ )를 명시해주세요.

 

 

출처 : http://rhea.pe.kr/498

얼마전 mongoDB를 좀 갖고 놀았다.

DBA는 DB 저장 프로시져가 당연히 첫번째 관심사항이겠지만
서버 개발자의 경우 DB와 연결하는 드라이버가 첫번째 관심사항이다.
당연히 개발자답게 Windows용 C++ Driver부터 빌드하였다.
소문대로 mongoDB 빌드는 무척 더러웠다.
mongoDB 소스를 갖고 Windows용 C++ 드라이버를 빌드한다는 것이 좀 이해가 안된다.

그리고 내린 결론은...
Windows 서버에서는 C++로 mongoDB 서버를 짜지 말자는 것이다.
추천언어는 C#.

이런 결정이 내려진 이유는...

1) scons에서 더이상 force32로 옵션으로 32비트 빌드가 안된다.
32비트 드라이버를 빌드할려면 32비트 PC나 가상 머신을 띄워 빌드 시켜야 한다.
64비트로만 서버를 짜겠다면 할말은 없지만 최소 클라이언트에서는 문제가 된다.
(사실 mongoDB 데몬을 따로 개조 & 빌드하여 Access를 대신하는 용도로 써볼까 생각을 해봤었다.
물론 이와 같은 문제로 안됨.
그리고 32비트에서는 mongoDB 역시 데이터 사이즈 2GB가 한계.)

2) boost를 1.42로만 써야한다.
mongoDB를 빌드시키는 것뿐만 아니라, mongoDB 드라이버를 이용하는 프로젝트는 역시 boost 1.42를 써야한다.

서버상에서 가장 큰 이유는 물론 두번째 항목이다.
boost 1.45에서 interface가 많이 바뀌었는데 mongoDB만으로 위해서 boost 1.42 를 따로 관리하고 빌드시 참조해야 한다는게 말도 안된다.
일괄빌드에 치명적인 문제를 안긴다. 소위 말하는 빌드비용의 증가 문제가 따라온다.

그래서 C++은 포기했다.
아니 애시당초 이렇게 복잡한 빌드 방법이라니 mongoDB는 Windows에서 C++로 쓰지 말라는 말같다. ㄴㅁ...

기왕 좀 삽질한 거, 서비스로 드라이버인 mongoclient.lib를 포스트에 올려드리고 싶지만,
용량이 무려 150 메가 이상이라 차마 올려드리진 못하겠다.

대신 mongoDB 드라이버 빌드팁을 적어본다.

0) 비쥬얼 스튜디오에서 빌드하는게 아니라면 Spider Monkey는 필요없다.
Scons로 빌드하므로 괜히 복잡하게 다운받고 어디에 설치해야 하는지 고민말자.

1) 몽고 DB소스(DB+ C++ 드라이버) 다운 https://github.com/mongodb/mongo , https://github.com/mongodb/mongo/downloads
(드라이버-리눅스-가 아님.)

2) 파이썬 32비트 설치

3) Scons 설치- 설치시 Python\Scripts 라는 기본 위치에 설치
Scons은 *.mak 파일 빌드를 대체하는 개념인데 파이썬을 쓰므로 이렇게 설치를 한다.

4) MongoDB는 boost 1.42에서 빌드되었음으로 MongoDB홈페이지에서 제공하는 해당버전 다운하고 적당한 폴더에 푼다.
http://www.mongodb.org/pages/viewpageattachments.action?pageId=12157032
이것은 앞으로 boost 빌드뿐만 아니라 mongoDB 드라이버를 이용하는 모든 프로젝트에서 사용해야 한다. 좆같다.

5) 4)에서 받은 mongoDB를 SconStruct 파일에서 boost 경로 지정.
대략 640라인으로 이동,
def find_boost():
for x in ('', ' (x86)'):
boostDir = "C:/boost" <-- 4)에서 지정한 경로로 적어줌.
if os.path.exists( boostDir ):
return boostDir
for bv in reversed( range(33,50) ):
for extra in ('', '_0', '_1'):
boostDir = "C:/Program Files" + x + "/Boost/boost_1_" + str(bv) + extra
if os.path.exists( boostDir ):
return boostDir
if os.path.exists( "C:/boost" ):
return "C:/boost"
if os.path.exists( "/boost" ):
return "/boost"
return None


6) 콘솔창에서 1)에서 받은 mongoDB 소스 폴더로 가서 PATH 명령어로 파이썬경로와 파이썬 아래 script 경로(Scons) 설정.
기존 PATH에 있는 것 쓸일 없으니까 단순히 아래대로 자신의 파이썬 경로를 적어주자.
SET PATH=D:\Program Files (x86)\Python27;D:\Program Files (x86)\Python27\Scripts

7) 클라이언트 빌드
--> 8)에서 몽고DB를 64로 빌드하면 클라이언트도 64, 몽고DB를 32로 빌드하면 클라이언트도 32...인데 Scons가 더이상 64비트에서 32비트로 강제 빌드가 안된다.
따라서 32비트 드라이버가 필요하면 32비트 머신이나 가상머신 필요...

8) Client 개발시 ws2_32.lib 추가
까보진 않았지만 mongoDB 데몬과 통신시 역시나 TCP/IP를 쓰는 듯 하다.
님들, 걍 C# 쓰세요~

님들, 걍 C# 쓰세요~

 

 

출처 : http://rhea.pe.kr/496

원문 : http://www.gamedevforever.com/39

안녕하세요? 요양과 잉여짓으로 바쁜 Rhea Strike 입니다.
정작 당분간 하지 않을려했던
잉여 프로그램들은 짜고 있지만 가장 맘먹은 동인지 원고와 짤 제작, 여친 만들기 등이 늦어져 무척 가슴 아픈 나날입니다.

다들 쟁쟁한 주제들과 현업에서 연구하신 소재들로 막강한 화력을 자랑하시지만
실력이 딸려 제가 준비한 것은 네트워크 게임을 만드는 튜터리얼 연재입니다.
이 연재를 시작하기전 무슨 주제로 어떻게 이어갈까 고민을 잠시 했는데요,
잠시 이 링크 http://rhea.pe.kr/493 를 읽어주시고 연재글을 봐주시면 감사하겠습니다.

연재의 초기는 기본적인 채팅으로 시작될 것입니다.
이는 네트워크 프로그래밍에 익숙하지 않는 분들과 있을지도 모를 학생들을 위한 것이기도 합니다만,
매 연재 속에 점점 단순 채팅 프로그램이 게임과 아키텍쳐로 점차 커져가며
퇴근길 지하철에서 전체 게임을 위해서 한번 곱씹어 생각해볼만한 꺼리를 던져드리는게 제 목적입니다.

자, 그럼 지루한 서론을 마치고 우선은 모두들 알고 계신 내용이라도 복습의 의미로
기초부터 시작해보지요. ...역시 지루하군요.

짤에 별다른 의미는 없습니다만 이런 여친을 찾고 있습니다. 도움을 바랍니다.

짤에 별다른 의미는 없습니다만 이런 여친을 찾고 있습니다. 도움을 바랍니다.


1. 윈속의 역사
이미 게임을 하나 운영하고 있는 곳이라면 많은 것이 갖춰져 있습니다.
개발자 이외에도 DB, 서버, 패치 시스템, 그리고 그것을 돌려주는 여러 DBA, SE분들이 있습니다.
그러나 아무 것도 없는 곳에서 새로 온라인/네트워크 게임을 만든다면 맨땅에 헤딩을 해야 합니다.

우선 기획서가 나오고 DB라던가 여러 가지 인프라들이 먼저 있어야할 것이지만,
가장 단순하게 윈도우 서버와 클라이언트부터 만든다고 가정합시다.
자, 그럼 서버는 어떻게 만들어야 할까요?
서버를 만들기 전에 서버와 클라이언트에는 어떤 "Winsock Model"을 사용할런지부터 결정해야 합니다.

원래 소켓은 Windows 운영체제가 아니라 BSD UNIX에서 먼저 만들어졌습니다.
그래서 초창기 윈도우 3.1 같은 경우에는 소켓(http://www.joinc.co.kr/modules/moniwiki/wiki.php/man/12/BSD%20socket)
이 아예 존재하지 않았습니다.

이는 단순 소켓 지원 여부를 떠나 당시 마이크로소프트(앞으로 MS)의 중요한 의사 표방이기도 합니다.
소켓은 TCP/IP, UDP를 위한 라이브러리이고 TCP/IP, UDP같은 프로토콜은 바로 인터넷의 기반이 되는 프로토콜들이죠. 모든 것을 집어삼킨 HTTP 역시 TCP/IP 80번 포트를 이용한 텍스트기반 프로토콜 아니겠습니까?

다시 말해, MS는 현재의 인터넷이 이리 사용될지 몰랐습니다, 무려 90년대 중반까지도요!
MS는 소켓과 TCP/IP가 아닌 자신들의 프로토콜로 MS-Network라는 현재의 인터넷과 비슷무리하지만 다른 개념을 만들고 싶었습니다.

그 망상은 Windows 2000 이 등장하면서 공식적으로 사라졌는데 \\로 접근하던 다른 PC접근과 네트워크 공유를 NetBIOS(NetBEUI) 설치 없이 TCP만 설치하면 되도록한 것이 그 증거(?)라고 볼수 있습니다. 정확하게 말하면 NetBIOS over TCP/IP 이란 개념인데 아뭏든 90년대에도 MS는 인터넷도, TCP도, BSD 소켓도 반대! 그냥 MS껄로 다 정ㅋ벅ㅋ할꺼야!!였었습니다.

그래서 Win 3.1에서 Netscape로 웹서핑을 하던 우리들은 Win 3.1에는 없는 TCP/IP 프로토콜을 쓰기 위해
트럼펫 윈속(http://www.trumpet.com.au/index.php/downloads.html) 을 따로 설치해 썼었었습니다.
진짜 일반 사용자가 쓸수 있는 MS표 소켓이 나오는 것은 Win95 때부터입니다.

추억 돋는 화면 하나 더! http://www.loblolly.net/~rddecker/helppages/win31trmwns.htm



....................




...........





.....


..


그러나 어쩌겠습니까?
모두가 알다시피 인터넷(다시 말해 TCP/IP과 그것을 쓰기 위한 BSD Sockets)이 대세가 되었는데요.


이 당시 빌횽의 마음...

이 당시 빌횽의 마음...









할수 없이, MS는 BSD 소켓과 똑같은 윈도우용 소켓 라이브러리를 제작합니다.
BSD 소켓 4.3에 기반해 1993년 WinNT 3.51과 함께 Winsock 1.1을 공개합니다.
즉, Winsock은 WINdows + SOCKet의 약자지요.

정말로 이때 빌횽은 전세계 개발자들에게 이말을 들었습니다.

정말로 이때 빌횽은 전세계 개발자들에게 이말을 들었습니다.




이 윈속 1.1은 놀랄만큼 BSD 소켓과 동일합니다.
고인이신 스티븐슨의 UNIX Network Programming에 있는 소스 대부분이 헤더 파일만 약간 바꾸면 그대로 돌아갈 정도입니다.

네트워크 게임계의 프로메테우스, 古 리차드 스티븐슨

네트워크 게임계의 프로메테우스, 古 리차드 스티븐슨

이는 다이렉트X 초창기의 모습과 유사합니다. DOS 게임 개발자들에게 복잡한 Windows Procedure와 콜백을 잘 몰라도 DOS 게임을 그대로 포팅할수 있도록 한 배려와 비슷한 모습입니다.

하지만 이때까지도 MS는 TCP/IP의 가능성을 잘 몰랐습니다.
...그래서 Winsock 1.1을 사용하는 Windows 서버는 ....상당히 구렸습니다.
윈도우는 서버로는 절대 안된다는 말이 이때부터 흘러나왔죠.

그러나 1995년 WinNT 4.0의 등장과 함께 선보인 Winsock 2.0부터 상황은 달라지기 시작합니다.
아키텍쳐부터 싹 달라졌습니다.
1.0과의 호환성은 100% 유지하면서 Windows에 최적화된 진짜 Winsock이 등장한 것입니다.
(달라진 아키텍쳐로 "개인방화벽" 프로그램이 만들어질 수 있었습니다. 자세한건 "SPI"로 검색을 ^^)

오오미 윈속이랑께~!!

오오미 윈속이랑께~!!

현재 우리가 주력으로 사용하는 Winsock은 16비트 모드가 제거되어 1996년에 발표된 2,2를 사용하고 있으며
Win8이 등장할때 Winsock에 Registered IO extensions 이 추가된다고 하는데 간만에 Winsock에 새로운 기능이 추가되는 것이라 기대가 큽니다.

2. 윈속 모델

모델의 좋은 사례, Winsock에게도 공식 모델이 필요합니다.

모델의 좋은 사례, Winsock에게도 공식 모델이 필요합니다.


드디어 오늘 기초의 핵심인 윈속 모델 이야기를 하겠습니다.
안타깝게도 윈속이 버전업이 되었다고 해서 윈속의 성능이 공짜로 올라가는 것은 아닙니다.


네트워크 프로그래밍에서 첫쨰 난관은 언제 "1) 누가 서버에게 Connect를 해 올줄 모른다"는 점입니다. 그리고 이때 클라이언트가 Connect를 해오는 순간, 아주 빠르게 accpet()를 해줘야 합니다. 접속이 수천개가 되면 이 작업 역시 딜레이가 걸리고 컨텍스트 스위칭이 걸립니다.
그런 다음, 일단 양쪽이 연결이 되면 "2) 서버든 클라든 언제 상대방의 메시지를 듣기 위해 recv()를 해야할지 모른다"는 점입니다(아시겠지만 연결을 끊는 close() 역시 사실은 상대방에게는 recv()입니다.)

언제 이벤트가 오는지 모르는 싯점을 체크해봤습니다.

언제 이벤트가 오는지 모르는 싯점을 체크해봤습니다.

이미지 출처 : http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Frzab6%2Frzab6connectionor.htm

사실 게임 클라이언트처럼 소켓 5개 정도에서는 큰 차이가 없습니다만,
서버는 한 프로세스가 수백~수천개의 접속을 관리해야 합니다.
이를 위해 단일 thread를 돌리는 방법으로는 고작 수십명이면 금방 한계에 찹니다.
아무리 서버장비가 좋아도 똑같습니다.
왜 단일 thread는 안될까요?
thread라는게 마법은 아닙니다. 단순히 CPU 한계치까지 일을 시킬 뿐, 계속 루프를 돌며 찌질하게 검사하는 역활 밖에 할 수 없습니다.

좋은 서버란 가장 적은 thread와 CPU를 사용해 가장 많은 접속을 빠르게 처리해줄수 있어야 하며 이를 위해 두가지 개념을 먼저 알아야 합니다.

우선 비동기(asynchronous )라는 개념이 들어갑니다.
쉽게 말해 계속 루프를 돌며 검사하는게 아니라 클라이언트에게 접속요청이 왔거나 메시지가 왔을때에만
해당 소켓에게 알맞은 작업을 해주는 것입니다.

그래서 비동기 Winsock을 위해 4가지 모델이 고려되었습니다.







이중 알맞은 Winsock Model 네명을 고르시오.

이중 알맞은 Winsock Model 네명을 고르시오.






...는 아니고...





모델명 특징 기타
Window Message Select
(WSAAsyncSelect)
소켓 이벤트를 윈도우 메시지를 이용해 알려준다. 윈도우 메시지를 이용하므로 반드시 윈도우가 생성되어야 하며 MFC의 CSocket 클래스가 바로 이 모델.
Kernel Event Select
(WSAEventSelect)
소켓 이벤트를 커널 이벤트로 알려준다. 한 객체당 최대 64개의 이벤트 밖에 못쓰므로 서버로 쓰기 위해서는 코드가 쓸때없이 복잡해짐.
Overlapped I/O
(Overlapped)
소켓 이벤트를 비동기 읽기/쓰기를 위한 오버랩 구조체를 이용해 알려준다. 위의 둘과 달리 Overlapped와 IOCP는 Win95, 98에서는 안됩니다.

이 기법은 파일에 비동기로 읽기쓰기를 같이 하기 위함이었으나 소켓이든 파일이든 둘다 같은 HANDLE 이잖아요? ^^

역시 MS의 꽁수의 결정체라 불릴만 합니다.
Overlapped I/O + IOCP
(IOCP)
위의 방식에 더해 커널 큐잉을 통해 소켓 이벤트를 통보받는다. 사실상 윈됴에서 서버는 이넘으로 정해져있다. C#에서 비동기 선언해도 IOCP가 만들어진다.

결론은 서버는 IOCP가 정답입니다!
...이런 모델들입니다.


두번째는 넌블로킹(non blocking)개념입니다.
단일 thread가 아닌 비동기 서버를 짰다고 합시다.
이때 어떤 이벤트가 일어나면 비동기로 OS가 서버에게 알려줍니다.
그러나 send(), recv() 같은 함수는 블로킹(blocking)함수 입니다.
send(), recv() 작업이 끝날 때까지 서버 프로그램은 다음 일을 할수 없습니다.
이때 WSASend(), WSARecv같은 넌블로킹 함수로 대체한다면, 작업이 끝나기 전에 서버는 다음 작업으로 진행될 수 있습니다.

아마 넌블러킹 함수는 네트워크 프로그래밍에서 처음 보신 분들도 있을 것이지만,
수많은 I/O관련 함수들의 고급은 전부 넌블러킹으로 흘러가게 됩니다.
넌블러킹 함수는 어쩔수 없이 Callback 함수가 반드시 뒤따라갑니다.
그래서 처음 접하면 조금 어렵습니다............................................가 아니라 실은 많이 어렵습니다,
거기에 Overlapped나 IOCP같은 비동기 통보까지 더해지면 아주 복잡해집니다.

다행히 클라이언트에서 주로 쓰이는 WSAAsyncSelect 모델과 WSAEventSelect 모델에서는 넌블로킹이 그리 중요한 개념은 아닙니다. 유선 인터넷이 하도 빨라서요...
클라이언트에서 넌블로킹을 잘못 쓰면 소켓 응답 오기 전까지 게임 프로그램 자체가 위험해질수도 있고
그 사이 화면 UI처리같은 다른 할일들도 신경을 써야 합니다(<-이거 은근 고급 토픽입니다.).

그렇다고 클라이언트=블로킹이 절대 아닙니다.
최근 스마트 디바이스에서 SNS클라이언트와 SNG와 같은 게임에서는 불안한 3G 네트워크 때문에
요즘은 클라이언트도 비동기 + 넌블로킹이 대세입니다.

3G가 워낙 느리고 자주 끊기니, 일단 클라이언트에서는 send()한 것으로 치고
유저에게 다음 작업을 할수 있도록 해주는 것입니다.
혹시 안된다면 나중에 에러 메시지가 뜨며 다시 하라고 알려줍니다.

SNG를 안하시는 분들도 트위터같은 앱에서 자주 보셨을 것입니다.
재미있게도 이런 처리 방식은 트위터 웹페이지에서도 확인할 수 있습니다.
물론 저도(사실은 밑에 팀원 갈궈서 하게 했습니다... >_<;;; ) SNS 만들때 이렇게 했고요.

만약 단순 블로킹이라면 이런 상황 자체를 볼수 없을 것입니다.

만약 단순 블로킹이라면 이런 상황 자체를 볼수 없을 것입니다.


이는 We Rule같은 게임이 네트워크가 불안정할때 쉽게 확인될수 있습니다. 단순해 보이는 SNG도 3G라는 현실로 제법 복잡한 네트워크 구현 기술을 알아야 합니다.

...이슬이...드립 치고 싶습니다.



3. 오늘의 소스
오늘의 소스는 간단한 클라이언트/서버 소스입니다.
클라이언트는 MFC로 만들었고 Winsock Model 역시 CSocket, 즉 윈도우 메시지 방식입니다.
서버는 boost::asio로 짰습니다. 기본 TCP 서버를 그대로 썼습니다.
단, 한 파일로 설명된 소스를 보기 좋게 클래스별로 나눠놨습니다.
이 정도면 네트워크 프로그래밍에 익숙하지 못한 초보분이라도 약간의 노력이면 다음 시간부터 따라오실수 있을 것입니다.

이 간단한 프로그램들로 네트워크 게임을 위한 첫 삽질이 시작될 것입니다.




4. 더 생각해볼꺼리

1) 심심하고 정말정말 할일이 없는 날, 단일 thread 방식의 채팅 서버와 윈도우 메시지 방식의 채팅 서버를 만들어 최대 몇개의 클라이언트에게 원할하게 메시지를 돌려주는지 직접 재어보세요.
2) 클라이언트가 블로킹으로 작동되어야할 경우와 넌블로킹으로 작동되어야할 경우를 생각해보세요.
3) 클라이언트가 블로킹과 넌블로킹, 모두가 동시에 필요하다면 소켓 라이브러리를 어떻게 만들어야할까요?
(힌트 : MFC에서 CSocket 와 부모 클래스인 CAsyncsocket를 살펴보세요.)
4) 실제 게임회사의 면접에서 IOCP의 동작 메커니즘을 구두로 답변하기는 무척 어렵습니다.
그래서 저는 이렇게 묻습니다,
"왜 IOCP 소스에는 recv() 함수가 두번 적혀있나요?"
여러분은 머라고 대답하시겠습니까?



내용은 맘대로 퍼갈수 있지만 동의없는 수정은 안되며 출처(http://www.gamedevforever.com/ , http://rhea.pe.kr/ )를 명시해주세요.

 

 

출처 : http://rhea.pe.kr/494

+ Recent posts