Transcript Document

CGCII
Cho sanghyun’s Game Classes II
Executing System
CGCII
실행처리
Thread (1)
Cho sanghyun’s Game Classes II
 일반적인 Unix 기반 서버
Socket당 Thread를 1:1로 물림
Thread Pool
• Socket당 하나의 Thread의 물려 버린다.
• 접속 수만큼 Thread가 필요해 사실상 동접이 제한
• 극악의 비효율! (Thread수>>>Core수)
• 효율적으로 제작하기가 불가능 그 자체!
• Apache MPM(Multi-Processing Modules) Prefork or Worker와 같은 Unix 기반 서버
CGCII
실행처리
Thread (2)
Cho sanghyun’s Game Classes II
 비동기 I/O 싱글 쓰레드 기반 서버
모든 Socket은 하나의 Thread로…
• 하나의 Thread로 모든 Socket을 처리.
• 비동기 이벤트 방식 혹은 폴 방식 Socket I/O를 사용.
따라서 동시 접속 수는 거의 제한이 없으면 단일 쓰레드라 성능이 극히 떨어짐.
• 효율적으로 제작하려면 추가적인 노력이 듬.
• Node.js, Nginx와 같은 류의 Unix 기반 서버류 혹은 AsyncSelect 등을 사용하는 Window 기반 서버류
CGCII
실행처리
Thread (3)
Cho sanghyun’s Game Classes II
 일반적인 IOCP를 사용하는 Window 서버
I/O Thread Pool
•일반적으로 IOCP와 다중 Thread로 구성
•기본적 Socket I/O의 처리나 받은 메시지를 큐잉하는 역할만 수행
Work Thread
•단일 혹은 멀티 Thread로 구성
•큐잉된 메시지를 꺼내와 실질적인 메시지의 처리
기타 Thread
•잡다한 처리를 할 Thread
•몇 개가 될지 대중 없다.
• 비동기식 처리기 때문에 접속자 제한이 없음.
• 모든 Core를 활용할 수 있음.
• 국내외 IOCP를 사용한 Windows 서버 엔진류
• Core에 비해 많은 Thread수를 강요.
• 복잡한 Thread 구조.
• 완벽한 부하균형이 힘들어 과부하 시 I/O와 Work의 쏠
림 현상이 나타나 서버가 불안해지는 경우가 많음.
CGCII
실행처리
Thread (4)
Cho sanghyun’s Game Classes II
 CGCII는 하나의 Thread Pool로 통해 로드 밸런싱!
Scheduler
•실행 예약된 것을 Executor에 걸어주는 역할
•Priority Queue로 최적화.
통합 Thread Pool
Socket I/O, Work Thread 역할, 기타 잡다한 처리 모두 하나의 Thread Pool로 통
합 실행할 수 있다.
 최적의 Core대 Thread수를 가질 수 있다.
 Core와 Thread의 사용효율을 극대화 시킨다.
 단일 Load Balancer이므로 I/O처리에 있어 쏠림 현상이 최소화된다.
CGCII
실행처리
IOCP (1)
Cho sanghyun’s Game Classes II
IOCP(I/O Completion Port)
= Thread Pool
IOCP는 결론적으로 쓰레드풀을 위한 시스템
 일반적으로 Socket I/O 처리에 Overlapped I/O를 처리하기 위해 사용한다.
CGCII
실행처리
IOCP (2)
Cho sanghyun’s Game Classes II
 일반적인 IOCP를 사용한 처리…
 일단 OVERLAPPED 구조체를 상속받은 구조체를 정의한다.
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;
};
3. I/O Type
1.
OVERLAPPED structure를 상속받음.
2. I/O Type을 설정할 변수 추가
CGCII
실행처리
IOCP (3)
Cho 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;
1.
MY_OVERLAPPED 객체를 동적 생성한다.
2.
I/O 타입을 설정한다.
3.
이렇게 넘김~
WSARecv(hSocket, &pOverlapped->sBuffer, 1, &dwByte, &dwFlag, g_pOverlapped, NULL);
CGCII
IOCP (3)
실행처리
Cho sanghyun’s Game Classes II
 일반적인 IOCP를 사용한 처리…
// @) I/O Thead
DWORD
DWORD
ULONG_PTR
MY_OVERLAPPED*
dwResult;
dwTransfered;
pPerHandelKey;
pOverlapped;
//
//
//
//
GetQueued…()함수의 결과
전송된Byte수를 저장할 변수
Per Handle Key의Pointer를 받을 변수
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;
1.
Overlapped 구조체의 포인터를 받는다.
2.
처리할 내용은 eIOType으로 결정한다.
3.
해당 처리를 수행한다.
case IOTYPE_CONNECT:
// Connect처리~
break;
case IOTYPE_DISCONNECT:
// Disconnect처리~
break;
case IOTYPE_SEND:
// Send처리~
break;
case IOTYPE_RECEIVE:
// Receive처리~
break;
};
CGCII
실행처리
ICGExecutor와 ICGExecutable (1)
Cho sanghyun’s Game Classes II
 CGCII에서는 이와 같은 방법이 아니라 상속을 사용한다.
 CGCII에서는 'ICGExecutor'와 'ICGExecutable'을 정의하여 사용한다.
CGCII
실행처리
ICGExecutor와 ICGExecutable (2)
Cho sanghyun’s Game Classes II
 Executable을 아래와 같이 정의한다.
class ICGExecutable :
1. OVERLAPPED 객체를 상속받는다.
public
OVERLAPPED
virtual public ICGReferenceCount
2. ICGReferenceCount를 상속받는다.
{
public:
virtual
bool
ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered) PURE;
};
3. 순수가상함수 실행할 내용을 작성할 함수.
CGCII
실행처리
ICGExecutor와 ICGExecutable (6)
Cho sanghyun’s Game Classes II
 이것을 상속받아 여러 다양한 Socket I/O용 Exectuable을 뿐만 아니라 다양한
처리를 위한 Executable을 만들 수 있다.
CGCII
실행처리
ICGExecutor와 ICGExecutable (3)
Cho sanghyun’s Game Classes II
 ICGExecutable을 상속받아 여러 Send용과 Receive용 Exectuable을 정의한다.
class CExecutableReceiveTCP :
1. ICGExecutable을 상속받는다.
virtual public ICGExecutable
{
2. ProcessExecute를 재정의한다.
public:
virtual
bool
ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);
protected:
…
};
bool CExecutableReceive TCP::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered)
{
Receive 완료 후 처리 내용
return
}
true;
CGCII
실행처리
ICGExecutor와 ICGExecutable (4)
Cho sanghyun’s Game Classes II
 이것을 Overlapped I/O에 건다.
CGPTR<CExecutableReceiveTCP> pExecutable
…
1.
DWORD dwBytes;
DWORD dwFlag
= 0;
pExecutable->AddRef();
= NEW<CExecutableReceiveTCP>();
2.
Executable을 생성한다.
OVERLAPPED를 걸기 전 AddRef한다.
WSARecv(m_hSocket, pWSABuffer, iBufferCount, &dwBytes, &dwFlag, pExecutable->get())
// Error 처리 생략…
3.
OVERLAPPED I/O를 건다.
CGCII
실행처리
ICGExecutor와 ICGExecutable (5)
Cho sanghyun’s Game Classes II
 GetQueuedCompletionPort()함수에서 아래 처럼 처리한다.
void CExecutorIOCP::Execute(DWORD p_tickWait)
{
while(bDone)
{
DWORD
dwResult;
DWORD
dwBytes;
ULONG_PTR
pHKey;
LPOVERLAPPED
pOverlapped;
6.
OVERLAPPED의 포인터를 얻는다.
dwResult = GetQueuedCompletionStatus(m_hCP,&dwBytes,&pHKey, &pOverlapped, p_tickWait);
ICGExecutable*
pExecuable
= static_cast<ICGExecutable*>(pOverlapped);
pExecutable->ProcessExecute(dwResult, dwBytes);
pExecutable->Release();
}
}
9.
8.
7.
ICGExecutable로 Casting을 한다.
ProcessExecute를 호출한다!
pExecutable을 Release()한다.
 이렇게 되면 IOCP 객체 쪽에서는 GetQueuedCompletionStatus()의 처리 내용이 무엇인지 알
필요가 없다!!!!
 결합도가 낮아진다.
CGCII
실행처리
ICGExecutor와 ICGExecutable (7)
Cho sanghyun’s Game Classes II
 WSARecv나 WSASend같은 Overlapped I/O 함수가 아니더라도 ICGExecutable을 상속받
아 정의하기만 하면 어떠한 것도 IOCP Executor에서 실행 가능하다.
class CExecutableWORK :
1. ICGExecutable을 상속받는다.
virtual public ICGExecutable
{
2. ProcessExecute를 재정의한다.
public:
virtual
bool
ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered);
protected:
…
};
bool CExecutableWORK ::ProcessExecute(DWORD p_dwReturn, DWORD p_dwTransfered)
{
printf(“실행했음!!!”);
return
true;
}
CGPTR<CExecutableWORK> pExecutable = NEW<CExecutableWORK>();
…
pExecutable->AddRef();
3. PostQueuedCompletionStatus()를 사용하면 된다!
PostQueuedCompletionStatus (m_hCP, 0, nullptr, pExecutable)
// Error 처리 생략…
CGCII
실행처리
ICGExecutor와 ICGExecutable (8)
Cho sanghyun’s Game Classes II
 이렇게 되면 어떤 형태의 실행처리도 IOCP에 걸어 통합 처리가 가능하다.
PostExecute(ICGExecutable* , …)
CGCII
실행처리
ICGExecutor와 ICGExecutable (9)
Cho sanghyun’s Game Classes II
 범용적인 용도로 사용되는 다양한 Executable도 정의할 수 있다.
일정시간마다 ProcessExecute를 실행함.
(Schedule에 의해 동작)
특정 함수를 실행함.
(전역함수, 멤버함수, 람다함수)
여러 Executable을 모아서 실행하고 모
두 완료 후 특정 작업을 실행 함.
(특정함수 실행, 메시지 전송, 블록킹)
CGCII
실행처리
ICGExecutor와 ICGExecutable (11)
Cho sanghyun’s Game Classes II
 간단하게 함수도 Executor에 걸 수 있다.
CGPTR<CGExecutor::CCompletionPortThread> g_pexecutorTest;
void fTest()
{
printf("함수를 실행함. \n");
};
1.
함수를 정의함.
void main()
{
// 1) Executor를 생성.
g_pexecutorTest = NEW<CGExecutor::CCompletionPortThread>();
// 2) Thread 2개를 시작한다.
g_pexecutorTest ->Start(2);
2. Executable 객체 생성
// 3) Executable 객체를 생성한다.
CGPTR<CGExecutable::CFunction> pExecutable = NEW<CGExecutable::CFunction>();
// 4) 함수를 설정한다.
pExecutable->SetFunction(fTest);
3.
Executable에 함수 설정
// 5) 실행을 건다.
g_pexecutorTest->PostExecute(pExecutable);
}
4.
Executor에 실행 요청
CGCII
실행처리
ICGExecutor와 ICGExecutable (12)
Cho sanghyun’s Game Classes II
 더 간단하게 람다(Lambda) 함수로도 설정 가능하다.
// 1) Executable 객체를 생성한다.
CGPTR<CGExecutable::CLambda> pExecutable = NEW<CGExecutable::CLambda>();
// 2) 함수를 설정한다.
pExecutable->SetFunction([]()
{
printf("함수를 실행함. \n");
});
1.
2.
Executable 객체 생성
Executable에 람다함수 설정
// 3) 실행을 건다.
g_pexecutorTest->PostExecute(pExecutable);
3.
Executor에 실행 요청
 TickCount로 시간을 정해 실행시킬 수도 있다. (Schedule에 의한 실행)
// 3) 현재부터 6초 후에 실행한다.
g_pexecutorTest->PostExecute(pExecutable, GET_TICKCOUNT()+6000);
실행 시간 설정: 현재 TICK+6초
CGCII
실행처리
Batch Execution
Cho 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
1.
2.
Batch Executable 객체 생성
일괄 실행할 Executable을 추가한다.
= NEW<CTestExecutable>();
pexecutableBatch->QueueExecutable(pexecutable);
}
// 3) 실행을 건다.
pexecutableBatch->RequestExecute(g_pexecutorTest);
// 4) 완료를 기다린다.
pexecutableBatch->WaitExecuteCompletion();
3. 일괄 처리를 요청한다.
4. 완료를 대기한다.
(모두 완료될 때까지 블록킹된다.)
CGCII
실행처리
Default Executor
Cho sanghyun’s Game Classes II
 기본(Default) Executor를 사용한다면 더 간단해 진다.
void fTest()
{
printf("함수를 실행함. \n");
};
// 1) 일반 함수를 실행할 경우..
POST_EXECUTE(fTest);
// 2) 람다로 실행함수를 설정한다.
POST_EXECUTE([]()
{
printf("함수를 실행함. \n");
});
CGCII
실행처리
결론
Cho sanghyun’s Game Classes II
 모든 처리는 최대한 하나의 Thread-Pool에 의해 관리한다.
 Thread-Pool 역할을 할 ICGExecutor 클래스를 정의하여 IOCP로 구현한다.
 I/O처리뿐만 아니라 모든 처리를 ICGExcutable을 상속받아 정의하기만 하면 하나의
Thread-Pool에서 관리가 가능해진다.
CGCII
예외처리
질문?
Cho sanghyun’s Game Classes II
질문?
[email protected]