CS/Network

[Network]신뢰적인 데이터 전송과 TCP(2)

frog-in-well 2022. 7. 31. 22:27
 

[Network]신뢰적인 데이터 전송과 TCP

UDP와 TCP의 가장 큰 차이는 TCP가 신뢰적인 데이터 전송을 기반으로 하는 프로토콜이라는 것이다. 이번 글에서는 신뢰적인 데이터 전송이란 무엇인 지 알아본 뒤 다음 글에서 본격적으로 TCP에 대

frog-in-well.tistory.com

지난 글에서는 신뢰적인 데이터 전송이 무엇인 지 알아보았다. 이번 글에서는 TCP에서 어떻게 신뢰적 데이터 전송이 이루어지는 지 알아보자. 

1. TCP

1.1 TCP 연결

TCP는 애플리케이션 프로세스가 다른 프로세스로 메시지를 전달하기 전에 “핸드셰이크"를 하기 때문에 연결지향형 프로토콜이라고 한다. 세 방향 핸드셰이크를 통해 연결이 설정되면 두 애플리케이션 프로세스는 서로 데이터를 보낼 수 있다.

다시 설명하자면 TCP는 종단 시스템에서 구현되기 때문에 중간의 라우터들은 TCP 연결을 감지하지 못한다.

또한, TCP 연결은 점대점 연결이다. 한 송신자가 여러 수신자에게 데이터를 전송하는 것은 불가능하다.

1.2 TCP 세그먼트

  • 순서 번호 필드, 확인응답 번호 필드 : 신뢰적인 데이터 전송 서비스 구현을 위해 사용
  • 수신 윈도우 : 흐름제어에 사용
  • 플래그 6비트 : ACK - 성공적으로 수신된 세그먼트에 대한 확인응답(handshake에서 사용) / RST,SYN,FIN - 연결 설정 및 해제에 사용 /PSH, URG

순서번호는 세그먼트에 있는 첫 번째 바이트의 바이트-스트림 번호다.

만약 프로세스가 보내려는 데이터 스트림이 500,000 바이트이고 최대 세그먼트 크기가 1,000 바이트라면 총 500개의 세그먼트가 만들어진다. 이 때 첫 번째 순서 번호는 0, 두 번째 순서 번호는 1000, 2000,3000, …, 과 같은 순서로 전송될 것이다.

호스트 A가 호스트 B로 데이터를 전송하고 있다고 생각해보자.

  • 호스트 A → B / 순서 번호 : 1000 (500바이트 크기 데이터)
  • 호스트 B → A / 확인응답 번호: 1501

확인응답 번호에는 수신자가 기대하는 다음 바이트의 순서 번호를 삽입한다.

만약 잘못된 순서가 도착한 경우에는 누적 확인응답을 통해 처음으로 잃어버린 바이트의 번호를 달라고 요청한다. 이 때 순서가 잘못된 세그먼트를 버릴 지, 저장하고 이전 순서를 기다릴 지는 TCP를 구현할 개발자가 결정할 수 있따.

1.3 RTT와 타임아웃

TCP는 세그먼트가 송신된 시간부터(IP에게 넘겨진 시간) 그 세그먼트에 대한 긍정응답이 도착한 시간까지 시간인 RTT를 측정한다.

TCP에서는 모든 전송된 세그먼트에 대해서 샘플 RTT를 측정한다.(재전송된 세그먼트는 측정하지 않음) 라우터에서의 혼잡과 종단 시스템에서의 부하 변화 때문에 세그먼트마다 RTT의 값이 다를 것이다.

TCP는 RTT 예측을 위해 이 샘플 RTT들의 평균값을 채택한다. 이 때, 최근의 샘플들이 가장 최근의 네트워크 혼잡 상태를 더 잘 반영하므로 지수적 가중 이동 평균을 사용한다.

새로운 샘플 RTT가 측정될 때마다 다음과 같이 평균을 갱신한다. (일반적으로 a의 값은 0.125를 사용)

EstimatedRTT = (1-a) * EstimatedRTT + a * SampleRTT

 

또한, TCP에서는 RTT의 변화율인 DevRTT를 측정힌다. (b의 값은 0.25)

 

DevRTT = (1-b) * DevRTT + b * | SampleRTT - EstimatedRTT |

1.4 타임아웃 주기 설정

EstimatedRTT와 DevRTT를 계산한 이유는 앞서 설명한 신뢰적 네트워크에서의 타임아웃을 위한 시간을 결정하기 위해서이다.

타임아웃 주기가 너무 크면 세그먼트를 잃었을 때, TCP 세그먼트의 재전송이 느리게 일어나 불필요한 지연이 발생할 것이다. 반대로 너무 작은 경우에는 불필요한 재전송이 일어날 것이다.

EstimatedRTT를 타임아웃 주기로 설정하게 된다면 너무 잦은 타임아웃이 발생할 것이므로 이에 여유 시간을 더하여 설정하는 것이 적절한 방법이다.

만약 SampleRTT에 변동이 크다면 여유 시간이 커야할 것이고 변동이 작을 때에는 작아야 할 것이다. DevRTT가 필요한 이유이다.

TimeoutInterval = EstimatedRTT + 4*DevRTT

2. 신뢰적인 데이터 전달

지금까지 공부한 내용으로 TCP 송신자의 동작을 정리하고 어떻게 신뢰적인 데이터 전송을 제공하는 지 살펴보자.

  • TCP 송신자는 애플리케이션에게 데이터를 받고, 세그먼트로 캡슐화하고, IP에게 세그먼트를 전달한다.
  • TCP의 단일 타이머가 다른 세그먼트를 측정하고 있지 않다면 전달과 동시에 타이머를 시작한다.
  • 타임아웃을 일으킨 세그먼트가 있다면 재전송한다.

2.1 타임아웃 주기의 두 배로 설정

만약 타임아웃이 발생한다면 TCP에서는 타임아웃 주기를 유지하는 대신에 2배로 늘린다. 이후 다시 세그먼트를 재전송하고, 또 타임아웃이 발생한다면 다시 2배로 늘린다.

2.2 빠른 재전송 (3개의 중복 ACK)

이제 수신자의 동작을 살펴보자.

  • 만약 기다리는 순서번호의 세그먼트가 도착한다면, 바로 ACK를 보내지 않고 연속해서 오는 세그먼트를 위해 500mec까지 기다린 후 ACK를 전송한다.
    • 만약 기다리는 동안 추가적인 세그먼트가 도착한다면 누적 확인응답 번호를 보낸다.
  • 기다리고 있는 것보다 높은 순서번호를 가진 세그먼트가 도착한다면 즉시 중복 ACK를 보낸다.
  • 격차가 모두 채워진다면 즉시 ACK를 보낸다.

수신자는 부정 확인응답을 사용하지 않기 때문에 데이터의 순서가 잘못된 경우에는 중복된 확인응답 번호를 전송하게 된다.

송신자는 3개의 중복 ACK를 수신하는 경우에는 타임아웃 이벤트가 발생하지 않았더라도 손실 세그먼트를 빠른 재전송을 한다. (초기 ACK 1개 + 3개 중복 ACK로 총 동일한 ACK 4개를 받으면 재전송한다.)

3. 흐름제어

TCP가 바이트를 수신하고 애플리케이션 프로세스가 읽기 전까지 수신 버퍼에 데이터를 저장한다. 버퍼에 있는 데이터를 애플리케이션이 가져가는 것은 애플리케이션이 선택할 일이다. 만약 애플리케이션이 데이터를 읽는 속도가 느리지만 송신자는 빠르게 데이터를 전송하고 있다면 수신 버퍼에 오버플로가 발생할 것이다.

3.1 수신 윈도우

TCP는 이를 방지하기 위해 흐름제어 서비스를 제공한다. 흐름제어는 송신자를 억제하는 정책이다.

수신 윈도우라는 변수를 유지해서 흐름제어를 제공한다. 수신 측에서 가용한 버퍼 공간이 얼마나 되는지를 송신자에게 알려준다. 참고로 TCP는 전이중 방식이기 때문에 각 송신자에게도 수신 윈도우가 별개로 존재한다.

  • 수신 윈도우의 크기 = 수신 버퍼의 크기 - (수신 버퍼에 저장된 바이트의 수 - 애플리케이션 프로세스가 읽은 바이트의 수)
  • 초기에는 수신 윈도우의 크기는 수신 버퍼의 크기와 동일할 것이다. 하지만 수신 호스트의 애플리케이션이 데이터를 읽지 않고 있다면 수신 윈도우의 크기는 0이 된다.
  • 송신자 입장에서 수신 버퍼에 공간이 생겼음을 확인하기 위해 수신 윈도우가 0인 경우에도 송신자는 계속해서 1바이트 데이터의 세그먼트를 전송한다.