KoreanFoodie's Study

[C++ 게임 서버] 4-3. Server Service 본문

Game Dev/Game Server

[C++ 게임 서버] 4-3. Server Service

GoldGiver 2023. 12. 5. 16:57

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 4-3. Server Service

핵심 :

1. IocpObject 는 안전성을 위해 shared_ptr 를 차용하는 것이 좋다.

2. 서버는 클라이언트와만 통신하지 않고, 다른 서버나 DB 서버와도 통신할 수 있다. 따라서 용도에 따라 세션의 생성 및 관리를 할 수 있도록, 서비스를 만들어 주면 편리하다.

우리는 이전에 IOCP Core 클래스를 만들어 핸들, 세션, 소켓과 관련한 기능들을 간단히 다룰 수 있도록 만들었다.

그런데 이전에 우리가 만든 IOCP 클래스는 리스너에서 Accept 를 받으면 세션을 임의로 생성하는 식으로 구현이 되어 있다.

 

그런데 사실 세션은 특정 서버에 종속되면 자유도가 떨어지게 된다. 우리는 일반적으로 게임 서버라고 하면 '클라이언트 <-> 서버' 사이의 통신만 떠올리는 경향이 있는데, 사실 DB 서버도 따로 있을 수 있고, 서버와 서버 끼리도 통신이 이루어질 수 있다.

따라서 '개념적으로' 세션을 어떻게 다룰지를 용도에 맞게 분리해서 사용할 수 있다면, 서로 다른 특성의 서버 및 네트워크에서 이를 가져다가 속성값만 바꿔서 활용할 수 있을 것이다. 풀링을 한다던지 할 수도 있고.

그래서 이번에는 서비스라는 개념을 추가할 것인데... 그 전에 먼저 간단하게 기존에 만든 IocpCore 오브젝트나 Session 을 SharedPtr 처럼 사용할 수 있도록 바꿀 것이다.

// shared_ptr
using IocpCoreRef		= std::shared_ptr<class IocpCore>;
using IocpObjectRef		= std::shared_ptr<class IocpObject>;
using SessionRef		= std::shared_ptr<class Session>;
using ListenerRef		= std::shared_ptr<class Listener>;
using ServerServiceRef	= std::shared_ptr<class ServerService>;

그럼 기존에 사용했던 IocpCore 오브젝트들의 타입을 IocpCoreRef 로 바꾸고 참조 방식을 조금 바꿔주어야 하는데, 굳이 여기에 다 적지는 않겠다 😅

 

다시 핵심으로 넘어와서... 서비스의 구현을 보자.

Service.h

#pragma once
#include "NetAddress.h"
#include "IocpCore.h"
#include "Listener.h"
#include <functional>

enum class ServiceType : uint8
{
	Server,
	Client
};

/*-------------
	Service
--------------*/

using SessionFactory = function<SessionRef(void)>;

class Service : public enable_shared_from_this<Service>
{
public:
	Service(ServiceType type, NetAddress address, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount = 1);
	virtual ~Service();

	virtual bool		Start() abstract;
	bool				CanStart() { return _sessionFactory != nullptr; }

	virtual void		CloseService();
	void				SetSessionFactory(SessionFactory func) { _sessionFactory = func; }

	SessionRef			CreateSession();
	void				AddSession(SessionRef session);
	void				ReleaseSession(SessionRef session);
	int32				GetCurrentSessionCount() { return _sessionCount; }
	int32				GetMaxSessionCount() { return _maxSessionCount; }

public:
	ServiceType			GetServiceType() { return _type; }
	NetAddress			GetNetAddress() { return _netAddress; }
	IocpCoreRef&		GetIocpCore() { return _iocpCore; }

protected:
	USE_LOCK;
	ServiceType			_type;
	NetAddress			_netAddress = {};
	IocpCoreRef			_iocpCore;

	Set<SessionRef>		_sessions;
	int32				_sessionCount = 0;
	int32				_maxSessionCount = 0;
	SessionFactory		_sessionFactory;
};

/*-----------------
	ClientService
------------------*/

class ClientService : public Service
{
public:
	ClientService(NetAddress targetAddress, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount = 1);
	virtual ~ClientService() {}

	virtual bool	Start() override;
};


/*-----------------
	ServerService
------------------*/

class ServerService : public Service
{
public:
	ServerService(NetAddress targetAddress, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount = 1);
	virtual ~ServerService() {}

	virtual bool	Start() override;
	virtual void	CloseService() override;

private:
	ListenerRef		_listener = nullptr;
};

서비스는 서비스 타입(해당 서비스의 용도에 따라 다를 것이다. 여기서는 서버에서 쓰는 것인지, 클라에서 쓰는 것인지로 일단 나눈다), NetAddress, IocpCoreRef(아까 스마트 포인터를 차용한 IocpCore 오브젝트), SessionFactory, maxSessionCount 값을 넣어 준다.

용도에 따라 ClientService 와 ServerService 를 만들고, 이를 커스터마이징 해 줄 것이다.

 

Service.cpp

#include "pch.h"
#include "Service.h"
#include "Session.h"
#include "Listener.h"

/*-------------
	Service
--------------*/

Service::Service(ServiceType type, NetAddress address, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount)
	: _type(type), _netAddress(address), _iocpCore(core), _sessionFactory(factory), _maxSessionCount(maxSessionCount)
{

}

Service::~Service()
{
}

void Service::CloseService()
{
	// TODO
}

SessionRef Service::CreateSession()
{
	SessionRef session = _sessionFactory();

	if (_iocpCore->Register(session) == false)
		return nullptr;

	return session;
}

void Service::AddSession(SessionRef session)
{
	WRITE_LOCK;
	_sessionCount++;
	_sessions.insert(session);
}

void Service::ReleaseSession(SessionRef session)
{
	WRITE_LOCK;
	ASSERT_CRASH(_sessions.erase(session) != 0);
	_sessionCount--;
}

/*-----------------
	ClientService
------------------*/

ClientService::ClientService(NetAddress targetAddress, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount)
	: Service(ServiceType::Client, targetAddress, core, factory, maxSessionCount)
{
}

bool ClientService::Start()
{
	// TODO
	return true;
}

ServerService::ServerService(NetAddress address, IocpCoreRef core, SessionFactory factory, int32 maxSessionCount)
	: Service(ServiceType::Server, address, core, factory, maxSessionCount)
{
}

bool ServerService::Start()
{
	if (CanStart() == false)
		return false;

	_listener = MakeShared<Listener>();
	if (_listener == nullptr)
		return false;

	ServerServiceRef service = static_pointer_cast<ServerService>(shared_from_this());
	if (_listener->StartAccept(service) == false)
		return false;

	return true;
}

void ServerService::CloseService()
{
	// TODO

	Service::CloseService();
}

잘 보면, 세션을 만드는 역할을 과거에는 GameServer 클래스에서 임의로 생성했었는데, 이제는 각 Service 단에서 SessionFactory 를 통해 만들고 있는 것을 알 수 있다.

각 서비스에 따라, 서비스가 시작하고 종료할 때 어떤 동작을 해야 할지를 구현하면 될 것이다(TODO 부분). 😊

Comments