KoreanFoodie's Study
[C++ 게임 서버] 6-2. JobQueue #1 본문
[C++ 게임 서버] 6-2. JobQueue #1
핵심 :
1. Command 패턴은, 실행될 동작에 대한 인터페이스를 만들어 구체적인 동작은 해당 인터페이스를 상속 받은 객체에서 결정하도록 하는 설계 기법이다.
2. Command 패턴과 JobQueue 를 이용하면, 이전에 Broadcast 로 인한 동작의 병목 현상(LOCK 으로 인한)을 줄일 수 있다.
이전 시간에 간단한 채팅 서버를 만들어 보았다. 다만 이전 방식은 Broadcast 를 하는 부분에서 병목 현상이 심각할 수 있다는 단점이 있었는데...
이를 해결하기 위해, 우리는 JobQueue 를 만들어 순차적으로 이 문제를 해결해볼 것이다. 일단 이번 시간에는 Command 패턴을 이용해 조악하게나마 JobQueue 를 구축해 보자.
Command 패턴이란?
: Command 패턴은, 실행될 동작에 대한 인터페이스를 만들어 구체적인 동작은 해당 인터페이스를 상속 받은 객체에서 결정하도록 하는 설계 기법이다. 더 자세한 설명은 이 글을 읽어보자. 대박이다!
예를 들어, 우리가 식당에서 음식을 주문한다고 해 보자. 그럼 우리는 주문할 요리를 주문서에 적어 전달할 것이고, 요리사는 그 주문서를 보고 요리를 한 다음, 완성된 요리를 전달할 것이다.
여기서 종이에 적힌 주문은 커맨드 역할을 하고, 주문서가 담긴 목록은 JobQueue 처럼 취급될 것이다.
이제, 본격적으로 예제 코드를 보자.
일단, 이제 클라이언트/서버가 하는 행동은 'JOB' 이라는 단위로 이해할 것이다. 이를 위해, IJob 이라는 인터페이스를 만든다.
class IJob
{
public:
virtual void Execute() { }
};
using JobRef = shared_ptr<IJob>;
이제 예를 들어, HP 를 회복하는 동작은 아래와 같이 정의될 것이다.
class HealJob : public IJob
{
public:
virtual void Execute() override
{
// _target은 찾아서
// _target->AddHP(_healValue);
cout << _target << "한테 힐" << _healValue << " 만큼 줌";
}
public:
uint64 _target = 0;
uint32 _healValue = 0;
};
이어서 Job 이 담기는 JobQueue 클래스를 만들어 보자.
class JobQueue
{
public:
void Push(JobRef job)
{
WRITE_LOCK;
_jobs.push(job);
}
JobRef Pop()
{
WRITE_LOCK;
if (_jobs.empty())
return nullptr;
JobRef ret = _jobs.front();
_jobs.pop();
return ret;
}
private:
USE_LOCK;
queue<JobRef> _jobs;
};
JobQueue 는 Job 을 담는 큐 역할을 할 뿐이다. 😅
그리고, Room 에서 JobQueue 를 추가한다.
#pragma once
#include "Job.h"
class Room
{
friend class EnterJob;
friend class LeaveJob;
friend class BroadcastJob;
private:
// 싱글쓰레드 환경인마냥 코딩
void Enter(PlayerRef player);
void Leave(PlayerRef player);
void Broadcast(SendBufferRef sendBuffer);
public:
// 멀티쓰레드 환경에서는 일감으로 접근
void PushJob(JobRef job) { _jobs.Push(job); }
void FlushJob();
private:
map<uint64, PlayerRef> _players;
JobQueue _jobs;
};
잘 보면, Enter, Leave, Broadcast 가 이제 private 으로 바뀐 것을 알 수 있다. 그 이유는, 우리는 이제 '구체적인 동작' 을 외부에서 호출하지 않고, Execute 를 통해 간접적으로 처리할 것이기 때문이다.
따라서 PushJob 을 통해 주문을 넣고, FlushJob 을 통해 쌓인 Job 들이 각 Job 의 특성에 맞게 순차적으로 처리될 것임을 유추할 수 있다. 😉
이제 Enter, Leave, Broadcast 에 대한 Job 들을 아래와 같이 정의해 주자.
// Room Jobs
class EnterJob : public IJob
{
public:
EnterJob(Room& room, PlayerRef player) : _room(room), _player(player)
{
}
virtual void Execute() override
{
_room.Enter(_player);
}
public:
Room& _room;
PlayerRef _player;
};
class LeaveJob : public IJob
{
public:
LeaveJob(Room& room, PlayerRef player) : _room(room), _player(player)
{
}
virtual void Execute() override
{
_room.Leave(_player);
}
public:
Room& _room;
PlayerRef _player;
};
class BroadcastJob : public IJob
{
public:
BroadcastJob(Room& room, SendBufferRef sendBuffer) : _room(room), _sendBuffer(sendBuffer)
{
}
virtual void Execute() override
{
_room.Broadcast(_sendBuffer);
}
public:
Room& _room;
SendBufferRef _sendBuffer;
};
그리고 Room 의 구현부는 아래와 같이 변한다 :
Room GRoom;
void Room::Enter(PlayerRef player)
{
_players[player->playerId] = player;
}
void Room::Leave(PlayerRef player)
{
_players.erase(player->playerId);
}
void Room::Broadcast(SendBufferRef sendBuffer)
{
for (auto& p : _players)
{
p.second->ownerSession->Send(sendBuffer);
}
}
void Room::FlushJob()
{
while (true)
{
JobRef job = _jobs.Pop();
if (job == nullptr)
break;
job->Execute();
}
}
잘 보면, Broadcast 를 하는 부분에는 더 이상 LOCK 이 없는 것을 확인할 수 있다.
물론 JobQueue 로 부터 Pop 을 할 때는 LOCK 이 잠깐 걸리긴 하지만, 각 Job 이 Execute 되는 과정에서는 LOCK 이 걸리지 않고 있다! 😮
위와 같이 Command 패턴을 사용하면, 각 Job 에 대한 동작을 다르게 만들어주면서, 동시에 싱글 쓰레드처럼 Job 들이 알아서 처리될 것이다. 다만, FlushJob 을 여러 곳에서 하면 안되고, 반드시 한 곳에서만 해 줘야 될 것이다.
실제로 ClientPacketHandler 에서, 클라이언트가 보낸 채팅 패킷을 서버가 어떻게 처리하는지를 보자.
bool Handle_C_CHAT(PacketSessionRef& session, Protocol::C_CHAT& pkt)
{
std::cout << pkt.msg() << endl;
Protocol::S_CHAT chatPkt;
chatPkt.set_msg(pkt.msg());
auto sendBuffer = ClientPacketHandler::MakeSendBuffer(chatPkt);
GRoom.PushJob(MakeShared<BroadcastJob>(GRoom, sendBuffer));
return true;
}
이제 직접적으로 Broadcast 하지 않고, 그냥 JobQueue 에 알맞은 타입의 Job 을 넣어주기만 하고 있다! 😁
'Game Dev > Game Server' 카테고리의 다른 글
[C++ 게임 서버] 6-4. JobQueue #3 (0) | 2023.12.20 |
---|---|
[C++ 게임 서버] 6-3. JobQueue #2 (0) | 2023.12.20 |
[C++ 게임 서버] 6-1. 채팅 실습 (0) | 2023.12.19 |
[C++ 게임 서버] 5-9. 패킷 자동화 #2 (0) | 2023.12.18 |
[C++ 게임 서버] 5-8. 패킷 자동화 #1 (0) | 2023.12.18 |