← 문제로

4. 신뢰성 UDP(RUDP) 설계: 순서보장·재전송·ACK

난이도 상
내 답안
모범답안

모범답안 — 신뢰성 UDP(RUDP) 설계

난이도: 상

핵심 답변

  • UDP 위에 신뢰성을 직접 구현하는 이유: TCP는 신뢰성·순서를 "전부 아니면 전무"로 강제해 HOL blocking과 재전송 지연을 낳는다. 게임은 패킷마다 신뢰성 요구가 다르다. 직접 구현하면 "이 패킷만 신뢰, 저건 최신만"처럼 세분화할 수 있고 재전송 정책도 게임에 맞게 튜닝한다.
  • 신뢰성의 3요소: 시퀀스 번호(누락·순서 판별) + ACK(수신 확인) + 재전송(미확인 시 다시 보냄).
  • 핵심 설계는 전송 모드(채널) 분리: Reliable+Ordered, Reliable+Unordered, Unreliable(최신만). 위치는 Unreliable, "사망/획득" 이벤트는 Reliable.
  • 신뢰 채널의 재전송 대기가 비신뢰 데이터를 막지 않게 독립 시퀀스 공간/큐로 분리 → TCP의 HOL blocking 회피.

깊이 있는 설명

왜 TCP가 아니라 RUDP인가

TCP의 한계:

  • HOL blocking: 한 세그먼트가 유실되면 그 뒤에 도착한 모든 데이터가 순서 때문에 앱에 전달되지 못하고 대기. 게임에선 "최신 위치는 도착했는데 옛날 패킷 재전송 기다리느라 못 씀."
  • 재전송·순서 강제가 불필요한 데이터에도 적용됨. 위치 패킷은 유실되면 그냥 다음 걸 쓰면 되는데 TCP는 굳이 재전송.
  • 혼잡 제어가 게임 트래픽 패턴과 안 맞을 수 있고, 모드별 튜닝 불가. RUDP는 이 모든 정책을 애플리케이션이 패킷 종류별로 결정한다.

신뢰성 3요소 & ACK 방식

  • 시퀀스 번호: 보내는 패킷마다 증가. 수신측은 이걸로 유실(빠진 번호)·중복·순서뒤바뀜을 판별.
  • 재전송: 보낸 패킷을 "미확인" 상태로 보관. ACK가 RTO 내에 안 오면 재전송.
  • ACK 방식:
    • 개별 ACK: 받은 시퀀스마다 ACK. 단순하지만 ACK 트래픽 많음.
    • 누적 ACK(cumulative, TCP 방식): "N번까지 다 받았다." 간결하지만 중간 유실 시 그 뒤 정보를 못 줌 → 불필요한 재전송 가능.
    • 선택적 ACK / 비트필드(SACK 스타일): "ack=N, 그리고 그 이후 N+1·N+3은 받았고 N+2는 못 받았다"를 비트마스크로. 게임 RUDP(예: 일부 라이브러리)는 ack number + 32비트 ackfield로 최근 32개 수신 여부를 한 번에 알려 재전송을 정밀하게 한다. 효율적.

RTO 산정

  • 고정 RTO의 문제: 너무 짧으면 정상 패킷을 유실로 오판해 불필요 재전송(네트워크·부하 악화), 너무 길면 진짜 유실에 늦게 반응(체감 렉). 네트워크마다 RTT가 다르므로 고정은 부적절.
  • 동적 산정(Jacobson/Karels, TCP 방식): 측정 RTT로 평활 평균 SRTT와 변동 RTTVAR를 갱신, RTO = SRTT + 4*RTTVAR. 변동이 큰 망에서는 RTO를 넉넉히. 재전송한 패킷의 RTT는 측정에서 제외(Karn's algorithm: 어느 전송에 대한 ACK인지 모호하므로). 재전송 시 RTO를 지수적으로 늘리는 백오프도 적용.

응용/실무 연결

전송 모드(채널) 분리 — 핵심 설계

모드 보장 용도
Unreliable (최신만/sequenced) 유실 허용, 오래된 건 버림 위치·회전·이동 동기화
Reliable + Unordered 반드시 도착, 순서 무관 독립 이벤트(아이템 획득 등 서로 순서 무의미한 것)
Reliable + Ordered 반드시 도착 + 순서 채팅, 상태 전이가 순서에 의존하는 이벤트
  • "캐릭터가 죽었다", "아이템을 획득했다" → Reliable. 유실되면 게임 상태가 어긋남.
  • "현재 위치 (x,y,z)" → Unreliable sequenced. 오래된 위치 패킷이 늦게 오면 그냥 버린다(시퀀스가 더 낮으면 무시). 재전송 안 함.

HOL blocking 회피(질문 5)

  • 신뢰 채널과 비신뢰 채널의 시퀀스 공간을 분리한다. 위치 패킷은 자기 시퀀스로 "최신이면 적용, 옛날이면 버림"만 하고 재전송 대기 큐에 들어가지 않는다.
  • 신뢰 채널이 유실 패킷을 재전송하는 동안에도 비신뢰 위치 패킷은 즉시 적용된다(서로의 순서에 묶이지 않음). 이것이 TCP 단일 스트림으로는 불가능한, RUDP/QUIC가 주는 이점 = HOL blocking 제거.
  • 실무에서는 RakNet, ENet, Lidgren, 자체 RUDP, 또는 QUIC(스트림별 독립 신뢰)·Steam Datagram 같은 솔루션을 쓴다.

추가 고려: 흐름·혼잡 제어를 너무 무시하면 네트워크/클라이언트를 폭주시킨다. 게임 RUDP도 최소한의 send rate 제어, MTU 고려(파편화 회피, 보통 1200바이트 이하 페이로드), keep-alive/타임아웃에 의한 연결 종료 감지가 필요하다.

흔한 오답·함정

  • "RUDP면 TCP보다 항상 빠르다" → 신뢰 채널에서 유실되면 RUDP도 재전송하므로 그 데이터는 느려진다. 이점은 비신뢰 데이터가 신뢰 데이터에 발목 잡히지 않는다는 것.
  • "모든 패킷에 신뢰성 부여" → 그러면 TCP를 재발명한 셈. 모드 분리가 핵심.
  • "ACK는 패킷마다 하나씩만" → 비트필드/누적 ACK로 묶으면 훨씬 효율적.
  • "고정 RTO면 충분" → 망 상태 변동을 못 따라간다. RTT 기반 동적 + 백오프 필요.
  • "재전송하면 그 패킷의 RTT로 SRTT 갱신" → Karn 규칙 위반(재전송 ACK는 모호). 측정 제외.

꼬리질문 대비

  • Q. QUIC은 RUDP와 어떤 관계? A. UDP 위에 신뢰성·다중 스트림·암호화를 얹은 표준 프로토콜. 스트림별 독립 전송으로 HOL blocking을 구조적으로 해결. 게임에서도 채택 증가.
  • Q. 시퀀스 번호가 오버플로(wrap-around)하면? A. 모듈러 비교(부호 있는 차이 비교)로 "더 최신인지"를 판별해 wrap을 안전하게 처리한다.
  • Q. MTU/파편화는 왜 신경 쓰나? A. IP 단편화가 일어나면 조각 하나만 유실돼도 전체 데이터그램 폐기 → 유실률 급증. 그래서 페이로드를 MTU(보통 ~1400, 안전하게 1200) 이하로 유지.
  • Q. 연결이 끊겼는지 어떻게 아나(UDP는 연결이 없는데)? A. 주기적 keep-alive/heartbeat + 타임아웃. 일정 시간 ACK/패킷이 없으면 끊긴 것으로 처리.