26. 시퀀스 번호 랩어라운드와 시리얼 산술 (최신/과거 판정 붕괴)
난이도 상 해설 보기 →
결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커
(A)(B) 는 주목 위치 힌트다.
결함 코드 · C#
// 시나리오: 신뢰성 UDP(RUDP) 수신측의 시퀀스 번호 처리.
// 각 패킷에 16비트 시퀀스 번호(0~65535, 다 쓰면 0으로 순환)가 붙는다.
// 수신측은 (1) 받은 패킷이 지금까지 본 것보다 "더 최신"인지 판정해 상태를 갱신하고,
// (2) 최근 윈도우로 중복/재전송(리플레이)을 걸러낸다.
//
// 요구사항:
// - 시퀀스가 65535 다음 0 으로 되돌아가도(wrap) "최신/과거" 판정이 정확해야 한다.
// - 지연·재정렬 도착과 중복을 올바르게 구분해야 한다.
// - 세션은 장시간 유지되므로 시퀀스는 한 바퀴 이상 돈다.
//
// 과제: (A)(B) 를 중심으로, 시퀀스가 경계(65535→0)를 넘을 때 어떤 오작동이
// 나는지(구체적 값으로)와 수정안을 제시하라.
using System.Collections.Generic;
public sealed class SequenceReceiver
{
private const int WindowSize = 256;
private ushort _lastSeq; // 지금까지 본 가장 최신 시퀀스
private bool _hasAny;
private readonly HashSet<ushort> _recent = new(); // 최근 본 시퀀스(중복 차단)
// 더 최신이면 처리(true), 과거 윈도우 밖이거나 중복이면 버림(false).
public bool Accept(ushort seq)
{
if (!_hasAny) { _hasAny = true; _lastSeq = seq; Remember(seq); return true; }
if (seq > _lastSeq) // (A) "이게 더 최신인가?"
{
_lastSeq = seq;
if (!_recent.Add(seq)) return false; // 이미 본 것이면 중복
Trim();
return true;
}
// 과거 쪽: 최근 윈도우 안의 재정렬 도착만 받아들인다.
if (_lastSeq - seq <= WindowSize) // (B) "윈도우 안의 과거인가?"
{
if (!_recent.Add(seq)) return false;
Trim();
return true;
}
return false; // 너무 오래된 것으로 보고 폐기
}
private void Remember(ushort seq) { _recent.Add(seq); Trim(); }
private void Trim()
{
// 윈도우 크기 유지(상세 생략): 오래된 시퀀스를 _recent 에서 제거.
if (_recent.Count > WindowSize * 2) _recent.Clear();
}
} 결함 코드 · C++
// 시나리오: 신뢰성 UDP(RUDP) 수신측의 시퀀스 번호 처리.
// 각 패킷에 16비트 시퀀스 번호(0~65535, 다 쓰면 0으로 순환)가 붙는다.
// 수신측은 (1) 받은 패킷이 지금까지 본 것보다 "더 최신"인지 판정해 상태를 갱신하고,
// (2) 최근 윈도우로 중복/재전송(리플레이)을 걸러낸다.
//
// 요구사항:
// - 시퀀스가 65535 다음 0 으로 되돌아가도(wrap) "최신/과거" 판정이 정확해야 한다.
// - 지연·재정렬 도착과 중복을 올바르게 구분해야 한다.
// - 세션은 장시간 유지되므로 시퀀스는 한 바퀴 이상 돈다.
//
// 과제: (A)(B) 를 중심으로, 시퀀스가 경계(65535→0)를 넘을 때 어떤 오작동이
// 나는지(구체적 값으로)와 수정안을 제시하라.
#include <cstdint>
#include <unordered_set>
#include <cstdio>
class SequenceReceiver {
public:
// 더 최신이면 처리(true), 과거 윈도우 밖이거나 중복이면 버림(false).
bool accept(std::uint16_t seq) {
if (!has_any_) { has_any_ = true; last_seq_ = seq; remember(seq); return true; }
if (seq > last_seq_) { // (A) "이게 더 최신인가?"
last_seq_ = seq;
if (!recent_.insert(seq).second) return false; // 이미 본 것이면 중복
trim();
return true;
}
// 과거 쪽: 최근 윈도우 안의 재정렬 도착만 받아들인다.
if (last_seq_ - seq <= kWindowSize) { // (B) "윈도우 안의 과거인가?"
if (!recent_.insert(seq).second) return false;
trim();
return true;
}
return false; // 너무 오래된 것으로 보고 폐기
}
private:
void remember(std::uint16_t seq) { recent_.insert(seq); trim(); }
void trim() { if (recent_.size() > kWindowSize * 2) recent_.clear(); }
static constexpr int kWindowSize = 256;
std::uint16_t last_seq_ = 0;
bool has_any_ = false;
std::unordered_set<std::uint16_t> recent_;
};
int main() {
SequenceReceiver rx;
rx.accept(65534);
rx.accept(65535);
std::printf("after wrap, accept(0) = %d\n", rx.accept(0) ? 1 : 0); // 기대: 최신으로 수용
std::printf("after wrap, accept(1) = %d\n", rx.accept(1) ? 1 : 0);
return 0;
} 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.