지난번에 언리얼에서 패킷핸들러를 쓸 수 있도록 만들었다. 그럼이제 쓰면 그만이다.
그런데 내가 독자적으로 변경한 패킷핸들러의 구조때문에 살짝 수정해야할 부분이 생겼다. 해결해보자.
언리얼 측에서는 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
'프로젝트 > Project_Island' 카테고리의 다른 글
| 52. 회원가입과 로그인 (0) | 2026.05.08 |
|---|---|
| 51. 언리얼 클라에서 OpenSSL 사용하기 (0) | 2026.05.01 |
| 49. 언리얼 클라이언트 패킷 송신 (0) | 2026.04.30 |
| 48. 언리얼 클라이언트 패킷 수신 (0) | 2026.04.27 |
| 47. 클라와 서버 연결하기 (0) | 2026.04.26 |