5. 멀티존 게임서버의 프로토콜 버전 게이트웨이 (종합)
난이도 최상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 멀티존 게임서버의 프로토콜 버전 게이트웨이 (종합)
// ----------------------------------------------------------------------------
// 시나리오(현실적):
// - 아키텍처: [클라] ── TCP ──> [게이트웨이(엣지)] ── 내부 ──> [존 서버들]
// 게이트웨이는 클라 패킷을 받아 적절한 존 서버로 라우팅한다.
// - 배포 방식: 무중단 롤링 배포. 게이트웨이 풀과 존 서버 풀을 각각
// 인스턴스 단위로 교체한다. 따라서 "신버전 게이트웨이 ↔ 구버전 존",
// "구버전 게이트웨이 ↔ 신버전 존"이 배포 윈도우 동안 동시에 존재한다.
// - 클라도 점진 업데이트라 v1.4 / v1.5 가 공존한다.
// - 이번 릴리스에서 패킷 헤더에 "압축 플래그(flags)"를 추가했고(v1.5),
// C_Move 패킷의 좌표를 int(cm) 에서 float(m) 로 바꾸기로 했다.
//
// 핵심 정책:
// - 게이트웨이는 클라 ProtocolVersion 으로 직렬화 방식을 선택해야 한다.
// - 협상된 버전은 세션에 저장되어 모든 패킷 처리에 일관 적용되어야 한다.
// - 어느 한 컴포넌트만 신버전이어도(부분 롤아웃) 시스템이 깨지면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고,
// "버전 불일치가 왜/어떻게 일어나는지", "패킷 버전을 무엇으로/어디서 판단해야 하는지"를
// 중심으로 설명하고, 수정안과 더 나은 설계(버전 협상/스키마 진화/롤링 배포 안전성)를 제시하라.
// ============================================================================
using System;
using System.Collections.Generic;
public static class Protocol
{
// 이 빌드의 프로토콜 버전. 릴리스마다 코드에 박아 올린다.
public const ushort Version = 0x0105; // v1.5
// 헤더: [ushort size][ushort id] ... v1.4 까지
// 헤더: [ushort size][ushort id][byte flags] ... v1.5 부터 (flags 추가)
public const int HeaderSizeV14 = 4;
public const int HeaderSizeV15 = 5;
}
public class GatewaySession
{
public ushort ClientVersion; // 핸드셰이크에서 채움
// (A) 협상 결과를 따로 저장하지 않는다 — 매번 ClientVersion 으로 분기
}
public class C_Move
{
public float X, Y; // v1.5: m 단위 float
// (B) 직렬화는 항상 현재 빌드 포맷(float)으로 한다
public static C_Move Parse(ReadOnlySpan<byte> payload)
{
var m = new C_Move();
m.X = BitConverter.ToSingle(payload.Slice(0, 4));
m.Y = BitConverter.ToSingle(payload.Slice(4, 4));
return m;
}
}
public class Gateway
{
private static readonly HashSet<ushort> Supported = new() { 0x0104, 0x0105 };
public void OnClientHandshake(GatewaySession s, ushort clientVer)
{
// (C) 지원 목록에 있으면 통과
if (!Supported.Contains(clientVer))
{
// (D) 지원 안 하면 그냥 끊는다
return;
}
s.ClientVersion = clientVer;
}
// 클라가 보낸 패킷 처리
public void OnClientPacket(GatewaySession s, ReadOnlySpan<byte> raw)
{
// (E) 헤더 크기를 "이 게이트웨이 빌드 버전" 기준으로 고른다
int headerSize = (Protocol.Version >= 0x0105) ? Protocol.HeaderSizeV15
: Protocol.HeaderSizeV14;
ushort size = (ushort)(raw[0] | (raw[1] << 8));
ushort id = (ushort)(raw[2] | (raw[3] << 8));
ReadOnlySpan<byte> payload = raw.Slice(headerSize, size - headerSize);
if (id == 2001) // C_Move
{
var move = C_Move.Parse(payload); // (B) 항상 float 로 파싱
RouteToZone(s, id, payload); // (F) 페이로드를 그대로 존으로 전달
}
}
private void RouteToZone(GatewaySession s, ushort id, ReadOnlySpan<byte> payload)
{
// 내부 존 서버로 그대로 포워딩 (내부 프로토콜 버전 협상은 없음)
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 멀티존 게임서버의 프로토콜 버전 게이트웨이 (종합)
// ----------------------------------------------------------------------------
// 시나리오(현실적):
// - 아키텍처: [클라] ── TCP ──> [게이트웨이(엣지)] ── 내부 ──> [존 서버들]
// 게이트웨이는 클라 패킷을 받아 적절한 존 서버로 라우팅한다.
// - 배포 방식: 무중단 롤링 배포. 게이트웨이 풀과 존 서버 풀을 각각
// 인스턴스 단위로 교체한다. 따라서 "신버전 게이트웨이 ↔ 구버전 존",
// "구버전 게이트웨이 ↔ 신버전 존"이 배포 윈도우 동안 동시에 존재한다.
// - 클라도 점진 업데이트라 v1.4 / v1.5 가 공존한다.
// - 이번 릴리스에서 패킷 헤더에 "압축 플래그(flags)"를 추가했고(v1.5),
// C_Move 패킷의 좌표를 int(cm) 에서 float(m) 로 바꾸기로 했다.
//
// 핵심 정책:
// - 게이트웨이는 클라 ProtocolVersion 으로 직렬화 방식을 선택해야 한다.
// - 협상된 버전은 세션에 저장되어 모든 패킷 처리에 일관 적용되어야 한다.
// - 어느 한 컴포넌트만 신버전이어도(부분 롤아웃) 시스템이 깨지면 안 된다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고,
// "버전 불일치가 왜/어떻게 일어나는지", "패킷 버전을 무엇으로/어디서 판단해야 하는지"를
// 중심으로 설명하고, 수정안과 더 나은 설계(버전 협상/스키마 진화/롤링 배포 안전성)를 제시하라.
// ============================================================================
#include <cstdint>
#include <cstring>
#include <unordered_set>
namespace Protocol {
// 이 빌드의 프로토콜 버전. 릴리스마다 코드에 박아 올린다.
constexpr uint16_t Version = 0x0105; // v1.5
// 헤더: [uint16 size][uint16 id] ... v1.4 까지
// 헤더: [uint16 size][uint16 id][uint8 flags] ... v1.5 부터 (flags 추가)
constexpr int HeaderSizeV14 = 4;
constexpr int HeaderSizeV15 = 5;
}
struct GatewaySession {
uint16_t ClientVersion = 0; // 핸드셰이크에서 채움
// (A) 협상 결과를 따로 저장하지 않는다 — 매번 ClientVersion 으로 분기
};
struct C_Move {
float X, Y; // v1.5: m 단위 float
// (B) 직렬화는 항상 현재 빌드 포맷(float)으로 한다
static C_Move Parse(const uint8_t* payload)
{
C_Move m;
m.X = *reinterpret_cast<const float*>(payload + 0);
m.Y = *reinterpret_cast<const float*>(payload + 4);
return m;
}
};
class Gateway {
public:
void OnClientHandshake(GatewaySession& s, uint16_t clientVer)
{
// (C) 지원 목록에 있으면 통과
if (Supported.find(clientVer) == Supported.end())
{
// (D) 지원 안 하면 그냥 끊는다
return;
}
s.ClientVersion = clientVer;
}
// 클라가 보낸 패킷 처리
void OnClientPacket(GatewaySession& s, const uint8_t* raw, size_t rawLen)
{
// (E) 헤더 크기를 "이 게이트웨이 빌드 버전" 기준으로 고른다
int headerSize = (Protocol::Version >= 0x0105) ? Protocol::HeaderSizeV15
: Protocol::HeaderSizeV14;
uint16_t size = *reinterpret_cast<const uint16_t*>(raw + 0);
uint16_t id = *reinterpret_cast<const uint16_t*>(raw + 2);
const uint8_t* payload = raw + headerSize;
size_t payloadLen = size - headerSize;
if (id == 2001) // C_Move
{
C_Move move = C_Move::Parse(payload); // (B) 항상 float 로 파싱
RouteToZone(s, id, payload, payloadLen); // (F) 페이로드를 그대로 존으로 전달
}
}
private:
std::unordered_set<uint16_t> Supported { 0x0104, 0x0105 };
void RouteToZone(GatewaySession& s, uint16_t id, const uint8_t* payload, size_t len)
{
// 내부 존 서버로 그대로 포워딩 (내부 프로토콜 버전 협상은 없음)
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.