21. 재접속 시 기존 세션 재바인딩과 옛 연결의 늦은 콜백/패킷
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 재접속 시 기존 세션 재바인딩과 옛 연결의 늦은 콜백/패킷
// ----------------------------------------------------------------------------
// 시나리오 (세션/접속 · 서버-클라):
// - 플레이어가 끊겼다가 재접속하면, 서버는 인게임 상태(HP, 위치 등)를 보존하기 위해
// "기존 세션 객체"를 찾아 새 연결로 재바인딩(reattach)한다.
// - 그런데 옛 연결의 이벤트는 늦게 도착할 수 있다: 옛 소켓의 종료 콜백(OnDisconnect)이나
// 아직 처리 중이던 패킷(OnPacket)이 재접속 "이후에" 도착할 수 있다(서로 다른 IO 스레드).
//
// 요구사항:
// - 재접속으로 세션이 새 연결에 바인딩된 뒤에는, "옛 연결"에서 온 종료/패킷이 새 연결의
// 세션을 종료시키거나 상태를 바꾸면 안 된다(현재 바인딩된 연결의 이벤트만 유효).
// - 어떤 시점에도 한 계정의 살아있는 세션은 정확히 하나여야 하고, 정상 접속 중인
// 플레이어가 옛 연결 정리 때문에 강제 종료되면 안 된다.
//
// 과제:
// 이 세션 매니저의 잠재적 문제를 모두 찾고, 재접속 직후 옛 연결의 OnDisconnect/OnPacket 이
// 도착하면 어떤 일이 벌어지는지 설명하고, 수정안과 더 나은 설계를 제시하라.
// (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
using System.Collections.Generic;
public class Session
{
public long AccountId;
public object Connection; // 현재 바인딩된 연결
public int Hp = 100;
public bool Alive = true;
}
public class SessionManager
{
private readonly Dictionary<long, Session> _byAccount = new();
public Session OnConnect(long accountId, object newConn)
{
if (_byAccount.TryGetValue(accountId, out var s))
{
s.Connection = newConn; // (A)
s.Alive = true;
return s;
}
s = new Session { AccountId = accountId, Connection = newConn };
_byAccount[accountId] = s;
return s;
}
public void OnDisconnect(long accountId, object conn)
{
if (_byAccount.TryGetValue(accountId, out var s))
{
s.Alive = false; // (B)
_byAccount.Remove(accountId); // (C)
}
}
public void OnPacket(long accountId, object conn, int damage)
{
if (_byAccount.TryGetValue(accountId, out var s))
{
s.Hp -= damage; // (D)
}
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 재접속 시 기존 세션 재바인딩과 옛 연결의 늦은 콜백/패킷
// ----------------------------------------------------------------------------
// 시나리오 (세션/접속 · 서버-클라):
// - 플레이어가 끊겼다가 재접속하면, 서버는 인게임 상태 보존을 위해 "기존 세션 객체"를
// 찾아 새 연결로 재바인딩(reattach)한다.
// - 옛 연결의 이벤트는 늦게 도착할 수 있다: 옛 소켓의 종료 콜백(OnDisconnect)이나
// 처리 중이던 패킷(OnPacket)이 재접속 "이후에" 도착할 수 있다(서로 다른 IO 스레드).
// - OnConnect 가 돌려준 Session* 를 새 연결 쪽이 들고 다니며 사용한다.
//
// 요구사항:
// - 재바인딩 뒤에는 옛 연결의 종료/패킷이 새 연결의 세션을 파괴/변경하면 안 된다.
// - 한 계정의 살아있는 세션은 정확히 하나. 정상 접속 중 플레이어가 옛 연결 정리로
// 강제 종료/메모리 오류를 겪으면 안 된다.
//
// 과제:
// 이 세션 매니저의 잠재적 문제를 모두 찾고(세션 객체의 소유권/수명 포함), 재접속 직후
// 옛 연결의 OnDisconnect/OnPacket 이 도착하면 무엇이 벌어지는지 설명하고, 수정안과 더
// 나은 설계를 제시하라. (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================
#include <unordered_map>
struct Connection { int fd; };
struct Session
{
long accountId;
Connection* conn; // 현재 바인딩된 연결
int hp = 100;
bool alive = true;
};
class SessionManager
{
std::unordered_map<long, Session*> byAccount_; // 세션 소유(생짜 포인터)
public:
Session* OnConnect(long accountId, Connection* newConn)
{
auto it = byAccount_.find(accountId);
if (it != byAccount_.end())
{
it->second->conn = newConn; // (A)
it->second->alive = true;
return it->second;
}
Session* s = new Session{ accountId, newConn };
byAccount_[accountId] = s;
return s;
}
void OnDisconnect(long accountId, Connection* conn)
{
auto it = byAccount_.find(accountId);
if (it != byAccount_.end())
{
delete it->second; // (B)
byAccount_.erase(it); // (C)
}
}
void OnPacket(long accountId, Connection* conn, int damage)
{
auto it = byAccount_.find(accountId);
if (it != byAccount_.end())
{
it->second->hp -= damage; // (D)
}
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.