24
Executing System CGCII Cho sanghyun’s Game Classes II

GCGC- CGCII 서버 엔진에 적용된 기술 (4) - Executing System

  • Upload
    -

  • View
    199

  • Download
    5

Embed Size (px)

Citation preview

Executing SystemCGCIICho sanghyun’s Game Classes II

CGCIICho sanghyun’s Game Classes II

실행처리

일반적인 Unix 기반 서버

Thread (1)

• Socket 당 하나의 Thread 의 물려 버린다 .

• 접속 수만큼 Thread 가 필요해 사실상 동접이 제한

• 극악의 비효율 ! (Thread 수 >>>Core 수 )

• 효율적으로 제작하기가 불가능 그 자체 !

• Apache MPM(Multi-Processing Modules) Prefork or Worker 와 같은 Unix 기반 서버

Thread Pool

Socket 당 Thread 를 1:1 로 물림

CGCIICho sanghyun’s Game Classes II

실행처리

비동기 I/O 싱글 쓰레드 기반 서버

Thread (2)

• 하나의 Thread 로 모든 Socket 을 처리 .

• 비동기 이벤트 방식 혹은 폴 방식 Socket I/O 를 사용 .

따라서 동시 접속 수는 거의 제한이 없으면 단일 쓰레드라 성능이 극히 떨어짐 .

• 효율적으로 제작하려면 추가적인 노력이 듬 .

• Node.js, Nginx 와 같은 류의 Unix 기반 서버류 혹은 AsyncSelect 등을 사용하는 Window 기반 서버류

모든 Socket 은 하나의 Thread 로…

CGCIICho sanghyun’s Game Classes II

실행처리

일반적인 IOCP 를 사용하는 Window 서버

Thread (3)

• 비동기식 처리기 때문에 접속자 제한이 없음 . • 모든 Core 를 활용할 수 있음 .• 국내외 IOCP 를 사용한 Windows 서버 엔진류

I/O Thread Pool•일반적으로 IOCP 와 다중 Thread 로 구성•기본적 Socket I/O 의 처리나 받은 메시지를 큐잉하는 역할만 수행

Work Thread•단일 혹은 멀티 Thread 로 구성•큐잉된 메시지를 꺼내와 실질적인 메시지의 처리

기타 Thread•잡다한 처리를 할 Thread•몇 개가 될지 대중 없다 .

• Core 에 비해 많은 Thread 수를 강요 .• 복잡한 Thread 구조 .• 완벽한 부하균형이 힘들어 과부하 시 I/O 와 Work 의 쏠

림 현상이 나타나 서버가 불안해지는 경우가 많음 .

CGCIICho sanghyun’s Game Classes II

실행처리

CGCII 는 하나의 Thread Pool 로 통해 로드 밸런싱 !

단일 Load Balancer 이므로 I/O 처리에 있어 쏠림 현상이 최소화된다 .

최적의 Core 대 Thread 수를 가질 수 있다 .

Thread (4)

통합 Thread PoolSocket I/O, Work Thread 역할 , 기타 잡다한 처리 모두 하나의 Thread Pool 로 통합 실행

Core 와 Thread 의 사용효율을 극대화 시킨다 .

Scheduler•실행 예약된 것을 Executor 에 걸어주는 역할•Priority Queue 로 최적화 .

IOCP(I/O Completion Port)

CGCIICho sanghyun’s Game Classes II

실행처리

IOCP 는 결론적으로 쓰레드풀을 위한 시스템

= Thread Pool

일반적으로 Socket I/O 처리에 Overlapped I/O 를 처리하기 위해 사용한다 .

IOCP (1)

CGCIICho sanghyun’s Game Classes II

실행처리

enum MY_IOTYPE{

int IOTYPE_ACCEPT,int IOTYPE_CONNECT,int IOTYPE_DISCONNECT,int IOTYPE_SEND,int IOTYPE_RECEIVE

};

struct MY_OVERLAPED : public OVERLAPPED{

MY_IOTYPE eIOType;WSABUF sBuffer;

};

enum MY_IOTYPE{

int IOTYPE_ACCEPT,int IOTYPE_CONNECT,int IOTYPE_DISCONNECT,int IOTYPE_SEND,int IOTYPE_RECEIVE

};

struct MY_OVERLAPED : public OVERLAPPED{

MY_IOTYPE eIOType;WSABUF sBuffer;

};2. I/O Type 을 설정할 변수 추가

1. OVERLAPPED structure 를 상속받음 .

일반적인 IOCP 를 사용한 처리…

일단 OVERLAPPED 구조체를 상속받은 구조체를 정의한다 .

IOCP (2)

3. I/O Type

CGCIICho sanghyun’s Game Classes II

실행처리

일반적인 IOCP 를 사용한 처리…

// @) hSocket 은 접속된 소켓 ;

// 1) Overlapped 와 통보받을 Event 를 생성한다 .g_pOverlapped = new MY_OVERLAPED;

// 2) MY_OVERLAPED 설정하기ZermoMemory(g_pOverlapped, sizeof(WSAOVERLAPPED));pOverlapped->eIOType = IOTYPE_RECEIVE;pOverlapped->hEvent = NULL;pOverlapped->sBuffer .buf = malloc(65536);pOverlapped->sBuffer.len = 65536;

// 3) Receive 걸어 놓는다 .DWORD dwByte;DWORD dwFlag = 0;

WSARecv(hSocket, &pOverlapped->sBuffer, 1, &dwByte, &dwFlag, g_pOverlapped, NULL);

// @) hSocket 은 접속된 소켓 ;

// 1) Overlapped 와 통보받을 Event 를 생성한다 .g_pOverlapped = new MY_OVERLAPED;

// 2) MY_OVERLAPED 설정하기ZermoMemory(g_pOverlapped, sizeof(WSAOVERLAPPED));pOverlapped->eIOType = IOTYPE_RECEIVE;pOverlapped->hEvent = NULL;pOverlapped->sBuffer .buf = malloc(65536);pOverlapped->sBuffer.len = 65536;

// 3) Receive 걸어 놓는다 .DWORD dwByte;DWORD dwFlag = 0;

WSARecv(hSocket, &pOverlapped->sBuffer, 1, &dwByte, &dwFlag, g_pOverlapped, NULL);

1. MY_OVERLAPPED 객체를 동적 생성한다 .

2. I/O 타입을 설정한다 .

3. 이렇게 넘김 ~

IOCP (3)

CGCIICho sanghyun’s Game Classes II

실행처리

일반적인 IOCP 를 사용한 처리…// @) I/O TheadDWORD dwResult; // GetQueued…() 함수의 결과DWORD dwTransfered; // 전송된 Byte 수를 저장할 변수ULONG_PTR pPerHandelKey; // Per Handle Key 의 Pointer 를 받을 변수MY_OVERLAPPED* pOverlapped; // Per I/Okey 를 받을 변수

// 1) Overlapped I/O 의 완료를 받는다 .dwResult = GetQueuedCompletionStatus(g_hCompletionPort, dwTransfered, pPerHandelKey, pOverlapped, INFINITE);

// 2) I/O 타입에 따라 I/O 를 처리한다 .switch(pOverlapped->eIOType){case IOTYPE_ACCEPT:

// Accept 처리 ~break;

case IOTYPE_CONNECT:// Connect 처리 ~break;

case IOTYPE_DISCONNECT:// Disconnect 처리 ~break;

case IOTYPE_SEND:// Send 처리 ~break;

case IOTYPE_RECEIVE:// Receive 처리 ~break;

};

// @) I/O TheadDWORD dwResult; // GetQueued…() 함수의 결과DWORD dwTransfered; // 전송된 Byte 수를 저장할 변수ULONG_PTR pPerHandelKey; // Per Handle Key 의 Pointer 를 받을 변수MY_OVERLAPPED* pOverlapped; // Per I/Okey 를 받을 변수

// 1) Overlapped I/O 의 완료를 받는다 .dwResult = GetQueuedCompletionStatus(g_hCompletionPort, dwTransfered, pPerHandelKey, pOverlapped, INFINITE);

// 2) I/O 타입에 따라 I/O 를 처리한다 .switch(pOverlapped->eIOType){case IOTYPE_ACCEPT:

// Accept 처리 ~break;

case IOTYPE_CONNECT:// Connect 처리 ~break;

case IOTYPE_DISCONNECT:// Disconnect 처리 ~break;

case IOTYPE_SEND:// Send 처리 ~break;

case IOTYPE_RECEIVE:// Receive 처리 ~break;

};

2. 처리할 내용은 eIOType 으로 결정한다 .

1. Overlapped 구조체의 포인터를 받는다 .

3. 해당 처리를 수행한다 .

IOCP (3)

CGCIICho sanghyun’s Game Classes II

실행처리 ICGExecutor 와 ICGExecutable (1)

CGCII 에서는 이와 같은 방법이 아니라 상속을 사용한다 . CGCII 에서는 'ICGExecutor' 와 'ICGExecutable' 을 정의하여 사용한다 .

CGCIICho sanghyun’s Game Classes II

class ICGExecutable : public OVERLAPPEDvirtual public ICGReferenceCount

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered) PURE;};

class ICGExecutable : public OVERLAPPEDvirtual public ICGReferenceCount

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered) PURE;};

Executable 을 아래와 같이 정의한다 .

1. OVERLAPPED 객체를 상속받는다 .

2. ICGReferenceCount 를 상속받는다 .

3. 순수가상함수 실행할 내용을 작성할 함수 .

실행처리 ICGExecutor 와 ICGExecutable (2)

CGCIICho sanghyun’s Game Classes II

이것을 상속받아 여러 다양한 Socket I/O 용 Exectuable 을 뿐만 아니라 다양한 처리를 위한 Executable 을 만들 수 있다 .

실행처리 ICGExecutor 와 ICGExecutable (6)

CGCIICho sanghyun’s Game Classes II

bool CExecutableReceive TCP::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered){

return true;}

bool CExecutableReceive TCP::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered){

return true;}

ICGExecutable 을 상속받아 여러 Send 용과 Receive 용 Exectuable 을 정의한다 .

Receive 완료 후 처리 내용

class CExecutableReceiveTCP : virtual public ICGExecutable

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);protected:

…};

class CExecutableReceiveTCP : virtual public ICGExecutable

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);protected:

…};

1. ICGExecutable 을 상속받는다 .

2. ProcessExecute 를 재정의한다 .

실행처리 ICGExecutor 와 ICGExecutable (3)

CGCIICho sanghyun’s Game Classes II

이것을 Overlapped I/O 에 건다 .

CGPTR<CExecutableReceiveTCP> pExecutable = NEW<CExecutableReceiveTCP>();…

DWORD dwBytes;DWORD dwFlag = 0;

pExecutable->AddRef();

WSARecv(m_hSocket, pWSABuffer, iBufferCount, &dwBytes, &dwFlag, pExecutable->get())

// Error 처리 생략…

CGPTR<CExecutableReceiveTCP> pExecutable = NEW<CExecutableReceiveTCP>();…

DWORD dwBytes;DWORD dwFlag = 0;

pExecutable->AddRef();

WSARecv(m_hSocket, pWSABuffer, iBufferCount, &dwBytes, &dwFlag, pExecutable->get())

// Error 처리 생략…

1. Executable 을 생성한다 .

2. OVERLAPPED 를 걸기 전 AddRef 한다 .

3. OVERLAPPED I/O 를 건다 .

실행처리 ICGExecutor 와 ICGExecutable (4)

void CExecutorIOCP::Execute(DWORD p_tickWait){ while(bDone) {

DWORD dwResult;DWORD dwBytes;ULONG_PTR pHKey;LPOVERLAPPED pOverlapped;

dwResult = GetQueuedCompletionStatus(m_hCP,&dwBytes,&pHKey, &pOverlapped, p_tickWait);

ICGExecutable* pExecuable = static_cast<ICGExecutable*>(pOverlapped);

pExecutable->ProcessExecute(dwResult, dwBytes);

pExecutable->Release(); }}

void CExecutorIOCP::Execute(DWORD p_tickWait){ while(bDone) {

DWORD dwResult;DWORD dwBytes;ULONG_PTR pHKey;LPOVERLAPPED pOverlapped;

dwResult = GetQueuedCompletionStatus(m_hCP,&dwBytes,&pHKey, &pOverlapped, p_tickWait);

ICGExecutable* pExecuable = static_cast<ICGExecutable*>(pOverlapped);

pExecutable->ProcessExecute(dwResult, dwBytes);

pExecutable->Release(); }}

CGCIICho sanghyun’s Game Classes II

GetQueuedCompletionPort() 함수에서 아래 처럼 처리한다 .

이렇게 되면 IOCP 객체 쪽에서는 GetQueuedCompletionStatus() 의 처리 내용이 무엇인지 알 필요가 없다 !!!!

결합도가 낮아진다 .

6. OVERLAPPED 의 포인터를 얻는다 .

7. ICGExecutable 로 Casting 을 한다 .

8. ProcessExecute 를 호출한다 !

9. pExecutable 을 Release() 한다 .

실행처리 ICGExecutor 와 ICGExecutable (5)

CGCIICho sanghyun’s Game Classes II

bool CExecutableWORK ::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered){

printf(“ 실행했음 !!!”);return true;

}

bool CExecutableWORK ::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered){

printf(“ 실행했음 !!!”);return true;

}

WSARecv 나 WSASend 같은 Overlapped I/O 함수가 아니더라도 ICGExecutable 을 상속받아 정의하기만 하면 어떠한 것도 IOCP Executor 에서 실행 가능하다 .

class CExecutableWORK : virtual public ICGExecutable

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);protected:

…};

class CExecutableWORK : virtual public ICGExecutable

{public:

virtual bool ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);protected:

…};

1. ICGExecutable 을 상속받는다 .

2. ProcessExecute 를 재정의한다 .

CGPTR<CExecutableWORK> pExecutable = NEW<CExecutableWORK>();…

pExecutable->AddRef();

PostQueuedCompletionStatus (m_hCP, 0, nullptr, pExecutable)

// Error 처리 생략…

CGPTR<CExecutableWORK> pExecutable = NEW<CExecutableWORK>();…

pExecutable->AddRef();

PostQueuedCompletionStatus (m_hCP, 0, nullptr, pExecutable)

// Error 처리 생략…

3. PostQueuedCompletionStatus() 를 사용하면 된다 !

실행처리 ICGExecutor 와 ICGExecutable (7)

CGCIICho sanghyun’s Game Classes II

이렇게 되면 어떤 형태의 실행처리도 IOCP 에 걸어 통합 처리가 가능하다 .

.

.

.PostExecute(ICGExecutable* , …)

실행처리 ICGExecutor 와 ICGExecutable (8)

CGCIICho sanghyun’s Game Classes II

범용적인 용도로 사용되는 다양한 Executable 도 정의할 수 있다 .

일정시간마다 ProcessExecute 를 실행함.(Schedule 에 의해 동작 )

특정 함수를 실행함 .( 전역함수 , 멤버함수 , 람다함수 )

여러 Executable 을 모아서 실행하고 모두 완료 후 특정 작업을 실행 함 .( 특정함수 실행 , 메시지 전송 , 블록킹 )

실행처리 ICGExecutor 와 ICGExecutable (9)

CGCIICho sanghyun’s Game Classes II

실행처리

간단하게 함수도 Executor 에 걸 수 있다 .

CGPTR<CGExecutor::CCompletionPortThread> g_pexecutorTest;

void fTest(){

printf( "함수를 실행함 . \n");};

void main(){

// 1) Executor 를 생성 . g_pexecutorTest = NEW<CGExecutor::CCompletionPortThread>();

// 2) Thread 2 개를 시작한다 . g_pexecutorTest ->Start(2);

// 3) Executable 객체를 생성한다 .CGPTR<CGExecutable::CFunction> pExecutable = NEW<CGExecutable::CFunction>();

// 4) 함수를 설정한다 .pExecutable->SetFunction(fTest);

// 5) 실행을 건다 .g_pexecutorTest->PostExecute(pExecutable);

}

CGPTR<CGExecutor::CCompletionPortThread> g_pexecutorTest;

void fTest(){

printf( "함수를 실행함 . \n");};

void main(){

// 1) Executor 를 생성 . g_pexecutorTest = NEW<CGExecutor::CCompletionPortThread>();

// 2) Thread 2 개를 시작한다 . g_pexecutorTest ->Start(2);

// 3) Executable 객체를 생성한다 .CGPTR<CGExecutable::CFunction> pExecutable = NEW<CGExecutable::CFunction>();

// 4) 함수를 설정한다 .pExecutable->SetFunction(fTest);

// 5) 실행을 건다 .g_pexecutorTest->PostExecute(pExecutable);

}

1. 함수를 정의함 .

2. Executable 객체 생성

4. Executor 에 실행 요청

3. Executable 에 함수 설정

ICGExecutor 와 ICGExecutable (11)

CGCIICho sanghyun’s Game Classes II

실행처리

더 간단하게 람다 (Lambda) 함수로도 설정 가능하다 .

// 1) Executable 객체를 생성한다 .CGPTR<CGExecutable::CLambda> pExecutable = NEW<CGExecutable::CLambda>();

// 2) 함수를 설정한다 .pExecutable->SetFunction([](){

printf( "함수를 실행함 . \n");});

// 3) 실행을 건다 .g_pexecutorTest->PostExecute(pExecutable);

// 1) Executable 객체를 생성한다 .CGPTR<CGExecutable::CLambda> pExecutable = NEW<CGExecutable::CLambda>();

// 2) 함수를 설정한다 .pExecutable->SetFunction([](){

printf( "함수를 실행함 . \n");});

// 3) 실행을 건다 .g_pexecutorTest->PostExecute(pExecutable);

1. Executable 객체 생성

3. Executor 에 실행 요청

2. Executable 에 람다함수 설정

TickCount 로 시간을 정해 실행시킬 수도 있다 . (Schedule 에 의한 실행 )

// 3) 현재부터 6 초 후에 실행한다 .g_pexecutorTest->PostExecute(pExecutable, GET_TICKCOUNT()+6000);// 3) 현재부터 6 초 후에 실행한다 .g_pexecutorTest->PostExecute(pExecutable, GET_TICKCOUNT()+6000);

실행 시간 설정 : 현재 TICK+6 초

ICGExecutor 와 ICGExecutable (12)

Batch ExecutionCGCIICho sanghyun’s Game Classes II

실행처리

일괄 처리를 수행할 수도 있다 .

// 1) Executable 객체를 생성한다 .CGPTR<CGExecutable::CBatchWait> pexecutableBatch = NEW<CGExecutable::CBatchWait>();

// 2) 실행할 Executable 을 추가한다 .for(int i=0; i<10; ++i){

CGPTR<CTestExecutable> pexecutable = NEW<CTestExecutable>();

pexecutableBatch->QueueExecutable(pexecutable);}

// 3) 실행을 건다 .pexecutableBatch->RequestExecute(g_pexecutorTest);

// 4) 완료를 기다린다 .pexecutableBatch->WaitExecuteCompletion();

// 1) Executable 객체를 생성한다 .CGPTR<CGExecutable::CBatchWait> pexecutableBatch = NEW<CGExecutable::CBatchWait>();

// 2) 실행할 Executable 을 추가한다 .for(int i=0; i<10; ++i){

CGPTR<CTestExecutable> pexecutable = NEW<CTestExecutable>();

pexecutableBatch->QueueExecutable(pexecutable);}

// 3) 실행을 건다 .pexecutableBatch->RequestExecute(g_pexecutorTest);

// 4) 완료를 기다린다 .pexecutableBatch->WaitExecuteCompletion();

1. Batch Executable 객체 생성

3. 일괄 처리를 요청한다 .

2. 일괄 실행할 Executable 을 추가한다 .

4. 완료를 대기한다 . ( 모두 완료될 때까지 블록킹된다 .)

CGCIICho sanghyun’s Game Classes II

실행처리

기본 (Default) Executor 를 사용한다면 더 간단해 진다 .

// 1) 일반 함수를 실행할 경우 ..POST_EXECUTE(fTest);

// 2) 람다로 실행함수를 설정한다 .POST_EXECUTE([](){

printf( "함수를 실행함 . \n");});

// 1) 일반 함수를 실행할 경우 ..POST_EXECUTE(fTest);

// 2) 람다로 실행함수를 설정한다 .POST_EXECUTE([](){

printf( "함수를 실행함 . \n");});

void fTest(){

printf( "함수를 실행함 . \n");};

void fTest(){

printf( "함수를 실행함 . \n");};

Default Executor

CGCIICho sanghyun’s Game Classes II

실행처리

모든 처리는 최대한 하나의 Thread-Pool 에 의해 관리한다 .

결론

Thread-Pool 역할을 할 ICGExecutor 클래스를 정의하여 IOCP 로 구현한다 .

I/O 처리뿐만 아니라 모든 처리를 ICGExcutable 을 상속받아 정의하기만 하면 하나의 Thread-Pool 에서 관리가 가능해진다 .

CGCIICho sanghyun’s Game Classes II

질문 ?예외처리

질문 [email protected]