43. 신뢰성 UDP의 ACK 의미 불일치로 인한 유실 패킷의 조기 폐기

난이도 중 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] 신뢰성 UDP(RUDP)의 ACK 의미 불일치 (손실 구간 처리)
// ----------------------------------------------------------------------------
// 시나리오:
//   UDP 위에 순서보장·재전송을 얹은 신뢰성 채널을 직접 구현했다.
//   - 송신 측은 보낸 패킷을 seq 별로 미확인(unacked) 버퍼에 보관하고,
//     ACK 를 받으면 확인된 것을 버퍼에서 제거한다. 타임아웃되면 남은 것을 재전송한다.
//   - 수신 측은 순서대로 도착한 패킷을 상위로 전달하고, 순서가 어긋난 패킷은 재정렬
//     버퍼에 잠시 담았다가 빈칸(gap)이 채워지면 이어서 전달한다. 그리고 ACK 를 돌려준다.
//
// 요구사항:
//   - 중간 패킷이 유실되면 그 패킷이 재전송되어 결국 전달되어야 한다.
//   - 송신 측이 아직 전달되지 않은 데이터를 확인된 것으로 오인해 버리면 안 된다.
//
// 과제:
//   ReliableReceiver.OnData 의 (A) 와 ReliableSender.OnAck 의 (B) 를 검토하라.
//   seq=5 가 유실되고 6,7 이 먼저 도착하는 상황을 따라가며 결과와 수정 방향을 설명하라.
// ============================================================================

using System.Collections.Generic;

public sealed class ReliableSender
{
    private readonly SortedDictionary<uint, byte[]> _unacked = new();
    private uint _nextSeq;
    private readonly ILink _link;
    public ReliableSender(ILink link) { _link = link; }

    public void Send(byte[] payload)
    {
        uint seq = _nextSeq++;
        _unacked[seq] = payload;
        _link.Transmit(seq, payload);
    }

    public void OnAck(uint ackSeq)
    {
        var remove = new List<uint>();
        foreach (var k in _unacked.Keys)
            if (k <= ackSeq) remove.Add(k);         // (B) ackSeq 이하를 모두 확인 처리
        foreach (var k in remove) _unacked.Remove(k);
    }

    public void OnRetransmitTick()
    {
        foreach (var kv in _unacked) _link.Transmit(kv.Key, kv.Value);
    }
}

public sealed class ReliableReceiver
{
    private readonly SortedDictionary<uint, byte[]> _reorder = new();
    private uint _expected;
    private uint _highestSeen;
    private readonly ILink _link;
    private readonly IApp _app;
    public ReliableReceiver(ILink link, IApp app) { _link = link; _app = app; }

    public void OnData(uint seq, byte[] payload)
    {
        if (seq < _expected) { _link.SendAck(_highestSeen); return; }

        _reorder[seq] = payload;
        if (seq > _highestSeen) _highestSeen = seq;

        while (_reorder.TryGetValue(_expected, out var p))
        {
            _app.Deliver(p);
            _reorder.Remove(_expected);
            _expected++;
        }
        _link.SendAck(_highestSeen);                 // (A) 가장 높이 본 seq 로 ACK
    }
}

public interface ILink { void Transmit(uint seq, byte[] payload); void SendAck(uint ackSeq); }
public interface IApp { void Deliver(byte[] payload); }
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.