2. C# 룸 기반 게임의 좀비 세션 / 유령 플레이어
난이도 중 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 룸 기반 게임에서 좀비 세션 / 유령 플레이어
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 접속 후 Room 에 입장(Enter)해 함께 플레이한다.
// - 연결이 끊기면(소켓 에러/정상 종료) OnDisconnect 가 호출된다.
// - SessionManager 는 sessionId -> Session 맵을 들고 전체 세션을 관리한다.
// - Room 은 자신에 속한 세션들을 들고 있고, 매 틱 브로드캐스트한다.
// - IO 스레드 풀(N개)에서 OnDisconnect 가, 게임 로직 스레드에서 Room.Tick 이
// 동시에 돈다.
//
// 요구사항:
// - 연결이 끊긴 플레이어는 즉시 Room/매니저에서 사라져야 한다(유령 금지).
// - 끊긴 세션의 자원/구독은 회수되어야 한다(누수 금지).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System;
using System.Collections.Generic;
public sealed class Session
{
public long Id;
public bool Connected = true;
public Room Room; // (A) 자신이 속한 룸을 강한 참조로 보유
public void Send(byte[] data) { /* 비동기 송신 ... */ }
}
public sealed class Room
{
private readonly object _lock = new object();
private readonly List<Session> _members = new List<Session>(); // (A) 멤버 세션 강한 참조
// 룸이 매 틱 만든 스냅샷을 멤버에게 전달하는 이벤트.
// 세션은 입장 시 여기에 핸들러를 등록한다. (B)
public event Action<byte[]> OnBroadcast;
public void Enter(Session s)
{
lock (_lock)
{
_members.Add(s);
s.Room = this;
// 세션의 Send 를 브로드캐스트 이벤트에 구독시킨다.
OnBroadcast += s.Send; // (B) 이벤트 핸들러 등록
}
}
// (C) 게임 로직 스레드: 매 틱 호출
public void Tick(byte[] snapshot)
{
lock (_lock)
{
// 끊긴 세션에도 그대로 보낸다
OnBroadcast?.Invoke(snapshot); // 구독된 모든 Send 호출(락 보유 중)
}
}
}
public sealed class SessionManager
{
private readonly object _lock = new object();
private readonly Dictionary<long, Session> _sessions = new Dictionary<long, Session>();
public void Add(Session s)
{
lock (_lock) { _sessions[s.Id] = s; }
}
// (D) IO 스레드 풀에서 연결 종료 시 호출
public void OnDisconnect(long id)
{
lock (_lock)
{
if (!_sessions.TryGetValue(id, out var s)) return;
s.Connected = false;
// 매니저 맵에서만 지운다. Room 에서는 빼지 않는다. (E)
_sessions.Remove(id);
}
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 룸 기반 게임에서 좀비 세션 / 유령 플레이어
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어는 접속 후 Room 에 입장(Enter)해 함께 플레이한다.
// - 연결이 끊기면(소켓 에러/정상 종료) OnDisconnect 가 호출된다.
// - SessionManager 는 sessionId -> Session 맵을 들고 전체 세션을 관리한다.
// - Room 은 자신에 속한 세션들을 들고 있고, 매 틱 브로드캐스트한다.
// - IO 스레드 풀(N개)에서 OnDisconnect 가, 게임 로직 스레드에서 Room::Tick 이
// 동시에 돈다.
//
// 요구사항:
// - 연결이 끊긴 플레이어는 즉시 Room/매니저에서 사라져야 한다(유령 금지).
// - 끊긴 세션의 메모리는 회수되어야 한다(누수 금지).
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 왜 문제인지 설명하고,
// 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <memory>
#include <vector>
#include <unordered_map>
#include <mutex>
#include <cstdint>
class Room;
class Session : public std::enable_shared_from_this<Session> {
public:
uint64_t id;
bool connected = true;
std::shared_ptr<Room> room; // (A) 자신이 속한 룸을 강한 참조로 보유
void Send(const std::vector<char>& data) { /* 비동기 송신 ... */ }
};
class Room : public std::enable_shared_from_this<Room> {
std::mutex mtx_;
std::vector<std::shared_ptr<Session>> members_; // (A) 멤버 세션 강한 참조
public:
void Enter(const std::shared_ptr<Session>& s) {
std::lock_guard<std::mutex> lk(mtx_);
members_.push_back(s);
s->room = shared_from_this();
}
// (B) 게임 로직 스레드: 매 틱 호출
void Tick(const std::vector<char>& snapshot) {
std::lock_guard<std::mutex> lk(mtx_);
for (auto& s : members_) {
s->Send(snapshot); // 끊긴 세션에도 그대로 보낸다
}
}
};
class SessionManager {
std::mutex mtx_;
std::unordered_map<uint64_t, std::shared_ptr<Session>> sessions_;
public:
void Add(const std::shared_ptr<Session>& s) {
std::lock_guard<std::mutex> lk(mtx_);
sessions_[s->id] = s;
}
// (C) IO 스레드 풀에서 연결 종료 시 호출
void OnDisconnect(uint64_t id) {
std::lock_guard<std::mutex> lk(mtx_);
auto it = sessions_.find(id);
if (it == sessions_.end()) return;
it->second->connected = false;
// 매니저 맵에서만 지운다. Room 에서는 빼지 않는다. (D)
sessions_.erase(it);
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.