6. RPC 메시지 ID ↔ 핸들러 매핑 테이블
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - RPC 메시지 ID ↔ 핸들러 매핑 테이블
// ----------------------------------------------------------------------------
// 시나리오:
// - 서버/클라는 패킷을 [ushort msgId][payload] 로 주고받는다.
// - msgId 별로 핸들러를 등록하고, 수신 시 테이블에서 찾아 디스패치한다.
// - 메시지 종류는 자동 생성된 enum(MsgId)으로 관리한다. 기능이 늘면
// 팀원들이 각자 enum 에 값을 추가하고, 부팅 시 핸들러를 Register 한다.
// - 최근 두 기능 브랜치가 머지되면서 enum 값이 재정렬됐고,
// 서버는 신버전으로, 일부 클라는 구버전으로 동작하는 구간이 있었다.
//
// 요구사항:
// - 알 수 없는/미등록 msgId 가 와도 서버가 죽거나 다른 핸들러로 오작동하면 안 된다.
// - msgId 는 한 번 배포되면 절대 의미가 바뀌면 안 된다(와이어 호환성).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지(특히 버전 공존/머지 시점),
// 수정안과 더 나은 메시지 ID 관리 설계를 제시하라.
// ============================================================================
using System;
using System.Collections.Generic;
// (A) 자동 생성 enum — 값을 명시하지 않고 컴파일러가 0,1,2... 부여
public enum MsgId : ushort
{
Login, // 0
Move, // 1
Chat, // 2
UseItem, // 3
// ... 브랜치 머지로 중간에 새 값이 끼어들 수 있다
}
public class RpcDispatcher
{
private readonly Dictionary<ushort, Action<byte[]>> _handlers = new();
public void Register(MsgId id, Action<byte[]> handler)
{
// (B) id 에 핸들러를 등록한다
_handlers[(ushort)id] = handler;
}
public void OnReceive(byte[] packet)
{
// (C) 앞 2바이트를 msgId 로 읽는다
ushort msgId = (ushort)(packet[0] | (packet[1] << 8));
// payload 는 그 뒤
byte[] payload = new byte[packet.Length - 2];
Array.Copy(packet, 2, payload, 0, payload.Length);
// (D) 핸들러 찾아 호출
var handler = _handlers[msgId];
handler(payload);
}
}
public class ServerBootstrap
{
public void Setup(RpcDispatcher d)
{
d.Register(MsgId.Login, OnLogin);
d.Register(MsgId.Move, OnMove);
d.Register(MsgId.Chat, OnChat);
d.Register(MsgId.UseItem, OnUseItem);
}
private void OnLogin(byte[] p) { /* ... */ }
private void OnMove(byte[] p) { /* ... */ }
private void OnChat(byte[] p) { /* ... */ }
private void OnUseItem(byte[] p) { /* ... */ }
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - RPC 메시지 ID ↔ 핸들러 매핑 테이블
// ----------------------------------------------------------------------------
// 시나리오:
// - 서버/클라는 패킷을 [uint16 msgId][payload] 로 주고받는다.
// - msgId 별로 핸들러를 등록하고, 수신 시 테이블에서 찾아 디스패치한다.
// - 메시지 종류는 enum(MsgId)으로 관리한다. 기능이 늘면
// 팀원들이 각자 enum 에 값을 추가하고, 부팅 시 핸들러를 Register 한다.
// - 최근 두 기능 브랜치가 머지되면서 enum 값이 재정렬됐고,
// 서버는 신버전으로, 일부 클라는 구버전으로 동작하는 구간이 있었다.
//
// 요구사항:
// - 알 수 없는/미등록 msgId 가 와도 서버가 죽거나 다른 핸들러로 오작동하면 안 된다.
// - msgId 는 한 번 배포되면 절대 의미가 바뀌면 안 된다(와이어 호환성).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜/어떻게 발생하는지(특히 버전 공존/머지 시점),
// 수정안과 더 나은 메시지 ID 관리 설계를 제시하라.
// ============================================================================
#include <cstdint>
#include <cstring>
#include <functional>
#include <unordered_map>
#include <vector>
// (A) enum — 값을 명시하지 않고 컴파일러가 0,1,2... 부여
enum class MsgId : uint16_t
{
Login, // 0
Move, // 1
Chat, // 2
UseItem, // 3
// ... 브랜치 머지로 중간에 새 값이 끼어들 수 있다
};
using Handler = std::function<void(const std::vector<uint8_t>&)>;
class RpcDispatcher {
public:
void Register(MsgId id, Handler handler)
{
// (B) id 에 핸들러를 등록한다
_handlers[static_cast<uint16_t>(id)] = std::move(handler);
}
void OnReceive(const uint8_t* packet, size_t len)
{
// (C) 앞 2바이트를 msgId 로 읽는다
uint16_t msgId = *reinterpret_cast<const uint16_t*>(packet);
// payload 는 그 뒤
std::vector<uint8_t> payload(packet + 2, packet + len);
// (D) 핸들러 찾아 호출
Handler& handler = _handlers[msgId];
handler(payload);
}
private:
std::unordered_map<uint16_t, Handler> _handlers;
};
class ServerBootstrap {
public:
void Setup(RpcDispatcher& d)
{
d.Register(MsgId::Login, [this](const std::vector<uint8_t>& p){ OnLogin(p); });
d.Register(MsgId::Move, [this](const std::vector<uint8_t>& p){ OnMove(p); });
d.Register(MsgId::Chat, [this](const std::vector<uint8_t>& p){ OnChat(p); });
d.Register(MsgId::UseItem, [this](const std::vector<uint8_t>& p){ OnUseItem(p); });
}
private:
void OnLogin(const std::vector<uint8_t>& p) { /* ... */ }
void OnMove(const std::vector<uint8_t>& p) { /* ... */ }
void OnChat(const std::vector<uint8_t>& p) { /* ... */ }
void OnUseItem(const std::vector<uint8_t>& p) { /* ... */ }
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.