UDP vs. TCP


발생일: 2013.04.18

문제:
수 개월 전에 아래 링크의 글을 보고 정리했던 내용이다.
http://gafferongames.com/networking-for-game-programmers/udp-vs-tcp/

노트를 정리하다 다시 읽어보니 꽤 괜찮은 내용인 것 같아 옮겨둔다.


해결책:

소켓의 종류 중 주요한 두 가지가 있는데, 하나는 TCP이고 다른 하나는 UDP이다.

TCP/IP에서 TCPs는 transmission control protocol 의 약자이고, IP는 internet protocol 을 의미한다.

TCP 소켓은 신뢰할 수 있는 프로토콜이다.
두 머신에서 데이터를 주고 받을 때, 파일에 쓰는 것처럼 한 쪽에서 쓰고 다른 쪽에서 읽을 수 있다.
연결은 신뢰할 수 있고 순차적이다.
즉, 한 쪽에서 보낸 데이터는 다른 쪽에서 안전하게 받을 수 있다.
TCP 소켓도 데이터 스트림이다. 데이터를 잘라 패킷에 담아 네트워크로 보낸다.

파일을 쓰는 것과 비슷하다. 심플하다.

TCP가 단순할 수 있었던 기반에는 IP가 있다.
IP에는 커넥션과 관련된 컨셉은 없다. 대신 패킷이 한 컴퓨터에서 다른 컴퓨터로 전달되는 것과 관련이 있다.
교실에서 손편지를 써서 건너건너 친구에게 전달하는 걸 떠올려보자.
어떻게든 친구에게 편지가 도착하긴 하겠지만, 여러 사람을 거쳐 전달될 거다.
편지가 친구에게 정확하게 도착할 거란 보장은 없다.
단지, 잘 도착하기만 바라고 보낼 뿐이다.
편지가 잘 도착했는지 알 수 있는 방법이 없다.
물론, 실제론, 어떤 컴퓨터도 가장 빠르게 도달할 수 있는 방법을 알지 못하기 때문에 이보다 더 복잡하기도 하다.
가끔은 패킷을 복사해 여러 경로로 보낼 수도 있다. 이에 따라 각각 도착시간이 달라질 수도 있다.

다른 방법으로, 여러 컴퓨터를 거치지 않고 데이터를 주고 받을 컴퓨터끼리 직접 연결하는 건 어떨까?
이 때 UDP를 사용할 수 있다.
UDP는 "user datagram protocol"의 약자인데, TCP와 같이 IP 기반 위에 구현되어 있다.
하지만, 다른 것과 달리 간단한 작업을 하는 아주 얇은 레이어만 올려져 있다.

UDP를 사용해서, 목적지의 IP(예: 112.140.20.10) 주소와 포트(예:52432)로 메시지를 보낼 수 있고,
컴퓨터를 거쳐 거쳐 목적지까지 도달할 수도 있고, 아님 도착하지 않을 수도 있다.

받는 곳에서는 포트(52432)를 열어두고, 패킷이 올 때까지 기다린다.
이 때, 어떤 컴퓨터에서 온 것이든 모두 받아들인다. (커넥션이 없다는 것을 기억한다)
받는 쪽에서는 패킷이 도착했을 때 이게 어느 IP의 어느 포트에서 왔고 크기가 얼마나 되는지 알 수 있다.
그리곤 패킷 데이터를 읽을 수 있다.

UDP는 안정적이지 않은 프로토콜이다.
일반적으로는 1~5% 정도의 데이터를 잃는 경우가 많고, 심지어는 전혀 받지 못할 수도 있다.
패킷이 전송된 순서 또한 보장되지 않는다.

UDP는 IP에서 많은 일을 하지 않는다.
데이터가 전송될 수도 안될 수도 있지만, UDP가 보장해주는 건 데이터의 '양'이다.
한 쪽에서 256바이트를 보냈으면, 받는 쪽에서도 무조건 256바이트를 받는다.
처음 100바이트를 받을 수는 없다.


중간 정리

TCP
- 커넥션 기반
- 안정성과 순서를 보장한다
- 패킷을 자동으로 쪼개준다
- 적당한 속도로 보내준다. (회선이 처리할 수 있을 만큼)
- 파일을 쓰는 것처럼 사용하기 쉽다

UDP
- 커넥션 기반이 아니다. 직접 구현해야 한다
- 안정적이지 않고 순서도 보장되지 않는다. 데이터를 잃을 수도, 중복될 수도 있다.
- 데이터가 크다면, 보낼 때 직접 패킷 단위로 잘라야 한다.
- 회선이 처리할 수 있을만큼 나눠서 보내야 한다.
- 패킷을 잃었을 경우, 필요하다면 다양한 방법으로 이를 찾아내서 다시 보내야 한다.

이럼 TCP가 되게 좋아보이지만, 사실 네트워크 게임 프로그래밍에선 실수하기 쉽다.


TCP가 정확히 하는 일

TCP와 UDP 모두 IP 기반이지만, 두 가지는 근본적으로 다르다.
UDP는 IP와 거의 비슷하게 동작하지만, TCP는 그렇지 않다.

TCP는 스트림 프로토콜이다.
우리는 스트림으로 바이트를 쓰고, TCP는 상대편에게 잘 전달하도록 한다.
IP가 패킷으로 구현되어 있기 때문에, TCP도 스트림을 패킷으로 잘라야 한다.
그래서, 내부적으로 데이터를 큐에 쌓아두고 지연해뒀다가 패킷 단위로 보낸다.

이건, 멀티 플레잉 게임에는 적합하지 않을 수 있다.
아주 작은 데이터를 보내고 있다고 가정해보자.
TCP는 데이터가 버퍼에 쌓일 때까지 보내지 않고 보관하고 있는다.
우리는 클라이언트에 데이터가 최대한 빨리 전달되기를 바라지만, 버퍼에 쌓아뒀다 나중에 보낸다면 게임 경험이 후지게 될 거다.

TCP에는 위와 같은 경우를 보정하기 위한 "TCP_NODELAY" 옵션이 있다.
이 옵션을 설정하면, 어떤 데이터를 보내든 큐에 쌓지 않고 바로 보내버린다.
하지만 이렇게 하면 순서를 보장할 수 없어서 TCP를 사용하는 장점을 잃게 된다.


TCP가 안정적이게 처리하는 방법

TCP는 스트림을 패킷으로 잘라서 IP로 보낸다.
받는 쪽에서는 패킷을 다시 조립한다.
내부 구현은 매우 복잡한데, 데이터를 잃어버린 경우 이를 판단해서 다시 보낸다.
데이터가 중복으로 보내질 수도 있지만, 이건 받는 쪽에서 조립할 때 해결한다.
그래서 안정적으로 모든 데이터를 보낼 수 있고, 순서도 보장되는 것이다.

문제는 TCP를 이용해서 동기화 할 때이다.
패킷이 제대로 도착하지 않을 때마다, 새로 받을 때까지 기다려야 한다.
즉, 중간에 데이터가 제대로 도착하지 않았다면, 새 데이터가 도착했어도 여기에 접근할 수 없다.

그럼 패킷을 다시 보내는 데는 얼마나 걸릴까?
적어도 데이터가 누락됐다고 알려주는 시간은 걸린다. (round trip latency)
예를 들어 125ms 로 ping을 보내고 있다면, 1/5 초가 걸린다.


네트워크 게임에 TCP를 쓰면 안되는 이유

게임은, 웹 브라우저나 이메일 같은 애플리케이션과 달리 실시간으로 패킷을 주고 받아야 한다.
사용자의 입력이나 캐릭터의 위치 같은 것들에는 몇 초 전의 정보가 중요하지 않다.
아주 최근의 정보가 가장 중요하다.

아주 간단한 슈팅 게임을 생각해보자.
우리는 아주 간단하게 구현해보려고 한다.
매 프레임마다 클라이언트의 입력 정보를 서버로 보내고(마우스, 키보드, 리모트컨트롤의 입력)
서버는 각 플레이어의 입력을 계산하고, 시뮬레이션을 업데이트 하고,
게임 객체의 현재 위치를 렌더링해준다.

여기서 만약 TCP를 사용한다면, 패킷을 잃을 때마다 서버나 클라언트는 응답을 기다려야 한다.
여기서 우리가 원하는 건, 잃어버린 데이터를 기다리지 않고, 최대한 빠르게 최신 데이터를 전달하는 거다.


가만, 그럼 TCP와 UDP를 섞어 쓰면 어떤가?

슈팅 게임을 그렇지 않지만, 게임에 따라 명령의 순서가 중요한 경우도 있다.
이런 경우엔, 순서가 중요한 경우엔 TCP를 쓰고, 사용자 입력이나 상태 처리엔 UDP를 쓰고 싶을 수 있다.
그렇지만, TCP와 UDP를 번갈아 가며 처리하기가 복잡하기도 하고,
TCP가 UDP의 패킷 손실을 유발한다는 정보도 있다.


해서, 게임에는 UDP만 쓸 것을 추천한다.
TCP를 섞어 쓰지말고, UDP로 받은 패킷을 어떻게 TCP처럼 사용할 수 있을 지를 연구하라.


카테고리

분류 전체보기 (710)
About me. (6)
Daylogs (675)
영어공부 (0)
My works - 추억 (29)
비공개 (0)