12. 귓속말: 대상 로그아웃/채널이동 직후 전송 · C#
난이도 하 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 귓속말: 대상이 막 로그아웃/채널이동한 직후의 전송
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 귓속말을 보내면 클라가 C_Whisper(targetName, text) 를 보낸다.
// - 서버는 대상 이름으로 온라인 세션을 찾아 그 세션으로 메시지를 전달한다.
// - 대상은 "막 로그아웃"했거나 "다른 채널/인스턴스로 이동"한 직후일 수 있다.
// 로그아웃/이동은 별도 스레드(OnLogout)에서 처리되어 레지스트리에서 제거된다.
// - 귓속말 전송(Whisper)과 로그아웃(OnLogout)은 서로 다른 스레드에서 거의 동시에
// 일어날 수 있다.
//
// 요구사항:
// - 대상이 오프라인/이동 중이면 보낸 사람에게 "상대가 접속 중이 아닙니다"를 알린다.
// - 이미 끊긴 세션으로 전송하면 안 된다(오송신 금지).
// - 이름→세션 조회는 동시 변경(로그아웃/로그인)에 안전해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 어떤 경합에서 예외/오송신/유실이 나는지
// 설명하고, 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================
using System;
using System.Collections.Generic;
public class Session
{
public long SessionId;
public string Name;
public bool Alive = true;
public void Send(string msg) { /* 소켓 송신(생략) */ }
}
public class ChatService
{
private readonly Dictionary<string, Session> _byName = new Dictionary<string, Session>();
// 귓속말 처리. 로직 워커 스레드에서 호출된다.
public bool Whisper(Session from, string targetName, string text)
{
// (A) 이름으로 대상 세션 조회
Session target = _byName[targetName];
// (B) 생사/유효성 확인 없이 바로 송신
target.Send($"{from.Name} (귓속말): {text}");
return true;
}
// 로그인 시 등록
public void OnLogin(Session s)
{
_byName[s.Name] = s;
}
// 로그아웃/채널이동 시 다른 스레드에서 호출
public void OnLogout(Session s)
{
// (C) 레지스트리에서 제거
_byName.Remove(s.Name);
s.Alive = false;
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 귓속말: 대상이 막 로그아웃/채널이동한 직후의 전송
// ----------------------------------------------------------------------------
// 시나리오:
// - 플레이어가 귓속말을 보내면 클라가 C_Whisper(targetName, text) 를 보낸다.
// - 서버는 대상 이름으로 온라인 세션을 찾아 그 세션으로 메시지를 전달한다.
// - 대상은 "막 로그아웃"했거나 "다른 채널/인스턴스로 이동"한 직후일 수 있다.
// 로그아웃/이동은 별도 스레드(OnLogout)에서 처리되어, 세션 객체가 레지스트리에서
// 제거되고 메모리도 해제된다.
// - 귓속말 전송(Whisper)과 로그아웃(OnLogout)은 서로 다른 스레드에서 거의 동시에
// 일어날 수 있다.
//
// 요구사항:
// - 대상이 오프라인/이동 중이면 보낸 사람에게 "상대가 접속 중이 아닙니다"를 알린다.
// - 이미 끊긴/해제된 세션으로 전송하면 안 된다(크래시·오송신 금지).
// - 이름→세션 조회는 동시 변경(로그아웃/로그인)에 안전해야 한다.
//
// 과제:
// 이 코드의 잠재적 문제를 모두 찾고, 어떤 경합에서 크래시/오송신/유실이 나는지
// 설명하고, 수정안과 더 나은 설계를 제시하라. (먼저 직접 리뷰 후 answer.md 와 대조)
// ============================================================================
#include <string>
#include <unordered_map>
struct Session {
long long sessionId;
std::string name;
bool alive = true;
void Send(const std::string& /*msg*/) { /* 소켓 송신(생략) */ }
};
class ChatService {
public:
// 귓속말 처리. 로직 워커 스레드에서 호출된다.
bool Whisper(Session* from, const std::string& targetName, const std::string& text)
{
// (A) 이름으로 대상 세션 조회
Session* target = byName_[targetName];
// (B) 생사/유효성 확인 없이 바로 송신
target->Send(from->name + " (귓속말): " + text);
return true;
}
// 로그인 시 등록
void OnLogin(Session* s)
{
byName_[s->name] = s;
}
// 로그아웃/채널이동 시 다른 스레드에서 호출
void OnLogout(Session* s)
{
// (C) 레지스트리에서 제거하고 세션 객체를 해제
byName_.erase(s->name);
s->alive = false;
delete s;
}
private:
std::unordered_map<std::string, Session*> byName_; // 이름 -> 온라인 세션
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.