34. 공유 사전 압축 프로토콜의 DictAdd 유실·순서 역전 (C#)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 공유 사전(dictionary) 기반 문자열 압축 프로토콜
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-클라):
// - 채팅 필터 문구, 아이템 접두 수식어, 반복 시스템 메시지처럼 자주 등장하는
// 문자열을 매번 그대로 보내지 않고, 서버와 클라가 "공유 사전"을 유지하며
// 문자열을 인덱스(ushort) 하나로 치환해 주고받는다.
// - 새 문자열이 처음 등장하면 서버가 사전에 추가하고, 클라에게
// [DictAdd: index, string] 메시지로 통지한다. 이후 그 문자열이 다시
// 등장하면 [DictRef: index] 로 인덱스만 보낸다.
// - DictAdd 와 DictRef 는 서로 다른 우선순위 큐(예: 안정성 큐/일반 큐)를 타고
// 나갈 수 있어 네트워크 상에서 도착 순서가 뒤바뀌거나, DictAdd 패킷이
// 간헐적으로 유실될 수 있다(비신뢰 구간을 경유하는 배치 전송 가정).
// - 서버는 사전에 추가한 즉시 그 인덱스를 참조하는 DictRef 를 만들어 낼 수 있다.
//
// 요구사항:
// - 클라이언트가 특정 인덱스를 담은 DictRef 를 받았을 때, 그 인덱스가
// 가리키는 문자열은 서버가 그 인덱스에 등록한 문자열과 항상 같아야 한다
// (같은 인덱스가 서로 다른 문자열로 오해석되면 안 된다).
// - 사전 상태가 어긋난 것을 감지했다면, 엉뚱한 문자열을 화면에 노출하는 대신
// 안전하게 처리해야 한다.
//
// 과제: (A)(B) 지점을 검토하라. DictAdd 유실이나 순서 역전이 있을 때 클라이언트가
// 어떻게 잘못된 문자열을 표시하게 되는지 원인을 밝히고 수정하라.
using System.Collections.Generic;
public sealed class ClientDictionary
{
// index -> string
private readonly Dictionary<int, string> _entries = new Dictionary<int, string>();
// 서버 -> 클라: 새 사전 항목 통지
public void OnDictAdd(int index, string value)
{
// (A) 유실/순서 역전 여부를 고려하지 않고 항상 최신 통지를 그대로 반영
_entries[index] = value;
}
// 서버 -> 클라: 인덱스로 문자열 참조
public string Resolve(int index)
{
// (B) 등록되지 않은 인덱스에 대한 처리만 있고, "다른 값으로 덮였을 가능성"은
// 고려하지 않는다.
if (_entries.TryGetValue(index, out var value))
return value;
return "[UNKNOWN]";
}
}
public sealed class ServerDictionary
{
private readonly Dictionary<string, int> _stringToIndex = new Dictionary<string, int>();
private readonly List<string> _indexToString = new List<string>();
// 문자열을 사전에 등록(없으면 추가)하고, 클라에 보낼 (index, isNew) 를 반환한다.
public (int index, bool isNew) Intern(string value)
{
if (_stringToIndex.TryGetValue(value, out var existing))
return (existing, false);
int newIndex = _indexToString.Count;
_indexToString.Add(value);
_stringToIndex[value] = newIndex;
return (newIndex, true);
}
} 결함 코드 · C++
// ============================================================================
// [코드리뷰 문제] C++ - 공유 사전(dictionary) 기반 문자열 압축 프로토콜
// ----------------------------------------------------------------------------
// 시나리오 (프로토콜 / 서버-서버):
// - 존 서버들과 채팅 릴레이 서버 사이에서, 반복되는 시스템 문구/닉네임 태그를
// 매번 그대로 보내지 않고 "공유 사전"을 유지해 인덱스(uint16_t)로 치환한다.
// - 새 문자열이 등장하면 원본 서버가 사전에 추가하고 [DictAdd: index, string]
// 을 릴레이 서버로 보낸다. 이후 그 문자열은 [DictRef: index] 로만 보낸다.
// - DictAdd 는 별도 관리 채널로, DictRef 는 일반 트래픽 채널로 나가며 두 채널의
// 상대적 도착 순서는 보장되지 않는다. 관리 채널이 혼잡하면 DictAdd 가
// 간헐적으로 지연되거나 유실될 수 있다.
// - 사전은 인덱스가 항상 0부터 연속으로 채워진다는 전제로 배열에 저장한다.
//
// 요구사항:
// - 특정 인덱스를 담은 DictRef 를 받았을 때, 그 인덱스가 가리키는 문자열은
// 원본 서버가 그 인덱스에 등록한 문자열과 항상 같아야 한다.
// - 아직 등록되지 않았거나 알 수 없는 인덱스를 참조하는 DictRef 가 와도
// 정의되지 않은 동작(범위 밖 접근 등) 없이 안전하게 처리해야 한다.
//
// 과제: (A)(B) 지점을 검토하라. DictAdd 유실/순서 역전이 있을 때 어떤 문제가
// 발생하는지, 그리고 그 처리 방식이 메모리 안전성 측면에서 왜 위험한지
// 밝히고 수정하라.
#include <string>
#include <vector>
#include <unordered_map>
#include <cstdint>
class RelayDictionary {
// index 를 그대로 배열 첨자로 사용 — "인덱스는 0부터 연속" 가정
std::vector<std::string> entries_;
public:
// 원본 서버 -> 릴레이: 새 사전 항목 통지
void OnDictAdd(uint16_t index, const std::string& value)
{
// (A) index 가 현재 entries_ 크기보다 훨씬 클 수 있다는 걸 고려하지 않음
// (DictAdd 들이 순서대로 온다는 가정 하에 항상 뒤에 push 하거나
// 정확히 그 위치에 있다고 가정)
if (index == entries_.size()) {
entries_.push_back(value);
} else if (index < entries_.size()) {
entries_[index] = value;
}
// index > entries_.size() 인 경우(중간 DictAdd 유실로 번호가 건너뜀)는
// 아무 처리도 하지 않고 조용히 무시한다.
}
// 릴레이가 참조 문자열이 필요할 때 호출 (호출자는 index 가 유효하다고 믿는다)
const std::string& Resolve(uint16_t index)
{
// (B) 유실로 인해 entries_ 크기가 index 보다 작을 수 있는데 그대로 첨자 접근
return entries_[index];
}
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.