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++
// ============================================================================
// [코드리뷰 문제] 신뢰성 UDP(RUDP)의 ACK 의미 불일치와 확인 버퍼 정리
// ----------------------------------------------------------------------------
// 시나리오:
// UDP 위에 순서보장·재전송을 얹은 신뢰성 채널이다.
// - 송신 측은 보낸 패킷을 seq 별 미확인 버퍼(map)에 보관하고, ACK 를 받으면 확인분을 제거한다.
// 타임아웃되면 남은 것을 재전송한다.
// - 수신 측은 순서대로 도착한 것을 상위로 전달하고, 어긋난 것은 재정렬 버퍼에 담았다가
// 빈칸이 채워지면 이어서 전달한 뒤 ACK 를 돌려준다.
//
// 요구사항:
// - 중간 seq 가 유실되면 재전송되어 결국 전달되어야 한다.
// - 송신 측이 아직 전달되지 않은 데이터를 확인된 것으로 오인해 버리면 안 된다.
//
// 과제:
// ReliableReceiver::onData 의 (A) 와 ReliableSender::onAck 의 (B) 를 검토하라.
// seq=5 유실 + 6,7 선도착 상황을 따라가며 결과와 수정 방향을 설명하라.
// ============================================================================
#include <map>
#include <vector>
#include <cstdint>
struct ILink {
virtual void transmit(uint32_t seq, const std::vector<uint8_t>& p) = 0;
virtual void sendAck(uint32_t ackSeq) = 0;
virtual ~ILink() = default;
};
struct IApp { virtual void deliver(const std::vector<uint8_t>& p) = 0; virtual ~IApp() = default; };
class ReliableSender {
public:
explicit ReliableSender(ILink& link) : link_(link) {}
void send(const std::vector<uint8_t>& payload) {
uint32_t seq = nextSeq_++;
unacked_[seq] = payload;
link_.transmit(seq, payload);
}
void onAck(uint32_t ackSeq) {
for (auto it = unacked_.begin(); it != unacked_.end(); ++it) {
if (it->first <= ackSeq)
unacked_.erase(it); // (B)
}
}
void onRetransmitTick() {
for (auto& kv : unacked_) link_.transmit(kv.first, kv.second);
}
private:
std::map<uint32_t, std::vector<uint8_t>> unacked_;
uint32_t nextSeq_ = 0;
ILink& link_;
};
class ReliableReceiver {
public:
ReliableReceiver(ILink& link, IApp& app) : link_(link), app_(app) {}
void onData(uint32_t seq, const std::vector<uint8_t>& payload) {
if (seq < expected_) { link_.sendAck(highestSeen_); return; }
reorder_[seq] = payload;
if (seq > highestSeen_) highestSeen_ = seq;
auto it = reorder_.find(expected_);
while (it != reorder_.end() && it->first == expected_) {
app_.deliver(it->second);
it = reorder_.erase(it);
expected_++;
}
link_.sendAck(highestSeen_); // (A)
}
private:
std::map<uint32_t, std::vector<uint8_t>> reorder_;
uint32_t expected_ = 0;
uint32_t highestSeen_ = 0;
ILink& link_;
IApp& app_;
}; 내 리뷰 · C#
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.
내 리뷰 · C++
내 답안 · 자동 저장
작성 후 위 해설 보기에서 모범 해설과 대조하세요.