bdfgdfg
[온라인 서버] 네트워크 라이브러리 - Thread 클래스 본문
Thread Class
- Thread 클래스는 사용자가 쉽게 틱(Tick) 쓰레드를 관리하기 위해 만든다.
- 틱 쓰레드는 정해진 시간마다 특정 연산을 하기 원할 때 쓰이는데 온라인 게임 서버에서는 꼭 필요한 기능.
-> 일반적으로 좀비상태의 클라이언트의 연결처리를 할때 많이 쓰인다고 한다.
-> 좀비상태는 사용자가 연결만 있고 아무것도 하지 않는 상태를 의미.
-> 이 상태는 클라이언트와 서버간의 연결 상태가 좋지않거나, 특정 사용자가 악의적인 목적으로 이런 상태를 만들 수 있다.
-> 그래서 보통 클라이언트에서는 일정 시간마다 패킷을 보내고 서버에서는 일정 시간안에 그 패킷이 도착하지 않으면
클라이언트와의 연결을 끊어버린다. (좀비상태 방지 가능)
- 틱 쓰레드는 위의 역할뿐만 아니라 온라인 게임 서버에서 동기화가 필요한 작업을 할 떄 서버의 시간을 기준으로 하는
데 이때도 서버 틱(Server Tick)이란 단위를 두어 처리할 수 있도록 해준다.
간단하니 바로 헤더와 cpp파일을 본다.
#pragma once
#include "std.h"
class Thread
{
public:
Thread();
virtual ~Thread();
public:
// 틱 쓰레드는 waitTick / 1000 만큼 대기하면서 실행.
// 즉 waitTick / 1000 시간마다 자식의 OnProcess가 처리 된다.
bool CreateThread(DWORD waitTick);
void DestroyThread();
void Run();
void Stop();
void TickThread();
virtual void OnProcess() abstract; // 순수 가상함수.
// 몇번의 틱이 돌았는지 반환.
inline DWORD GetTickCount() { return m_tickCount; }
bool IsRun() { return m_bRunState; }
protected:
HANDLE m_hThread; // 쓰레드 핸들
WSAEVENT m_hQuitEvent; // 이벤트 핸들 (쓰레드 종료를 위해 존재한다)
bool m_bRunState;
DWORD m_waitTick;
DWORD m_tickCount;
};
#include "Thread.h"
Thread::Thread() : m_hThread(INVALID_HANDLE_VALUE),m_bRunState(false),m_tickCount(0),m_waitTick(0)
{
// manual-reset, non-signaled상태로 생성.
m_hQuitEvent = ::WSACreateEvent();
}
Thread::~Thread()
{
::CloseHandle(m_hQuitEvent);
if (m_hThread)
::CloseHandle(m_hThread);
}
unsigned int WINAPI CallTickThread(LPVOID p) // this가 넘어온다.
{
Thread* pTickThread = (Thread*)p;
pTickThread->TickThread(); // 쓰레드는 틱 쓰레드 함수를 호출한다.
return 0;
}
bool Thread::CreateThread(DWORD waitTick) // 대기할 틱이 인자로 넘어온다.
{
UINT uiThreadId = 0;
// 대기상태로 쓰레드 생성
m_hThread = (HANDLE)::_beginthreadex(nullptr, 0, CallTickThread, this, CREATE_SUSPENDED, &uiThreadId);
if (m_hThread)
{
// 로그 클래스 추가 예정 (에러처리)
return false;
}
m_waitTick = waitTick;
return true;
}
void Thread::DestroyThread()
{
Run(); // 쓰레드가 멈춘상태일 수 있으므로. 먼저 Run을 호출(돌고있다해도 조건체크를 통해 무시된다)
::SetEvent(m_hQuitEvent); // 이벤트를 signaled상태로 바꾼다. -> 이 이벤트가 켜지면 쓰레드는 TickThread함수에서 빠져나온다.
::WaitForSingleObject(m_hThread, INFINITE); // 쓰레드가 종료될때까지 대기한다.
}
void Thread::Run()
{
// 쓰레드를 동작시킨다.
if (m_bRunState == false)
{
m_bRunState = true;
::ResumeThread(m_hThread);
}
}
void Thread::Stop()
{
// 쓰레드를 멈춘다.
if (m_bRunState == true)
{
m_bRunState = false;
::SuspendThread(m_hThread);
}
}
void Thread::TickThread()
{
// 쓰레드가 처리하는 메인 함수.
// 자식 클래스의 OnProcess를 호출한다.
while (true)
{
DWORD ret = ::WaitForSingleObject(m_hQuitEvent, m_waitTick); // waitTick만큼 대기.
if (ret == WAIT_OBJECT_0) // 만약 m_hQuitEvent가 signaled라면 (DestroyThread함수가 호출되었다는 소리)
{
break; // 빠져나온다.
}
else if(ret == WAIT_TIMEOUT) // 그게아니라 타임아웃이라면. -> 틱만큼 대기하다 OnProcess호출. (틱만큼 대기 -> OnProcess호출이 반복)
{
++m_tickCount;
OnProcess();
}
}
}
먼저 생성자, 소멸자,쓰레드의 메인함수를 본다.
Thread::Thread() : m_hThread(INVALID_HANDLE_VALUE),m_bRunState(false),m_tickCount(0),m_waitTick(0)
{
// manual-reset, non-signaled상태로 생성.
m_hQuitEvent = ::WSACreateEvent();
}
Thread::~Thread()
{
::CloseHandle(m_hQuitEvent);
if (m_hThread)
::CloseHandle(m_hThread);
}
unsigned int WINAPI CallTickThread(LPVOID p) // this가 넘어온다.
{
Thread* pTickThread = (Thread*)p;
pTickThread->TickThread(); // 쓰레드는 틱 쓰레드 함수를 호출한다.
return 0;
}
생성자에서 이벤트를 생성하고 있는데.
위의 이벤트는 DestroyThread함수에서 생성한 이벤트를 signaled상태로 바꾸어 쓰레드가 TickThread함수를 빠져나오게 한다. (쓰레드 종료)
그리고 쓰레드 메인 함수에서는 인자로 넘어온(this) 객체를 통해 TickThread함수를 호출하게 된다.
bool Thread::CreateThread(DWORD waitTick) // 대기할 틱이 인자로 넘어온다.
{
UINT uiThreadId = 0;
// 대기상태로 쓰레드 생성
m_hThread = (HANDLE)::_beginthreadex(nullptr, 0, CallTickThread, this, CREATE_SUSPENDED, &uiThreadId);
if (m_hThread)
{
// 로그 클래스 추가 예정 (에러처리)
return false;
}
m_waitTick = waitTick;
return true;
}
void Thread::DestroyThread()
{
Run(); // 쓰레드가 멈춘상태일 수 있으므로. 먼저 Run을 호출(돌고있다해도 조건체크를 통해 무시된다)
::SetEvent(m_hQuitEvent); // 이벤트를 signaled상태로 바꾼다. -> 이 이벤트가 켜지면 쓰레드는 TickThread함수에서 빠져나온다.
::WaitForSingleObject(m_hThread, INFINITE); // 쓰레드가 종료될때까지 대기한다.
}
쓰레드의 생성. 위에서 말한 CallTickThread를 호출하며 인자로 this를 넘겨주고 있다.
-> this를 넘기는 이유는 쓰레드 메인함수는 전역함수이므로.
그리고 CREATE_SUSPENDED 인자를 넘겨 쓰레드가 바로 실행되지 않고 대기상태로 만들어준다.
-> 사용자가 원하는 시점에서 Run함수를 호출시켜 쓰레드가 돌아가게끔 한다.
마지막으로 CreateThread함수를 호출하면서 넘긴 인자값인 Tick을 멤버에 저장한다.
-> 기본단위는 waitTick / 1000이며 (밀리세컨드 1 / 1000) 1000을 넘겨주면 1초마다 대기하며 틱 쓰레드 함수를 돈다.
DestroyThread함수는 TickThread 함수를 돌던 쓰레드를 이벤트를 통해 빠져나오게하여 쓰레드를 종료시킨다.
void Thread::Run()
{
// 쓰레드를 동작시킨다.
if (m_bRunState == false)
{
m_bRunState = true;
::ResumeThread(m_hThread);
}
}
void Thread::Stop()
{
// 쓰레드를 멈춘다.
if (m_bRunState == true)
{
m_bRunState = false;
::SuspendThread(m_hThread);
}
}
각각 쓰레드를 멈추게 하는 기능 동작하게 하는 기능.
void Thread::TickThread()
{
// 쓰레드가 처리하는 메인 함수.
// 자식 클래스의 OnProcess를 호출한다.
while (true)
{
DWORD ret = ::WaitForSingleObject(m_hQuitEvent, m_waitTick); // waitTick만큼 대기.
if (ret == WAIT_OBJECT_0) // 만약 m_hQuitEvent가 signaled라면 (DestroyThread함수가 호출되었다는 소리)
{
break; // 빠져나온다.
}
else if(ret == WAIT_TIMEOUT) // 그게아니라 타임아웃이라면. -> 틱만큼 대기하다 OnProcess호출. (틱만큼 대기 -> OnProcess호출이 반복)
{
++m_tickCount;
OnProcess();
}
}
}
쓰레드가 Run상태가 되고 쓰레드 메인함수인 CallTickThread에 진입하면 위의 함수가 호출이 된다.
처음에 CreateThread함수를 호출할 때 인자로 넘긴 waitTick값만큼 쓰레드는 대기하면서 OnProcess함수를 호출하게 된다.
-> 즉 1000을 넘겼다면 1초마다 OnProcess함수를 처리.
OnProcess함수는 순수 가상함수로 자식에서 재정의 해야한다.
그리고 DestroyThread함수 호출을 통해 m_hQuitEvent멤버가 signaled상태가 되면 WAIT_OBJECT_0을 반환하고 함수를 빠져나온다 -> 종료
TickThread를 사용한 간단한 예제.
(헤더 + cpp파일 이어서)
//------HEADER---------
class TickThreadTest : public Thread
{
public:
void OnProcess() override;
};
//------HEADER---------
//------CPP---------
void TickThreadTest::OnProcess()
{
std::cout << "현재 틱 횟수 : " << m_tickCount << std::endl;
}
//------CPP---------
이 처럼 TickThread함수는 자식의 OnProcess함수를 호출하는데 단순히 얼마나 틱 시간안에 몇번이나 이 함수가 호출될지 정의한 함수.
int main()
{
TickThreadTest t;
t.CreateThread(1000); // waitTick값을 넘겨준다. 1000 / 1000이므로 1초 마다 OnProcess호출.
t.Run(); // 대기중이던 쓰레드를 깨운다.
::Sleep(10000); // 10초대기
t.DestroyThread();
return 0;
}
내 컴퓨터는 10초를 대기하면서 총 밑의 사진과 같이 호출 되었음.
본문은 강정중님의 온라인 게임 서버 서적을 참고하였습니다.
'게임프로그래밍 > 서버 책' 카테고리의 다른 글
[온라인 서버] 네트워크 라이브러리 - Log Class (0) | 2022.01.10 |
---|---|
[온라인 서버] 네트워크 라이브러리 - Queue Class (0) | 2022.01.09 |
[온라인 서버] 네트워크 라이브러리 - RingBuffer 클래스 ★ (메모리 풀!) (0) | 2022.01.05 |
[온라인 서버] 네트워크 라이브러리 - Singleton Class (0) | 2022.01.05 |
[온라인 서버] 네트워크 라이브러리 - Monitor Class (0) | 2022.01.04 |