gcgc- cgcii 서버 엔진에 적용된 기술 (4) - executing system
TRANSCRIPT
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 에서 관리가 가능해진다 .