TCP/IP 스택 - 전송, 응용

Byeonggwan Kang·2021년 9월 1일
0
post-thumbnail

TCP/IP 5 계층

이전 포스트에서 네트워크 계층이 필요한 이유까지 알아봤습니다. 다음 상위 계층은 전송계층입니다.


전송 계층

네트워크 계층의 IP 주소가 컴퓨터끼리 통신하는 방법이라면, 전송 계층은 컴퓨터 안의 프로세스들이 통신하는 방법입니다. 여기서 우리가 아는 포트(port)라는 개념이 등장합니다.

포트

포트는 프로세스끼리 통신하기 위해 사용되는 번호입니다. 컴퓨터에 들어오는 모든 요청을 구분하기 위해서 우리는 포트를 사용합니다.

만약에 서로 다른 어플리케이션끼리 같은 포트를 사용하려고 한다면 충돌이 일어납니다. 따라서 개발자들은 특정 범위의 포트들을 다른 용도로 사용하지 않기로 약속했습니다. 이 책에서는 우리가 프로그램을 개발할 때에 포트 번호 49152부터 65535까지인 동적 포트를 사용하다가, ICANN(위키링크)에서 다른 포트를 배정받기를 권장합니다.

전송 계층을 설명할 때 빠지지 않고 등장하는 프로토콜 두 개가 있습니다. 바로 UDP와 TCP입니다.

UDP

UDP(user datagram protocol)은 IP를 사용하는 컴퓨터끼리 데이터를 전달하기 위한 프로토콜입니다. 말 그대로 전달만 하기 때문에 아주 단순합니다. 패킷의 헤더에도 송수신 포트, 총 길이, 체크섬만 존재합니다. 하지만 단순한 만큼 데이터의 순서나 전달 유무를 보장해주지는 않습니다.

TCP

TCP(transmission control protocol)은 IP를 사용한다는 점에서 UDP와 유사하지만 컴퓨터간 연결을 유지하고 데이터의 순서나 도착 유무를 보장한다. (TCP 헤더나 연결 전 후로 사용하는 3-handshaking이 궁금하다면 위키링크)

어떻게 TCP는 데이터 전달 유무를 보장하는가?

TCP는 패킷을 주고 받을 때 시퀀스 번호와 ACK 번호가 포함된 세그먼트를 주고 받습니다. 각 세그먼트는 첫 바이트의 시퀀스 번호를 가지고 시퀀스 번호는 1바이트마다 1씩 증가됩니다. ACK 번호는 반대 호스트에게 받을 다음 시퀀스 번호를 의미합니다.

세그먼트를 보냈음에도 불구하고 상대 호스트의 ACK이 오지 않는다면 문제가 생긴 것이므로 다시 세그먼트를 보낼 수 있습니다.

어떻게 TCP는 데이터 순서를 보장하는가?

ACK 번호는 '이 정도 데이터까지는 확실히 받았다'라는 뜻입니다. 어떤 경우에는 상대 호스트가 보낸 세그먼트의 시퀀스 번호가 ACK 번호와 맞지 않을 수도 있습니다. 이 경우엔 그냥 세그먼트를 버리거나 버퍼에 가지고 있으면서 내가 원하는 세그먼트가 올 때까지 기다릴 수 있습니다.

어떻게 TCP는 느린 속도를 극복할 수 있는가?

TCP는 상대방의 ACK이 오지 않아도 여러 세그먼트를 보낼 수 있습니다. 이렇게 받은 세그먼트는 버퍼에 담겠지만 너무 많은 양의 세그먼트가 온다면 처리 속도가 감당하지 못해서 메모리 버퍼가 꽉 차버릴 것입니다. 이런 경우엔 다음 세그먼트를 누락시키기 때문에 대역폭이 낮아집니다.

이러한 문제를 해결하고자 TCP는 흐름 제어(flow control)혼잡 제어(congestion control)를 합니다. 흐름 제어는 상대방에게 보낼 패킷의 양을 조절하는 것으로, 현재 버퍼의 남은 공간을 나타내는 수신 윈도우(receive window)를 이용합니다.

흐름 제어가 호스트 간의 처리 속도에 따라서 데이터 양을 조절한다면, 혼잡 제어는 네트워크의 혼잡도에 따라서 조절합니다. 이는 패킷을 주고 받으면서 계속 윈도우 크기를 늘려가다가 패킷 로스가 일어날 때 크기를 줄이는 식으로 구현됩니다. 자세한 구현 방법을 알고 싶다면 위키링크



응용 계층

응용 계층은 TCP/IP 5계층의 최상위 계층입니다. 지금까지 배운 식으로 말하자면 UDP와 TCP 위에서 어플리케이션이 사용하는 프로토콜이라고 할 수 있습니다. 대표적으로 HTTP, DHCP, DNS 프로토콜 등이 있습니다.

여기서는 이 책에서 가장 재밌게 본 프로토콜 하나를 소개하고 마치겠습니다.

NAT

이론상 IP는 0.0.0.0부터 255.255.255.255까지 이루어져 있어서 엄청나게 많지만, 갈수록 인터넷을 사용하는 기기가 늘어나 이 양은 턱없이 부족해졌습니다.

따라서 한 IP로 여러 인터넷 기기들을 사용할 수 있게 하는 기술인 NAT(network address translation)이 등장했습니다. 우리들 집에 있는 공유기가 이 프로토콜을 사용합니다.

NAT를 사용하기 위해서는 이 공유기에 연결된 서브넷 기기들이 공인 IP 대신 사설 IP를 할당받게 됩니다.

사설 IP 범위 (공인 IP는 이 범위에 포함되지 않음)
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16

이렇게 사설 IP를 사용하는 서브넷과 인터넷에 직접 연결된 큰 네트워크를 LAN과 WAN으로 나누기도 합니다.

NAT 예시

요즘 가정집에는 거의 모두 공유기를 사용합니다. 이 경우에서는 게임 서버와 가정집 컴퓨터가 어떻게 데이터를 주고 받는지 예시를 들어 설명하겠습니다.

위 그림은 아무무가 게임 서버에 접속하는 상황을 나타낸 아름다운 다이어그램입니다.

우선 아무무의 컴퓨터는 발신지와 수신지를 표시한 패킷(192.168.0.3:4000과 5.6.7.8:9000)을 공유기로 먼저 보냅니다. 공유기는 게임 서버와 패킷을 주고 받을 포트를 할당합니다. 이 경우 1.2.3.4:5000입니다.

이 후 NAT 테이블에 공유기의 5000포트는 아무무의 컴퓨터와 게임 서버가 주고 받는 통로라고 명시합니다. 공유기는 이 패킷의 발신지를 공유기 공인 IP의 5000포트로 변경한 뒤 서버와 패킷을 주고 받게 됩니다.

이 다음부터는 공유기의 5000포트로 들어오는 패킷을 NAT 테이블에 따라 아무무의 컴퓨터인 192.168.0.3에 전달해줄 수 있습니다.

NAT 투과

그렇다면 가정집에 있는 컴퓨터끼리 통신하기 위해선 무조건 서버를 거쳐야 할까요?

우리가 친구와 스타크래프트를 같이 한다고 상상해봅시다. 나의 컴퓨터를 호스트 A, 친구의 컴퓨터를 호스트 B라고 하겠습니다. 배틀넷을 킨 뒤, 같은 방에 입장하고 게임을 합니다. 여기서 게임을 하는 동안 중간에 서버를 계속 거쳐가야 한다면 굉장히 느려지겠죠? (사실 실제로 어떻게 구현했는지는 저는 모릅니다...)

따라서 배틀넷은 호스트 A와 B를 연결해주는 중개 서버 역할만 합니다. 서로의 IP만 알려주고 게임을 진행시킬수도 있겠죠. 하지만 IP만으로는 두 호스트가 공유기를 사용하는 상황에 컴퓨터끼리 소통할 수가 없습니다. 공유기에는 여러 인터넷 기기가 물려있어서 들어오는 패킷이 어디로 향해야 하는지 알 수가 없기 때문입니다.

따라서 두 공유기는 어떤 포트가 호스트 A와 B를 연결하는 통로인지 명시해야만 합니다. 이 책에서는 이를 구현하는 방식을 STUN(simple traversal of UDP through NAT), 또는 TCP 홀 펀칭이라는 방법으로 설명합니다. 쉽게 설명하자면 게임 중개 서버에 접속한 두 기기의 공인 IP와 포트를 서로에게 알려주는 방법입니다. 이렇게 하면 두 공유기는 호스트 A와 B로 직접 가는 통로를 마련할 수 있는 것입니다.



마치며

이렇게 TCP/IP 5 계층을 전부 알아봤습니다. 물론 모든 계층이 중요하지만 게임 서버를 다룰 때에는 전송 및 응용 계층이 가장 중요해 보였습니다. 최적화의 부분도 여기서 많이 이루어지겠죠.

우리가 어떤 게임을 만드는가에 따라서 TCP를 쓸지 UDP를 쓸지, 그 위에서는 어떻게 통신할 것인지 확실하게 알아야 합니다. 단순히 TCP는 느리지만 신뢰도가 높고 UDP는 그 반대다, 이런 식으로 아는 것은 빈 껍데기에 불과한 것 같습니다. 어떤 프로토콜을 사용하는가에 따라서 턴제 게임에는 어떤 이득과 손해가 있는가? 유닛의 위치를 어떻게 동기화해야 하는가? 3D 게임에서 캐릭터가 버벅거리며 움직이는 것처럼 보이는 건 어떻게 해결해야 하는가? UDP에게 최소한의 신뢰성을 부여하려면 어떻게 해야할까? 이런 수도 없이 많은 질문을 이해하기 위해서는 기본부터 제대로 알고 관련된 정보를 최대한 많이 찾아봐야 한다는 것을 느꼈습니다.

0개의 댓글