37. 재접속 재바인딩 시 이전 연결 미정리로 인한 유령 연결 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 접속 이탈-재접속(reconnect) 처리
//
// 모바일 클라이언트는 네트워크가 자주 끊긴다. 우리는 세션을 즉시 파기하지 않고
// 짧은 유예 동안 유지하다가, 클라이언트가 reconnectToken 으로 다시 붙으면 기존
// 세션에 새 연결을 "재바인딩"해 진행 중이던 상태(전투/파티/버프)를 이어준다.
//
// 요구사항:
// - 하나의 논리 세션에는 항상 최대 1개의 활성 물리 연결만 매여 있어야 한다.
// - 재바인딩되면 이전 연결로는 더 이상 이 세션에 데이터가 흐르면 안 된다.
// - 각 연결은 독립된 수신 루프를 돌리며, 받은 패킷을 바인딩된 세션에 전달한다.
// - 재접속 요청은 이전 연결의 수신 루프가 아직 살아있는 동안에도 들어올 수 있다.
//
// 아래 매니저의 (A)(B) 지점을 검토하라.
using System.Collections.Generic;
public sealed class Connection
{
public readonly int SocketId;
public Session BoundSession;
public bool Closed;
public Connection(int id) { SocketId = id; }
public void Send(byte[] data) { /* 소켓으로 송신 */ }
public void Close() { Closed = true; /* 소켓 닫기 */ }
// 각 연결의 수신 스레드 루프가 패킷마다 이걸 호출한다.
public void OnPacket(byte[] data)
{
BoundSession?.Handle(data); // (B)
}
}
public sealed class Session
{
public readonly string PlayerId;
public Connection Conn;
public Session(string playerId) { PlayerId = playerId; }
public void Handle(byte[] data) { /* 게임 로직 적용 */ }
}
public sealed class SessionManager
{
private readonly Dictionary<string, Session> _byToken = new();
// 재접속: 새 연결 newConn 이 token 으로 기존 세션에 다시 붙는다.
public bool Reconnect(string token, Connection newConn)
{
if (!_byToken.TryGetValue(token, out var session))
return false;
// 기존 세션에 새 연결을 매단다.
session.Conn = newConn; // (A)
newConn.BoundSession = session;
return true;
}
} 결함 코드 · C++
// 시나리오: 접속 이탈-재접속(reconnect) 처리 (C++)
//
// 세션을 즉시 파기하지 않고 유예 동안 유지하다가, 클라이언트가 reconnectToken 으로
// 다시 붙으면 기존 세션에 새 연결을 "재바인딩"해 진행 중이던 상태를 이어준다.
//
// 요구사항:
// - 하나의 논리 세션에는 항상 최대 1개의 활성 물리 연결만 매여 있어야 한다.
// - 재바인딩되면 이전 연결로는 더 이상 이 세션에 데이터가 흐르면 안 되고,
// 이전 연결이 소유하던 소켓/스레드 자원은 정리되어야 한다.
// - 각 연결은 독립된 수신 스레드에서 받은 패킷을 바인딩된 세션에 전달한다.
// - 재접속은 이전 연결의 수신 루프가 아직 살아있는 동안에도 들어올 수 있다.
//
// 아래 매니저의 (A)(B) 지점을 검토하라.
#include <cstdint>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
class Session;
class Connection {
public:
explicit Connection(int id) : socketId_(id) {}
Session* bound = nullptr;
void close() { closed_ = true; /* 소켓 닫기 */ }
// 이 연결의 수신 스레드 루프가 패킷마다 호출한다.
void onPacket(const std::vector<uint8_t>& data); // (B) 아래 정의
int socketId() const { return socketId_; }
private:
int socketId_;
bool closed_ = false;
};
class Session {
public:
explicit Session(std::string pid) : playerId_(std::move(pid)) {}
Connection* conn = nullptr;
void handle(const std::vector<uint8_t>&) { /* 게임 로직 */ }
private:
std::string playerId_;
};
void Connection::onPacket(const std::vector<uint8_t>& data) {
if (bound) bound->handle(data);
}
class SessionManager {
public:
// 재접속: 새 연결 newConn 이 token 으로 기존 세션에 다시 붙는다.
bool reconnect(const std::string& token, Connection* newConn) {
auto it = byToken_.find(token);
if (it == byToken_.end()) return false;
Session* session = it->second;
session->conn = newConn; // (A)
newConn->bound = session;
return true;
}
// 소켓 오류/정리로 호출: 소유 중인 연결을 파괴한다.
void closeSocket(int socketId) {
conns_.erase(socketId); // unique_ptr 파괴 → Connection 소멸
}
private:
std::unordered_map<std::string, Session*> byToken_;
std::unordered_map<int, std::unique_ptr<Connection>> conns_;
};
int main() { SessionManager m; (void)m; return 0; } 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.