지난번에 언리얼에서 패킷핸들러를 쓸 수 있도록 만들었다. 그럼이제 쓰면 그만이다.

그런데 내가 독자적으로 변경한 패킷핸들러의 구조때문에 살짝 수정해야할 부분이 생겼다. 해결해보자.


언리얼 측에서는 std::shared_ptr 대신 TSharedPtr을 쓰는 것 처럼 std::function 대신 TFunction 을 써야한다. 근데 지금 내 서버패킷 핸들러에서는 std::function을 쓰고있으니까, 이 부분을 적절하게 수정해놓자.

 

패킷핸들러 탬플릿파일에 저번에 했던 것 처럼 매크로 조건문으로 언리얼이면 TFunction을, 서버면 std::function을 쓰도록 수정한다. 여기저기서 쓰이므로 using을 써서 이름은 똑같이 쓰되 언리얼이냐 서버냐에 따라 다르게 적용되도록 만들었다.

 

패킷핸들러 탬플릿 파일

#pragma once

#if UE_BUILD_DEBUG + UE_BUILD_DEVELOPMENT + UE_BUILD_TEST + UE_BUILD_SHIPPING >= 1
#include "Client.h"
#else
#include "Types.h"
#include "Session.h"
#include "SendBuffer.h"
#include <memory>
#include <functional>
#endif
#include "Protocol.pb.h"

#if UE_BUILD_DEBUG + UE_BUILD_DEVELOPMENT + UE_BUILD_TEST + UE_BUILD_SHIPPING >= 1
using DeferredFunc = TFunction<void()>;
using PacketHandlerFunc = TFunction<bool(DeferredFunc&, PacketSessionRef&, BYTE*, int32)>;
#else
using DeferredFunc = std::function<void()>;
using PacketHandlerFunc = std::function<bool(DeferredFunc&, PacketSessionRef&, BYTE*, int32)>;
#endif

extern PacketHandlerFunc GPacketHandler[UINT16_MAX];

enum : uint16
{
{%- for pkt in parser.total_pkt %}
	PKT_{{pkt.name}} = {{pkt.id}},
{%- endfor %}
};

bool	Handle_INVALID(DeferredFunc& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len);
{%- for pkt in parser.recv_pkt %}
void	Handle_{{pkt.name}}(const PacketSessionRef& session, const Protocol::{{pkt.name}}& pkt);
{%- endfor %}

class {{ output }}
{
public:
	static void	Init()
	{
		for (int i = 0; i < UINT16_MAX; ++i)
			GPacketHandler[i] = Handle_INVALID;
{%- for pkt in parser.recv_pkt %}
		GPacketHandler[PKT_{{pkt.name}}] = [](DeferredFunc& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len) {
			return GetCallback<Protocol::{{pkt.name}}>(outFunc, Handle_{{pkt.name}}, session, buffer, len);
		};
{%- endfor %}
	}

	static bool	PacketHandler(DeferredFunc& outFunc, PacketSessionRef session, BYTE* buffer, int32 len)
	{
		PacketHeader*	header = reinterpret_cast<PacketHeader*>(buffer);
		return GPacketHandler[header->id](outFunc, session, buffer, len);
	}

{%- for pkt in parser.send_pkt %}
	static SendBufferRef MakeSendBuffer(Protocol::{{pkt.name}}& pkt) { return MakeSendBuffer(pkt, PKT_{{pkt.name}}); }
{%- endfor %}

private:
	template<typename PacketType, typename ProcessFunc>
	static bool	GetCallback(DeferredFunc& outFunc, ProcessFunc func, PacketSessionRef session, BYTE* buffer, int32 len)
	{
		PacketType	pkt;
		if (false == pkt.ParseFromArray(buffer + sizeof(PacketHeader), len - sizeof(PacketHeader)))
			return false;
		outFunc = [func, session, pkt](){ func(session, pkt); };
		return true;
	}

	template<typename T>
	static SendBufferRef	MakeSendBuffer(T& pkt, uint16 pktId)
	{
		int	headerSize = sizeof(PacketHeader);
		int	pktSize = pkt.ByteSizeLong();
		#if UE_BUILD_DEBUG + UE_BUILD_DEVELOPMENT + UE_BUILD_TEST + UE_BUILD_SHIPPING >= 1
				SendBufferRef sendBuffer = MakeShared<SendBuffer>(headerSize + pktSize);
		#else
				SendBufferRef sendBuffer = make_shared<SendBuffer>(headerSize + pktSize);
		#endif
		PacketHeader	header;
		header.id = pktId;
		header.size = headerSize + pktSize;

		sendBuffer->AppendBuffer(reinterpret_cast<BYTE*>(&header), headerSize);
		if (pktSize > 0)
		{
			pkt.SerializeToArray(sendBuffer->WritePos(), pktSize);
			sendBuffer->OnWrite(pktSize);
		}
		return sendBuffer;
	}
};

 

cpp파일도 수정해줘야한다.

 

게임서버측 패킷핸들러.cpp

//function<bool(function<void()>&, PacketSessionRef&, BYTE*, int32)> GPacketHandler[UINT16_MAX];
//
//bool	Handle_INVALID(std::function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len)
//{
//	return false;
//} 기존의 이 부분을 아래와같이 수정

PacketHandlerFunc GPacketHandler[UINT16_MAX];

bool	Handle_INVALID(DeferredFunc& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len)
{
	return false;
}

 

언리얼 클라측 패킷핸들러.cpp

PacketHandlerFunc GPacketHandler[UINT16_MAX];

bool	Handle_INVALID(std::function<void()>& outFunc, PacketSessionRef& session, BYTE* buffer, int32 len)
{
	return true;
}

 

그리고 패킷핸들러 클래스의 Init을 호출해야

PacketHandlerFunc GPacketHandler[UINT16_MAX];

가 채워져서 정상적으로 작동되니까 어디선가는 호출해줘야한다.

PacketSession::PacketSession(class FSocket* Socket) : Socket(Socket)
{
    ServerPacketHandler::Init();
}

일단은 패킷세션의 생성자에서 호출하도록 했다. (더 적절한 위치를 찾는게 좋아보인다. 깃허브에 이슈로 등록해놓자.)


 

게임서버쪽에서 쓰던 것 처럼 ServerPacketHandler::PacketHandler 를 호출해서 패킷에 따라 실행시킬 적절한 함수를 func 로 받고, 호출한다.

void PacketSession::HandleRecvPackets()
{
	while (true)
	{
		TArray<uint8> Packet;
		if (RecvPacketQueue.Dequeue(OUT Packet) == false)
			break;
		DeferredFunc Func;
		if (ServerPacketHandler::PacketHandler(OUT Func, AsShared(), Packet.GetData(), Packet.Num()))
			Func();
	}
}

S_CHAT이 들어오면 패킷핸들러가 Handle_S_CHAT 함수를 Func에 넣어줄 것이다(정확히는 Handle_S_CHAT를 호출을 담은 람다함수). 그러니까  패킷핸들러가 성공적으로 Func를 담아준다면, 호출해서버로 Response를 보내도록 해보자.

 

저번에는 그냥 C_CHAT을 아무 메시지나 새로 채워서 보냈는데, 이번에는 서버가 보낸 메시지를 그대로 넣어 보내보자.

void	Handle_S_CHAT(const PacketSessionRef& session, const Protocol::S_CHAT& pkt)
{
	Protocol::C_CHAT Response;
	
	Response.set_msg(pkt.msg());
	session->SendPacket(ServerPacketHandler::MakeSendBuffer(Response));
}

 

패킷핸들러를 통해 Handle_S_CHAT이 func에 담기고, 이를 호출해 서버로부터 받은 메시지를 그대로 C_CHAT 패킷에 담아서 보낸다. 실행해보면 서버가 보낸 HelloWrold! 를 그대로 담아서 서버로 보내고, 서버가 정상적으로 받아서 출력한다.

잘 된다!


지금까지의 git 버전

언리얼 클라

 

Feat: 패킷핸들러 사용 · Dodontak/Project_Island_Client@d452d4c

이제 패킷핸들러를 사용해 TFunction 객체에 실행할 적절한 함수를 받음.

github.com

게임서버

 

Feat: 패킷핸들러 탬플릿 수정 · Dodontak/Project_Island_GameServer@7adbf42

패킷핸들러에서 쓰는 std::function을 언리얼 클라이언트에서는 TFunction을 사용하도록 수정함

github.com

 

 

+ Recent posts