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#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.