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]