블로그 이미지
:infinity cout << "lv up!\n\r"; goto infinity; RosaGigantea

카테고리

Programming log (471)
IT 기기 (33)
리눅스 서버에 대해서 (90)
윈도우 서버에 대해서 (49)
DataBase (22)
프로그램 설계 (8)
알고리즘 (34)
boost (1)
C/C++언어 (89)
콘솔 게임 프로그래밍 (20)
임베이드 장비 (4)
모바일 프로그래밍 (3)
기타 언어 프로그래밍 (7)
Util 프로그램 (7)
대학생 졸업하기 전.. (104)
Total544,343
Today52
Yesterday152

온라인 게임과 콘솔 / PC게임 이 두 가지 차이점은 무엇일까요?
당연한 이야기지만, 온라인 게임은 네트워크망을 사용해서 불특정 다수와 관계를 맺어가면서 즐기는 게임입니다.

그에 반해 콘솔 PC게임은 자신 홀로 게임 세계에 들어가 즐기는 게임이죠.


게임을 하다 보면, 이것저것 막힐 때가 있을 겁니다. PC 게임 같은 경우 데이터 파일을 헥사 파일로 열어서 특정 수치를 변조하여 초반부터 최고 레벨로 플레이를 하는 행위가 가능합니다.


하지만 온라인 게임의 경우는 조금 문제가 있습니다.
내 행동이 다른 이들에게 영향을 주는 게임이다 보니, 내가 게임이 지루하다고 내 캐릭터 레벨을 10에서 100으로 올려버리면, 다른 유저들이 힘들게 올린 100레벨은 무의미해지죠 박탈감이 심해져서 열심히 일한 개미(열정적인 유저들을) 떠나게 합니다.
그래서, 이런 치팅을 하지 못하도록 막아야 할 필요성이 생깁니다. 어떤 처리를 넣으면 좋을까요?

 

역사적으로 봤을 때, 인류는 이런 고민을 상당히 오래전부터 하였습니다.
오랜 예부터 특히 군대에서 적군이 눈치채지 않고 인접 아군에게 작전 지시를 내리는데,
전령이 적군에게 사로잡힐 경우를 대비해서 사용했던 암호화가 바로 그것입니다.

 

며칠 몇 시에 어느 곳으로 이동해서 어디를 공략해야 한다는 전술 명령을 산 건너 있는 부대장에게 전달해야 하는데,
이 작전 문서를 평문으로 보내면 어떻게 될까요?
전령병이 산을 넘다가 적에게 포로로 잡히면, 이 작전이 무산되거나, 혹은 명령서를 바꿔서 보내 작전이 원하는 대로 움직이지 않을 것이고 결국 전쟁에서 패배라고 말 것입니다.
그래서 옛날부터 이런 작전 문서의 문자를 암호화하여 난센스 문장을 써서 전달하는 시저 암호나 에니그마 같은 것이 나왔습니다.

 

서버 프로그램도 같습니다. 사실 클라이언트 프로그램이 유저에게 제공되는 순간, 이 클라이언트 프로그램은 소스까지 모두 제공한 거로 보셔야 합니다.

(자세한 건 리버싱 엔지니어링을 공부해 보세요) 그러므로 서버에 접속하는 프로그램이 반드시 제공한 클라이언트일 확신이 없으며, 이 클라이언트가 가짜라는 가정하여야 하고, 패킷은 난독화(Obfuscation)시켜서 송수신해야 합니다.

 

그러면, 키를 넣어 변조시킨 뒤, 다시 같은 키를 넣어 원래대로 데이터를 만드는 방법을 어떻게 하면 좋을까요?
일반적으로 자주 사용되는 방법은 XOR 연산입니다.

 

XOR는 배타적 연산으로 2개의 명제 중 하나만 참일 때 참을 출력하는 로직입니다.
이를 이용해서 3변수를 사용하지 않고 2변수 만으로도 swap 함수를 만들 수 있죠.
그럼 이제 특정 Key를 만들어서 패킷데이터에 XOR연산을 하면 사람이 알아보기 힘든 값이 나오겠군요.

해커들이 보고 한숨 쉴 수 있도록 작업해 봅시다.

추가: /ServerLibrary/ServerLibrary.h 

 

#include "./Net/Packet/PacketObfuscation.h"

 

패킷 난독화 소스를 서버 라이브러리에 추가하였습니다. 실제 구현 소스는 아래와 같습니다.

 

 추가: /ServerLibrary/Net/Packet/PacketObfuscation.h

 

#pragma once

#include "stdafx.h"

 

//--------------------------------------------------------//

// 난독화 최상위, 다른 난독화 알고리즘은 이 클래스를 상속 받을 것

class Obfuscation

{

public:

    virtual void encoding(Byte *packet, size_t len) = 0;

    virtual void decoding(Byte *packet, size_t len) = 0;

};

 

//--------------------------------------------------------//

class XorObfuscation : public Obfuscation

{

    Byte key_;

public:

    XorObfuscation();

private:

    void encoding(Byte *packet, size_t len);

    void decoding(Byte *packet, size_t len);

};

 

//--------------------------------------------------------//

// 패킷 난독화용

class PacketObfuscation : public Singleton < PacketObfuscation >

{

    Obfuscation     *obfuscation_;

public:

    PacketObfuscation();

    ~PacketObfuscation();

 

    void encoding(Byte *packet, size_t len);

    void decoding(Byte *packet, size_t len);

};

 

난독화 알고리즘은 여러 가지이지만, 소스에서 필요한 것은 인코딩/디코딩 정도이므로
이를 강제할 수 있게 최상위 순수 가상 클래스를 작성했습니다.
실제 사용하는 것은 PacketObfuscation 함수인데, 소스 내용은 아래와 같습니다.

 

 추가: /ServerLibrary/Net/Packet/PacketObfuscation.cpp

#pragma once

#include "stdafx.h"

#include "PacketObfuscation.h"

 

XorObfuscation::XorObfuscation()

{

    //암호 선택에 대해서는 조금 고민해 볼것.

    Key_ = 'T';

}

 

void XorObfuscation::encoding(Byte *packet, size_t len)

{

    for (int index = 0; index < len; ++index) {

        packet[index] ^= *key;

    }

}

void XorObfuscation::decoding(Byte *packet, size_t len)

{

    for (int index = 0; index < len; ++index) {

packet[index] ^= *key;

    }

}

 

//--------------------------------------------------------//

PacketObfuscation::PacketObfuscation()

{

    //Todo : xor 말고 다른 알고리즘도 만들어서 config xml에서 선택하게 하기

    obfuscation_ = new XorObfuscation();

}

 

PacketObfuscation::~PacketObfuscation()

{

    SAFE_DELETE(obfuscation_);

}

 

void PacketObfuscation::encoding(Byte *packet, size_t len)

{

    obfuscation_->encoding(packet, len);

}

 

void PacketObfuscation::decoding(Byte *packet, size_t len)

{

    obfuscation_->decoding(packet, len);

}

 

XOR 연산 킷값으로 ‘T’ 라는 단어를 주었습니다.
하지만 이 경우 해커가 key값을 0~255까지 XOR연산 테스트 하는 것만으로도 Key값이 확인 될 겁니다.

대부분 해커들은 채팅 메시지를 테스트 삼아 (‘aaaaa’입력 했을 때 패킷을 캡쳐 해서 분석) 이 XOR 키 값을 찾아갑니다.

 

그러므로 채팅 메시지는 별도로 암호화 처리 하는 것도 좋은 방법이라고 생각되지만,
여러 단어를 XOR값으로 테스트 하다 보면 쉽게 Key값을 알아 차리 겠군요.
좀 더 복잡하게 해서 해커를 혼란스럽게 만들어 봅시다.
...

 

위 내용은 제가 집필한 "게임 서버 프로그래밍 입문" 책의 내용중 일부 부분에 대한 내용입니다.

전체 소스 코드와 책 구입에 대해서는 http://rosagigantea.tistory.com/589 에 링크 시켰습니다.

 

 

저작자 표시 비영리 변경 금지
Posted by code쟁이 RosaGigantea

지금까지 서버 프로그램을 만들고 서버끼리 데이터를 주고받는 것까지 테스트하였습니다.

하지만 실제 온라인 게임이라 하지는 못하죠.


온라인 게임을 만드는데 서버만으로는 만들 수 없습니다.

당연히 클라이언트 프로그램도 만들어야겠죠.

그래서 여기서는 간단한 더미 클라이언트를 만들어 보는 시간을 가지려 합니다.
여기서 더미(Dummy)는 가짜라는 말입니다.

일반적으로 온라인 게임에서는 더미 클라이언트랑 클라이언트를 따로 만들게 됩니다. 왜냐구요?


실제 서버 프로그래밍에서도 성능 개선 등을 할 때, 수백, 수천 대의 클라이언트를 접속할 방법이 없으므로, 로직이 같은 간단한 더미 클라이언트를 수백 개 띄어 서버에 접속시켜 테스트하는 방법을 주로 사용합니다.

 

물론 이를 완벽하게 신뢰하기에는 네트워크 특성상 불가능하니 여력이 되는 온라인 게임은 공개 테스트 서버 등에 먼저 업데이트를 해서 추가될 내용에 이상이 없는지 확인 작업을 진행합니다.

 

그런데 테스트를 위해서 클라이언트를 2개 만들어야 한다니, 클라이언트 프로그래머는 야근하라는 걸까요? 물론 그렇지 않습니다. 그래픽 출력 부분만 잘 만들어 위의 Log처럼 전략적으로 구동방식을 교체할 수 있도록 하면 좋은 구조겠죠.
보통 클라이언트 프로그램은 아래와 같은 큰 모듈들의 조합으로 이루어졌습니다.

 


그럼 이제부터 클라이언트를 만들도록 해봅시다.
테스트 용도로 만드니까 클라이언트는 C#으로 제작하려고 합니다.
MFC나 WinAPI를 사용해서 만드셔도 상관은 없지만, 제 생각으로는 이종(異種)언어들간의 패킷을 주고받게 하는 것을 만들어 보는 것도 나중에 모바일 게임 작성 등에 좀 더 공부가 될 거로 생각하기 때문에 C#을 선택했습니다.


소스를 참고하셔서 클라이언트를 C 이외의 언어, Java나 Objective-C, Swift로 만들어 보시는 것도 큰 공부가 될 것으로 생각합니다.

 

위 내용은 제가 집필한 "게임 서버 프로그래밍 입문" 책의 내용중 일부 부분에 대한 내용입니다.

전체 소스 코드와 책 구입에 대해서는 http://rosagigantea.tistory.com/589 에 링크 시켰습니다.

 

저작자 표시 비영리 변경 금지
Posted by code쟁이 RosaGigantea

지금까지 게임 서버를 만들기 위한 코드를 작성해 왔습니다.

지금 완성된 모듈로도 어느 정도 게임 서버 구실이 나올 수 있을 겁니다.

그럼 실제 게임 서버는 어떤 구조로 되어 있을까요?
간략하게 로그인에 처리 및 서버 부팅 단계를 그려 보았습니다.

 

 

개인적으로 경험한 회사는 별로 없지만, 대부분의 게임 서버는 위와 같은 구조로 돌아갑니다.


처음 서버가 뜨면, 정해진 ip/port를 받아서 통신할 준비를 하며, 다른 서버군 (모니터닝 서버나, 아이템 샵 서버, 게임 월드 서버 등……)에게 자신이 실행 중이라는 사실을 알립니다.


당연히 DB 처리가 있으면, DB 연결도 해야 하겠네요,
게임 서버라면 게임 데이터를 읽어서 각각의 실행 객체를 생성하는 작업을 진행합니다.
이후, IOCP 에코 서버와 같이, 클라이언트 접속을 기다려서, 클라이언트가 붙으면 세션을 만들고, 패킷을 분석해서 적절한 처리를 해줍니다.


이제 위의 엑티비티 다이어그램을 참고로 지금까지 작성한 라이브러리를 사용해서 진짜 게임 서버 프로그램을 만들어 봅시다. 테스트로 서버프로그램 2개만 생성해 봅시다.

하나는 LoginServer이고, 다른 하나는 DB와 연결을 직접 하는 DBAgent 입니다. 
DB처리 서버를 분리 하느냐고요?
최근 추세는 DB와 연결하는 서버를 따로 두어서 DB에 데이터를 받거나 읽는 처리는 DBAgent라는 서버로 분리하는 추세입니다.

 

이렇게 분리하면 아래와 같은 이점이 있습니다.
1. 게임 로직과 DB 처리 로직을 분리 할 수 있습니다.


2. DB라는 것이 물리적으로 다른 서버에 구축되어 있으므로 게임 로직상 DB를 거치게 되면 DB의 응답이 올 때까지 로직이 멈추게 구현할 수 있습니다.
물론 체감이 느껴지진 않지만 이로 인해 서버 공격 (DB 처리가 많이 드는 행동을 집단으로 한다던가)가 발생 할 수 있습니다. 만약 분리를 한다면, DB 데이터 요청도 일종의 서버 패킷처리와 같이할 수 있으므로 로직 작성 시 위와 같은 비 동기화 처리가 자연히 고려되어 작성하게 됩니다.

 

3. DB처리만으로 보면 아주 간단한 처리이므로 만들어놓고 문제가 생기지 않는 이상 건드릴 필요 없습니다. 즉 관리 이슈가 많이 줄어듭니다.

이를 바탕으로 만들려고 하는 간이 서버 구조는 아래와 같습니다.

위 내용은 제가 집필한 "게임 서버 프로그래밍 입문" 책의 내용중 일부 부분에 대한 내용입니다.

전체 소스 코드와 책 구입에 대해서는 http://rosagigantea.tistory.com/589 에 링크 시켰습니다

저작자 표시 비영리 변경 금지
Posted by code쟁이 RosaGigantea

보통 게임 서버에서는 클라이언트 데이터를 직접 저장 하지 않고 DataBase라는 프로그램에 데이터를 저장 합니다.

 

그런 의미에서 저도 DB에 데이터를 저장하고 로드 하는 방법을 알려드리려 합니다.

우선 무료로 설치 할 수 있는 MS-SQL Express 2014를 설치해 봅시다.  (가능하면 다른 PC나 가상PC에서 설치하세요)


 http://msdn.microsoft.com/ko-kr/evalcenter/dn434042.aspx
 

 

설치하면 중간에 기능 선택 기능이 있는데 관리 도구는 반드시 체크합시다.
그리고 인스턴스 루트 디렉터리는 DB가 IO 문제가 민감해서 웬만하면 OS가 설치 안된 다른 하드 디스크를 선택하는 것이 좋습니다.


 

특별히 설치하는 데는 큰 어려움이 없다고 생각합니다.
가끔 설치했는데, Management Studio가 없는 경우도 있는데, 이는 설치 파일을 잘 못 받은 것입니다. 설치 파일을 확인하시고 다시 설치하시기 바랍니다.
여기까지 설치되면 DB를 사용할 준비는 완료된 것입니다.


 

 

그럼 여기에 데이터를 어떻게 넣고 조회는 어떻게 할까요?

데이터베이스에는 이런 데이터를 조회하고 넣는등의 스크립트들을 정립해서 표준화 시켰습니다.

이를 SQL이라고 하죠.

게임 서버에서 자주 사용되는 SQL문법은 아래와 같습니다.

 

1. Use
Database를 선택할 때 사용합니다.
ex: > USE [데이터 베이스 이름];


2. GO
쿼리 문 실행을 끝냄을 의미합니다. 즉 GO가 나오기 전까지는 일종의 {}에 묶여 있는 형태로 보시면 좋을 것 같습니다.


3. Create
DB, 테이블, 프로시저, 트리거등을 만드는 문입니다.
ex: > CREATE TABLE [테이블 명] (칼럼 명, 데이터 타입 및 제약 조건 ……)


4. Alter
DB, 테이블, 프로시저 등을 수정할 때 사용합니다.
ex: > ALTER PROC [프로세서 이름]


5. Insert
레코드를 테이블에 넣을 때 사용합니다.
ex: > INSERT INTO [테이블 명] (칼럼1, 칼럼2, ……) VALUES(값1, 값2……)


6. Select
테이블 검색에 사용합니다.
ex: > SELECT * FROM [테이블] WHERE [조건식] ORDER BY [정렬 방법]
ex2: > SELECT TOP 10 FROM [테이블]            -- 테이블 항중 10개만 출력
ex3: > SELECT DISTINCT [칼럼A] FROM [테이블]   -- 칼럼A의 중복 값 제거한 레코드 출력


7. Update
레코드 수정에 사용합니다.
ex: > UPDATE [테이블] SET [칼럼] = [값], …… WHERE [조건식]


8. Delete
레코드를 삭제 합니다.
ex: > DELETE [테이블] WHERE [조건식]


9. Truncate
레코드를 비웁니다. Delete와 다른 점은 트렌젝션 로그를 남기지 않습니다.
즉, DELETE [테이블A]; ROLLBACK 하면 롤백이 가능합니다만,
TRUNCATE TABLE [테이블A]; ROLLBACK 하면 롤백이 불가능합니다.


10. Drop
테이블, 프로시저, DB를 완전히 삭제 합니다.
DELETE, TRUNCATE 는 데이터를 삭제하는데, Drop은 객체 자체를 지웁니다.
ex: > DROP TABLE [테이블A]
    > DROP DB [DB이름]
    > DROP PROC [프로시저 이름]


11. Join
테이블과 테이블을 어느 조건에 맞춰 합치게 합니다.
예를 들어 아래와 같은 것을 생각할 수 있습니다.


<User Table>
userOid name job level money
1 롱스틱 마법사 280 10034
2 사과크림 전사 100 592
3 포도크림 소환사 39 110
… … … … …
<Item Table>
itemOid name lv userOid
1 절대반지 50 3
2 엑스칼리버 120 2
3 파멸의 손짓 240 1
… … … …
여기서 userOid로 묶을 수 있으니까 아래와 같이 입력하면
> SELECT * FROM UserTable AS A1
  INNER JOIN ItemTable AS A2.
  ON A1.userOid = A2.userOid

 


userOid name job level money itemOid name lv
1 롱스틱 마법사 280 10034 3 파멸의 손짓 240
2 사과크림 전사 100 592 2 엑스칼리버 120
3 포도크림 소환사 39 110 1 절대반지 50


이런 식으로 조합이 가능합니다.
그리고 눈치채셨겠지만 Oid를 기점으로 데이터를 합칠 수 있으니 Oid가 아닌 것들은 데이터 수정에서 자유로울 수 있는 장점이 있습니다. (캐릭터 이름 변경이라던가……)|
위에서는 교집합인 Inner의 예가 빈번히 사용돼서 설명 드렸고, 그 외의 합집합인 Outer join 문은 물론 Cross Apply도 살펴보시길 바랍니다.


12. Sub Query
일종의 Select 문 안에 Select 를 사용하는 방법입니다.
ex: > SELECT * FROM [테이블A] WHERE [칼럼]
       = (SELECT * FROM [테이블] WHERE [칼럼] = ‘조건’)
조금 변형하면 이런 것들도 가능합니다.


> SELECT [칼럼1], [칼럼2] INTO [테이블B] FROM [테이블A] WHERE [칼럼] = ‘조건’
  -> 테이블A의 조건을 만족하는 칼럼1, 칼럼2의 테이블 B를 생성합니다.
> UPDATE A SET A.[칼럼] = B.[칼럼] + x
 FROM [테이블A] AS A JOIN [테이블B] AS B ON A.[칼럼] = B.[칼럼] WHERE [칼럼] = ‘조건
 -> 특정 A 테이블 중, A와 B의 join한 테이블의 특정 조건의 레코드의 항목만 갱신


13. Set
변수를 설정하는 명령어 입니다.
ex: > DECLARE @tmp1 AS INT
      SET @tmp1 = 1000


14. Print
메시지를 출력합니다. 굉장히 길고 처리 시간이 오래 걸리는 쿼리 문 작성할 때, 중간에 Print 메시지를 넣어서 어디까지 실행 되었는지 파악할 때 사용합니다.
ex: > PRINT ‘쿼리 A까지 완료’


15. PROCEDURE
위의 기본 쿼리 문 (SELECT, INSERT, DELETE, UPDATE) 하나만으로 데이터 넣는 처리를 하는데 어느 정도 한계가 있습니다. 그래서 이 코드들을 하나의 함수처럼 선언 할 수 있는데, 이를 프로시저라고 합니다.
프로시저를 선언하게 되면, 해당 코드들을 MSSQL내부에서 미리 컴파일 해 가지고 있기 때문에 실행 속도가 훨씬 빠릅니다.
그리고 응용단계로 프로시저 안에서 프로시저를 생성해서 실행 할 수 있는데, 이를 동적 프로시저(Dynamic)라고 합니다.


ex: > CREATE PROC [프로시저 명] [@파라 미터], ……
      AS BEGIN
         SELECT * FROM … 쿼리 문
      END


16. BEGIN TRANSACTION / COMMIT TRANSACTION / ROLLBACK TRANSACTION
트렌젝션은 어느 업무 처리 단계를 두는 행위로, 데이터에 어떤 행위(수정, 삭제)를 했고, 이 결과가 실패할 경우 이전 단계로 복구해 주는 기능을 뜻합니다.
만약 성공했다면, COMMIT, 실패해서 이전 단계로 복구하려면 ROLLBACK을 사용합니다.


17. EXEC
프로시저를 실행합니다.
ex: > EXEC [프로시저 명] [값1], [값2] ……

 

이밖에 알아두면 좋은 것은 Index, 트리거, 권한주기, X쿼리나 내부 함수인 DATEADD, COUNT, MAX, MIN, GROUP BY 등을 공부하면 좋겠지만, 위의 17가지 + 알파만 알고 있어도 서버 프로그래머 업무상 큰 지장은 없고 모르면, 누누이 말씀드리지만 그때그때 구글로 찾아도 참고할 자료는 많이 있습니다.

 

위 내용은 제가 집필한 "게임 서버 프로그래밍 입문" 책의 내용중 일부 부분에 대한 내용입니다.

전체 소스 코드와 책 구입에 대해서는 http://rosagigantea.tistory.com/589 에 링크 시켰습니다.

저작자 표시 비영리 변경 금지
Posted by code쟁이 RosaGigantea

보통 온라인 게임에서는 패킷이 몇 개나 필요할까요?
게임에 따라 다르지만 보통 적게는 300 ~ 2,000개 정도 사용하는 거 같네요.
음…… 즉, 패킷 클래스를 300 ~ 몇천 개 +를 만들어야 하는데…… 이걸 어느 세월에 만들어야 할까요?


이런 단순 반복적인 비생산적인 코딩 작업은 컴퓨터에 대신해주면 좋겠죠?
그래야 단순 반복적인 작업에서 오는 에러를 막을 수 있을 겁니다.
패킷 클래스 cpp, h를 특정 규칙에 따라 만들어주는 프로그램을 짜보도록 합시다.
그리고 관리가 쉽도록 엑셀 파일을 사용해 봅시다.


패킷을 관리해야 하니 당연히 용도에 맞는 작명 법이 필요합니다. 저는 아래와 같은 규칙으로 작성하도록 하겠습니다.
1. C로 시작하는 건 (Client)클라이언트가 보내는 패킷입니다.
2. S로 시작하는 건 (Server)서버가 보내는 패킷입니다.
3. I로 시작하는 건 (Inter)서버들간 주고 받는 내부 패킷입니다.
4. 위의 단어 다음은 패킷의 속성을 기술합니다. REQ는 요청, ANS은 답변, NOTIFY는 전달 등의 의미 입니다.
5. 대문자로 작성하되, 단어 사이에는 스페이스 대신 _(스네이크)를 붙입니다.
6. #이 가장 먼저 나오면 그 행은 주석입니다.

 

위의 룰에 맞춰 아래와 같이 작성합니다. (시트 명 packetData)
 이것만 해도 상관은 없으나, 클래스 구조를 하드코딩으로 소스에 넣으면 나중에 수정하기 힘들어지므로 일정 양식(포멧)을 만들어서 프로그램이 이 포멧을 기반으로 소스를 뽑도록 만들어 봅시다.

 

 

위 내용은 제가 집필한 "게임 서버 프로그래밍 입문" 책의 내용중 일부 부분에 대한 내용입니다.

전체 소스 코드와 책 구입에 대해서는 http://rosagigantea.tistory.com/589 에 링크 시켰습니다.

 

저작자 표시 비영리 변경 금지
Posted by code쟁이 RosaGigantea

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

티스토리 툴바