UDP 는 아무것도 안 해 준다. 그래서 골라 살 수 있다
앞 편의 마지막 문제는 이것이었습니다. 앞의 하나가 늦으면 뒤에 도착한 것이 전부 묶입니다.
그런데 묶인 것들을 들여다보면, 대부분은 기다릴 필요가 없는 것들이었습니다. 캐릭터 위치는 최신 하나만 있으면 되니까요.
TCP 는 그걸 구별해 주지 않습니다. 구별할 방법이 없습니다. TCP 가 보는 것은 바이트 줄 하나뿐이고, 그 줄의 어디가 위치이고 어디가 아이템인지는 모릅니다. 아는 것은 우리뿐입니다.
그래서 방향을 바꿉니다. 아무것도 안 해 주는 것으로 시작해서, 아는 만큼만 직접 얹습니다.
하는 일이 거의 없다
UDP 헤더는 여덟 바이트입니다. 들어 있는 것이 이게 전부입니다.
| 칸 | 크기 | 하는 일 |
|---|---|---|
| 출발지 포트 | 2바이트 | 어느 프로그램이 보냈는가 |
| 도착지 포트 | 2바이트 | 어느 프로그램에게 가는가 |
| 길이 | 2바이트 | 이 덩어리가 몇 바이트인가 |
| 체크섬 | 2바이트 | 오는 길에 값이 깨졌는가 |
TCP 헤더는 최소 20바이트입니다. 순서 번호, 답한 번호, 창 크기, 각종 깃발이 들어갑니다. 그 12바이트 차이가 곧 TCP 가 해 주는 일의 목록입니다. UDP 에는 그게 없습니다.
없다는 것은 이런 뜻입니다.
- 순서를 안 맞춰 줍니다. 온 순서 그대로 올라옵니다
- 빠진 것을 안 알려 줍니다. 안 온 것과 원래 없던 것을 구별하지 못합니다
- 다시 안 보냅니다. 사라지면 사라진 것입니다
- 얼마나 보낼지 안 정해 줍니다. 보내는 대로 나갑니다
대신 묶어 두지 않습니다. 도착한 것은 그 자리에서 바로 올라옵니다.
연결이 없다는 말의 뜻
TCP 에서 “연결” 은 양쪽이 상태를 들고 있다는 뜻이었습니다. UDP 에는 그 상태가 없습니다. 열고 닫는 절차도 없습니다. 그냥 주소를 적어서 보냅니다.
가벼운 대신 따라오는 것이 있습니다.
중간 장비가 연결을 볼 수 없습니다. 집이나 회사의 공유기는 안쪽 여러 기기를 바깥의 주소 하나로 바꿔 내보냅니다. NAT 라고 합니다. 답이 돌아오면 그걸 안쪽 누구에게 줄지 판단해야 하는데, 그 판단의 근거가 “아까 이 기기가 저기로 보냈다” 는 기록입니다.
TCP 는 언제 연결이 시작되고 끝나는지가 헤더에 드러나서, 장비가 그 기록을 언제 만들고 지울지 압니다. UDP 에는 그게 없습니다. 그래서 장비는 마지막으로 보낸 지 얼마나 됐는지로 짐작합니다. 한동안 조용하면 기록을 지웁니다. 지워진 뒤에는 바깥에서 오는 답이 갈 곳을 잃습니다.
그래서 UDP 를 쓰는 쪽은 할 말이 없어도 주기적으로 무언가를 보냅니다. 아래에서 이야기할 하트비트가 이 일도 겸합니다.
신뢰성은 전부 아니면 전무가 아니다
여기가 이 편의 중심입니다.
게임이 보내는 값들을 늘어놓고 각각 무엇이 필요한지 물어봅니다.
| 보내는 것 | 빠지면 | 순서가 바뀌면 | 그래서 필요한 것 |
|---|---|---|---|
| 캐릭터 위치 | 다음 것이 덮어씀 | 옛것을 버리면 그만 | 아무것도. 최신만 쓰면 됨 |
| 아이템 획득 | 아이템이 사라짐 | 상관없음 | 반드시 도착. 순서는 무관 |
| 채팅 | 말이 빠짐 | 대화가 꼬임 | 도착 + 순서 |
| 발사 이펙트 | 티가 덜 남 | 상관없음 | 아무것도 |
TCP 는 이 표의 모든 줄에 마지막 칸의 가장 강한 보장을 매깁니다. 위치에도 “반드시 도착하고 순서까지 맞춰서” 를 적용합니다. 그게 앞 편에서 본 줄 서기의 정체입니다.
이 세 가지를 한 연결 안에서 섞어 쓰는 것이 UDP 위에 뭔가를 얹는 이유의 전부입니다. 그 얹은 것을 흔히 RUDP 라고 부릅니다. 표준 이름이 아니라, 같은 일을 하는 구현들을 묶어 부르는 말입니다.
그래서 무엇을 얹는가
번호를 붙인다
UDP 덩어리 하나하나에 번호를 붙입니다. TCP 와 달리 바이트가 아니라 덩어리 단위로 셉니다.
번호가 생기면 세 가지가 가능해집니다. 빠진 것을 알아채고, 순서가 바뀐 것을 알아채고, 두 번 온 것을 버립니다. 앞 편에서 본 것들이 전부 여기서부터 시작합니다.
받았다는 답을 몰아서 보낸다
TCP 는 “여기까지 빠짐없이” 하나만 말했습니다. 그래서 중간이 비면 답이 멈췄습니다.
RUDP 는 대개 다르게 합니다. 가장 큰 번호 하나와, 그 앞 몇 개가 왔는지를 비트로 같이 보냅니다.
이 그림의 핵심은 다시 보낼지 말지를 우리가 정한다는 것입니다. TCP 에서는 그 판단이 없었습니다. 빠진 것은 무조건 다시 보내고, 그동안 뒤엣것은 기다렸습니다.
한 연결 안을 여러 칸으로 나눈다
보낼 것마다 필요한 보장이 다르니, 연결 하나를 여러 칸으로 나눕니다. 흔히 채널이라고 부릅니다.
- 0번 칸은 비신뢰, 최신만. 위치와 회전이 여기로
- 1번 칸은 신뢰, 무순서. 아이템 획득과 피해 판정이 여기로
- 2번 칸은 신뢰, 순서 보장. 채팅과 방 입장이 여기로
칸이 나뉘어 있으면 2번 칸이 막혀도 0번 칸은 계속 흐릅니다. 채팅 한 줄이 늦는다고 캐릭터가 멈추지 않습니다.
여기서 조심할 것이 하나 있습니다. 신뢰 + 순서 칸에 이것저것 다 넣으면 앞 편의 줄 서기가 그대로 돌아옵니다. UDP 위에 만들었다고 면제되는 것이 아닙니다. 그 보장을 산 칸 안에서는 똑같이 막힙니다.
조용해도 계속 부른다
연결이라는 개념이 없으니 직접 만들어야 합니다.
일정 간격으로 작은 덩어리를 보냅니다. 하트비트라고 합니다. 그리고 상대에게서 한동안 아무것도 안 오면 끊어진 것으로 칩니다.
이걸로 세 가지가 한꺼번에 해결됩니다.
- 상대가 살아 있는지 압니다
- 앞에서 이야기한 NAT 의 기록이 안 지워집니다
- 오가는 시간을 계속 잴 수 있습니다. 이 값이 5편과 6편에서 쓰입니다
간격을 어떻게 잡을지가 판단입니다. 촘촘하면 끊긴 것을 빨리 알지만 그만큼 계속 나갑니다. 느슨하면 조용하지만, 실제로 끊긴 뒤에도 한참 모릅니다.
한 덩어리 안에 들어가게 자른다
선 위를 지나는 덩어리에는 크기 한계가 있습니다. MTU 라고 합니다. 보통 1500바이트쯤입니다.
그보다 큰 것을 보내면 중간에서 여러 조각으로 잘립니다. 문제는 조각 중 하나만 잃어도 전부 못 쓴다는 것입니다. 받는 쪽은 조각을 다 모아야 원래 덩어리를 복원할 수 있는데, 조각 단위로 다시 달라고 할 방법이 없습니다.
그래서 보낼 것을 MTU 안에 들어가게 잘라서 보냅니다. 반대로 작은 것들은 모아서 한 덩어리로 보냅니다. 앞 편의 Nagle 과 목적이 같지만, 언제 모으고 언제 그냥 보낼지를 우리가 정한다는 점이 다릅니다.
빠뜨리기 쉬운 것
여기까지가 흔히 RUDP 에 얹는 것들입니다. 하나가 더 남았습니다.
혼잡 제어입니다.
앞 편에서 TCP 가 손실을 보고 보내는 양을 줄인다고 했습니다. 그 일은 공짜로 되고 있었습니다. 우리가 아무것도 안 해도 알아서 돌았습니다.
UDP 위에 직접 만들면 그게 없습니다. 보내는 대로 그냥 나갑니다.
길이 이미 막혀 있어도 계속 보냅니다. 큐가 더 차고, 지연이 더 늘고, 손실이 더 납니다. 손실이 나니 신뢰 칸에서 다시 보냅니다. 그게 큐를 또 채웁니다.
1편에서 큐잉이 지연의 유일한 조절 가능한 조각이라고 했는데, 여기서는 그걸 잘못된 방향으로 돌리고 있는 셈입니다.
이게 RUDP 를 직접 짜는 것이 위험하다고 하는 진짜 이유입니다. 번호를 붙이고 다시 보내는 것은 어렵지 않습니다. 하루면 돌아가는 것을 만듭니다. 어려운 것은 언제 덜 보낼지 정하는 쪽이고, 그건 잘 돌아가는 동안 티가 안 납니다. 회선이 나쁜 사용자에게서만 드러납니다.
그래서 대개는 이미 만들어진 것을 씁니다. 직접 짠다면 최소한 보내는 양에 상한을 두고, 답이 늦어지면 줄이는 것은 넣어야 합니다.
정리
- UDP 헤더는 여덟 바이트. 하는 일은 포트 구분과 체크섬뿐입니다
- 연결이 없으니 중간 장비도 연결을 못 봅니다. NAT 기록이 지워지는 문제가 여기서 나옵니다
- 신뢰성은 전부 아니면 전무가 아닙니다. 위치는 최신만, 아이템은 반드시, 채팅은 순서대로
- 번호를 붙이면 빠짐 · 뒤바뀜 · 중복을 알아챌 수 있습니다
- 답은 가장 큰 번호 + 비트필드로 몰아서. 답 하나가 사라져도 다음 답이 메웁니다
- 무엇을 다시 보낼지 우리가 정합니다. 위치는 다시 안 보내고 최신을 보냅니다
- 칸을 나누면 한 칸이 막혀도 다른 칸은 흐릅니다. 대신 신뢰+순서 칸은 여전히 막힙니다
- 하트비트로 연결이라는 개념을 직접 만듭니다
- MTU 안에 맞춥니다. 조각나면 하나만 잃어도 전부 잃습니다
- 혼잡 제어를 빠뜨리기 쉽습니다. TCP 가 공짜로 해 주던 것이고, 없으면 스스로 회선을 막습니다
여기까지가 “무엇을 타고 가는가” 입니다. 선 위에서 벌어지는 일은 대충 다 봤습니다.
다음 편부터는 질문이 바뀝니다. 무엇을 보낼 것인가, 그리고 누구 말이 진짜인가.