소켓 응용 5/5 · 지연 대응 — 실시간 위치를 부드럽게 맞추기
순서를 보장하지 않는 WebRTC 위치 채널에서 늦게 도착한 과거 좌표를 폐기하고, 최신 위치 사이를 보간하는 방법을 배웁니다.

목차
먼저 전제를 분명히 하겠습니다. 이 글과 데모는 ordered: false로 설정한 WebRTC
DataChannel 위치 채널을 가정합니다. 단일 TCP·WebSocket 연결은 애플리케이션에 전송
순서대로 데이터를 전달하므로, 네트워크 지연만으로 뒤 메시지가 앞 메시지를 추월하지
않습니다.
순서를 보장하지 않는 위치 채널에서 서버가 x30, x50, x70을 보냈다면 x70이 먼저 도착하고 x50이 나중에 도착할 수 있습니다. 받은 좌표를 모두 도착 순서대로 화면에 적용하면 캐릭터는 x70까지 갔다가 갑자기 x50으로 뒤로 움직입니다.
실시간 상태에서는 늦게 도착한 과거 데이터가 없는 데이터보다 나쁠 수 있습니다.
지연 대응 데모에서 도착 순서대로 적용을 먼저 실행하면 화면 위치가 과거로 돌아갑니다. 순서 + 보간 적용에서는 2번 좌표를 버리고 최신 위치까지 부드럽게 이동합니다.
순서를 보장하지 않는 채널에서는 도착 순서가 달라집니다
unordered WebRTC DataChannel은 뒤 메시지가 앞 메시지를 기다리지 않습니다. 패킷마다 지연이 다르거나 오래된 메시지가 재전송되면 애플리케이션이 보는 순서가 발생 순서와 달라질 수 있습니다. 서로 다른 채널·연결·서버의 결과를 하나로 합칠 때도 동일한 문제가 생길 수 있습니다.
서버 발생: #1 x30 → #2 x50 → #3 x70
화면 도착: #1 x30 → #3 x70 → #2 x50
좌표만 보면 x50이 새 데이터인지 오래된 데이터인지 판단할 수 없습니다. 그래서 위치 메시지에 증가하는 sequence 또는 서버 tick을 붙입니다.
{
"type": "POSITION",
"entityId": "player-4",
"x": 70,
"seq": 3
}
마지막 적용 번호보다 작으면 버립니다
클라이언트는 개체별 lastAppliedSeq를 기억합니다.
if message.seq <= lastAppliedSeq:
drop(message)
else:
lastAppliedSeq = message.seq
apply(message)
데모에서 #3을 적용한 뒤 lastAppliedSeq는 3입니다. 뒤늦게 #2가 오면 2 ≤ 3이므로
폐기합니다. 이 정책은 자주 새 값이 오는 위치, 조준 방향, 임시 센서 수치에 잘 맞습니다.
모든 메시지를 버려도 되는 것은 아닙니다
데이터의 의미에 따라 채널 정책을 분리해야 합니다.
| 데이터 | 오래된 값의 가치 | 일반적인 처리 |
|---|---|---|
| 플레이어 위치 | 최신 값이 곧 덮어씀 | 오래된 좌표 폐기 |
| 조준 방향·커서 | 최신 상태가 중요 | 일부 유실 허용 |
| 채팅 메시지 | 각 메시지가 독립적으로 중요 | 신뢰·순서 보장 |
| 아이템 획득·결제 | 부작용과 기록이 중요 | ACK·재시도·중복 제거 |
하나의 소켓 연결에서도 메시지 종류별 애플리케이션 규칙을 다르게 둘 수 있습니다. WebRTC DataChannel을 사용하면 신뢰 또는 부분 신뢰, 순서 있음 또는 순서 없음을 채널별로 선택할 수도 있습니다.
반대로 WebSocket·Socket.IO 한 연결에서 순서가 뒤집히는 예제로 이 데모를 해석하면 안 됩니다. 그 환경에서도 sequence는 재연결 경계의 오래된 상태, 여러 생산자의 갱신, 중복 메시지와 애플리케이션 비동기 처리 순서를 판별하는 데 유용합니다.
최신 좌표를 바로 점프시키면 화면이 떨립니다
오래된 좌표를 버렸다고 화면이 곧바로 부드러워지는 것은 아닙니다. 새 좌표가 불규칙한 간격으로 도착하면 캐릭터가 한 칸씩 순간 이동해 보입니다.
표시 위치와 네트워크의 최신 위치를 분리합니다.
논리 위치: 서버에서 받은 최신 x70
표시 위치: 현재 x58 → x62 → x66 → x70
짧은 시간 동안 두 점 사이를 잇는 것을 보간(interpolation)이라고 합니다. 약간 늦게 보여 주는 대신 움직임을 일정하게 만듭니다.
작은 보간 버퍼가 지연 흔들림을 흡수합니다
클라이언트가 아주 짧은 시간의 위치 샘플을 모아 과거의 두 확정 지점 사이를 그리면, 도착 간격이 조금 흔들려도 화면 속도는 안정됩니다.
- 버퍼가 너무 작으면 네트워크 지터를 흡수하지 못합니다.
- 너무 크면 조작 반응이 늦게 보입니다.
- 경쟁 게임과 관제 지도는 허용 가능한 지연이 다릅니다.
- 실제 네트워크의 지연 분포를 측정해 크기를 조정합니다.
로컬 플레이어는 입력 예측으로 먼저 움직이고 서버 응답 뒤 보정할 수도 있습니다. 이 글의 보간은 주로 다른 플레이어나 원격 객체를 보여 주는 가장 기초적인 방법입니다.
순간 이동은 보간하지 않을 수 있습니다
모든 위치 차이를 천천히 잇는 것도 잘못입니다. 포털 이동, 리스폰, 큰 오차 보정은 중간 경로가 의미 없을 수 있습니다.
거리 < 작은 임계값 → 보간
거리 ≥ 큰 임계값 → 즉시 스냅
메시지 type=TELEPORT → 즉시 스냅
프로토콜에 이벤트 의미를 넣으면 화면이 숫자 차이만 보고 잘못 추측하지 않아도 됩니다.
시계 대신 sequence부터 시작하세요
서로 다른 기기의 절대 시각은 오차가 있을 수 있습니다. 단순히 “더 큰 timestamp가 최신”이라고 가정하면 기기 시계 차이 때문에 순서가 틀어질 수 있습니다.
한 서버가 상태를 확정하는 구조라면 먼저 서버가 증가시키는 tick이나 entity별 sequence를 사용하는 편이 쉽습니다. timestamp는 지연 측정과 보간 시점 계산에 추가로 사용할 수 있습니다.
데모에서 볼 순서
ordered: false위치 채널을 가정한 도착 순서대로 적용에서 #3 x70이 먼저 도착하는지 봅니다.- 뒤늦은 #2 x50을 적용해 화면이 뒤로 이동하는지 확인합니다.
- 순서 + 보간 적용으로 바꿉니다.
- #3을 받은 뒤 표시 위치가 x58, x66처럼 목표로 이동하는지 봅니다.
- #2가 도착해도
#2 < #3이라 폐기되고 최종 x70을 유지하는지 확인합니다.
응용 시리즈 전체 요약
이번 5부작은 소켓 API보다 그 위의 애플리케이션 규칙을 다뤘습니다.
- 전달 보장 — ACK와 메시지 ID로 중복 부작용을 막습니다.
- 흐름 제어 — 느린 수신자 앞에서 멈추고 이어받습니다.
- 상태 동기화 — 버전과 스냅숏으로 현재 정답을 맞춥니다.
- 서버 확장 — 브로커와 저장소로 서버 사이를 잇습니다.
- 지연 대응 — 과거 상태를 버리고 최신 상태를 부드럽게 표시합니다.
소켓은 통로를 만들어 줍니다. 그 통로 위에서 무엇을 재시도하고, 얼마나 쌓아 두고, 어떤 상태를 정답으로 삼고, 어디까지 공유하고, 무엇을 버릴지는 애플리케이션이 결정합니다.
자료: RFC 8831 — WebRTC Data Channels, RFC 6455 — The WebSocket Protocol

