본문 바로가기
컴퓨터과학/네트워크

3-4. 전송(Transport) 계층: TCP

by 니키티스 2021. 10. 17.

해당 글은 한양대학교 이석복 교수님의 과목 "컴퓨터 네트워크"를 공부하고 작성한 글입니다. 틀린 내용이 있으면 덧글로 지적해주시면 감사하겠습니다.

컴퓨터 네트워크 - 한양대학교 | KOCW 공개 강의

 

컴퓨터네트워크

인터넷을 동작시키는 컴퓨터네트워크 프로토폴을 학습한다.

www.kocw.net

 


 

TCP

0. Overview

TCP의 특징을 요약하면 다음과 같다.

 

  1. point-to-point
    한 프로세스에는 무조건 한 프로세스가 매핑된다.
    • sender 하나에 receiver 하나가 연결된다.
    • TCP는 소켓 한 쌍의 통신을 책임진다.
  2. reliable, in-order byte
    • 신뢰성이 보장되므로 데이터가 유실되거나 데이터에 에러가 발생하지 않는다.
    • 전송하면 데이터가 순서대로 전송된다.
  3. pipelined
    • 파이프라인 방법을 사용하여, 한 번에 데이터를 쏟아붓는다.
    • ACK가 돌아올 때까지 기다리지 않고 다음 데이터를 전송한다.
  4. full duplex data
    • 같은 연결에서 양방향으로 데이터가 전송된다.
    • 서버, 클라이언트 모두 서로에게 데이터를 전송할 수 있다. 즉, 모든 서버와 클라이언트는 sender이자 receiver이다.
  5. sender & receiver buffer
    • sender는 전송해야 할 데이터를 저장하기 위해 버퍼(buffer)를 사용한다.
    • receiver는 순서가 뒤바뀐 바이트를 바이트를 받기 위해 버퍼를 사용한다.
    • 모두 sender이자 receiver이므로 모든 디바이스는 sender 버퍼receiver 버퍼를 갖는다.
  6. flow controlled
    • sender는 receiver가 받을 수 있는 만큼만 패킷을 전송할 수 있다.

 

UDP와 달리 TCP는 소켓 한 쌍의 통신을 담당한다. UDP하나의 서버 소켓여러 개의 클라이언트 소켓이 매핑되어 통신을 하게 된다. 그러나 TCP는 그 특징으로 인해 한 서버 소켓에 무조건 하나의 클라이언트 소켓이 매치된다.

TCP는 모두가 전송을 담당하는 sender이자 메시지를 수용하는 receiver이므로 기본적으로 양방향 통신이다. 보다 데이터를 빠르게 전송하기 위해 파이프라인 기법을 사용하기도 한다.

 

1. TCP 세그먼트 구조

TCP 세그먼트는 위와 같이 구성되어 있다.

그렇다면 TCP는 어떤 방식으로 되어 있는가? 이를 알아보기 전에 TCP를 통해 전송되는 세그먼트 구조를 보면서 어떤 방식으로 동작하는지 알아보자.

 

  1. 포트 번호
    TCP는 다중화(mutliplexing)와 역다중화(demultiplexing) 과정을 위해 출발지와 도착지의 포트 번호를 요구한다. 포트 번호는 각각 16비트씩 할당되어 총 32비트로 구성되는데, 따라서 포트 번호는 $2^{16}=65536$개만큼 존재할 수 있다. 즉, 0~$65535$까지 존재한다.
  2. 시퀀스 번호(sequence number)
    sequence number는 세그먼트 데이터의 첫 바이트의 바이트 스트림 번호(byte stream number)를 나타낸다. 100바이트를 전송할 때 첫 메시지(세그먼트)는 0~9번 바이트, 두 번째 메시지는 10~29번 바이트까지 보낸다고 하자. 그러면 첫 메시지의 seq #(시퀀스 넘버) = 0이고, 두 번째 메시지의 seq # = 10이 된다.
  3. acknowledgement number
    acknowledgement number는 ACK 번호를 가리킨다. 주의할 점은, TCP에서의 ACK는 '다음번에 받을 바이트의 seq #'이다. 지난 포스팅에서 Go-Back-N에서는 ACK 10이 '10번 세그먼트까지 받았음'을 의미하지만, TCP는 '9번 세그먼트까지 받았고 10번을 받을 차례'를 의미한다. Go-Back-N과 동일하게 ACK가 누적(cumulative)한 방식이지만 그 번호가 갖는 의미가 약간 다른 셈이다.
  4. checksum
    체크섬은 에러를 발견하기 위해 사용한다.
  5. Receive window
    receive window는 현재 receiver가 수신할 수 있는 바이트의 수를 말한다. receive 버퍼에 남은 공간이 얼마나 되는지를 가리킨다.

 

이 이외에도 다양한 데이터가 있지만 그건 나중에 살펴보자.

 

서로 하고 싶은 말만 한다

모든 클라이언트와 서버는 송신자(Sender)이자 수신자(Receiver)이다.

메시지를 보낼 때마다 서로 시퀀스(Seq) 번호와 ACK 번호를 전송한다.

Seq 번호는 send buffer에서 보내는 첫 바이트 번호이다.

그리고 상대 send buffer에게서 받고자 하는 바이트 번호를 ACK 번호로 요청한다.

 

두 호스트가 서로 통신하는 모습

위 그림은 두 호스트가 자신이 받은 메시지를 상대에게 다시 전송해주는 모습이다. 호스트 A가 호스트 B에게 send buffer에서 문자 'C'를 전송하면 호스트 B도 호스트 A에게 send buffer에서 문자 'C'를 전송하고, 호스트 A가 또 호스트 B에게 같은 방식으로 데이터를 전송한다.

 

데이터와 함께 헤더에는 Seq와 ACK를 전송한다. 서로 한 바이트씩 보내므로, 상대방(Host B)의 ACK는 나(Host A)의 Seq의 다음 번호(Seq + 1)가 된다. Host A가 Seq 42로 1바이트를 전송했으므로 Host B가 요구하는 바이트 스트림 번호는 42 + 1인 ACK 43이 된다. 그리고 상대방(Host B)이 다음에 나에게 보낼 데이터는 내가 ACK로 요구한 번호와 같다. Host A가 ACK 79로 79번 바이트를 요구했으므로, 다음에 Host B가 전송할 데이터는 79번이고 Seq 79로 메시지를 보내게 된다.

 

서로가 송신자(Sender)이자 수신자(Receiver)이므로, 상대방에게서 받은 메시지에 ACK를 보내주는 것과 동시에 메시지를 보낼 수도 있다(Seq)는 것이 특징이다.

 

Timeout을 얼마로 하는가

지난 포스팅(3-2. 전송(Transport) 계층: 신뢰성 있는 데이터 전송(RDT) (tistory.com))에서 타이머는 길이마다 장단점이 있어서 짧음과 긺 사이에서 적당히 조절해야 한다고 설명했다.

 

그러나 Timeout을 고정된 상수 값으로 설정하게 되면 문제가 생길 수 있다. 메시지를 보내고 ACK가 다시 돌아올 때까지의 시간을 패킷 왕복 시간 또는 RTT(Round trip time)라 하는데, 이론적으로 timeout은 RTT 정도로 설정하는 것이 가장 적절하다. 그러나 이 RTT가 모든 세그먼트 간에 동일하면 상관이 없지만, 각 세그먼트마다 RTT가 달라질 수 있다는 것이 문제이다. 세그먼트마다 지나는 경로가 다를 수도 있고(더 먼 경로더 가까운 경로) 같은 경로를 지나도 같은 라우터가 갖는 큐잉 지연(Queueing delay, 얼마나 많은 패킷이 대기 중인가?)도 접속자의 수에 따라 달라질 수 있다.

 

실제 RTT와 Estimated RTT 값

위 그림에서 남색 점은 RTT 값을 나열한 것이다. RTT는 상황에 따라 중구난방으로 값이 바뀌고 예측하기 어렵다.

 

따라서 EstimatedRTT를 사용하여 RTT를 추정한다. EstimatedRTT는 다음과 같이 구한다.

EstimatedRTT는 지수 가중 이동평균(Exponentially Weighted Moving Average)으로, 쉽게 말해서 최근에 나타난 RTT의 경향성을 통해 RTT를 추정한 것이다.

이렇게 구한 EstimatedRTT는 SampleRTT에 비해 훨씬 안정적인 변화 양상을 보이며, 이전에 나타난 RTT를 통해 다음 RTT를 추정할 수 있다는 특징이 있다.

그러나 그래프를 보면 알 수 있듯, EstimatedRTT는 SampleRTT에 비해 상대적으로 작거나 클 때가 있으므로, 이 값을 그대로 timeout 값으로 사용하기는 어렵다. EstimatedRTT < SampleRTT일 경우 유실이 확실하지 않은 상황에서 timeout으로 처리될 수 있다.

 

따라서 EstimatedRTT에 특정한 수를 더하여 timeout을 판별한다. 해당 식은 아래에 적어놓았지만 굳이 외울 필요는 없다고 한다.

TCP에서 Timeout 값은 위와 같이 설정한다.

 

2. Reliable data transfer

TCP에서 보장하는 rdt(Reliable data transfer) 서비스는 기존 rdt와 비교하여 세 가지 차이점이 있다.

  1. 파이프라인 방식으로 세그먼트를 전송
  2. 누적 확인 응답(Cumulative acks)
  3. TCP는 재전송을 위해 단일 타이머를 사용함

 

또한 TCP는 Go-Back-N과 달리, timeout이 발생하면 해당 세그먼트만 전송하게 된다.

 

예시

대표적인 예시로 재전송의 동작 방식을 알아보자.

ACK가 유실되는 경우(좌)와 너무 이르게 timeout이 발생했을 때(우)

첫 번째, ACK가 유실되는 경우가 있다.

Host A가 92~99번 바이트(8바이트)를 전송하고 Host B는 제대로 받아 ACK 100을 전송한다.

그런데 ACK 100이 유실되었으므로 timeout이 발생하고 재전송된다.

 

두 번째, ACK가 전송되는 중 timeout이 발생하는 경우가 있다.

timeout이 발생했으므로 Host A는 ACK를 받은 후에 가장 처음 보낸 메시지(Seq=92)를 재전송한다.

Host B는 Seq=92, Seq=100인 데이터를 받아 다음에는 Seq=120인 메시지를 기다린다. 그런데 timeout이 터지고 나서 처음에 받은 메시지(Seq=92)를 받으니 이 메시지를 버리고 ACK를 다시 전송하게 된다. 이 경우 가장 최근의 ACK인 ACK 120을 전송한다.

Host A는 ACK 120을 받았으므로 send 버퍼에서 119번 바이트까지를 모두 비우고 타이머를 초기화한다.

 

 

Cumulative ACK(좌)

세 번째, ACK가 유실되지만 가장 마지막 ACK를 받는 경우가 있다.

해당 시나리오에서는 ACK 100과 ACK 120이 전송되다가 ACK 120만이 유실되지 않고 도착하였다.

그러나 TCP는 누적 확인 응답(cumulative ACK) 방식을 사용하므로, 하나의 ACK만으로 데이터 전송을 확인할 수 있다. ACK 100이 유실되었음에도 불구하고 Host A는 ACK 120을 통해 92번~99번 바이트와 100번~119번 바이트가 모두 정상 도착했음을 알 수 있다.

ACK 120은 119번 바이트까지 모두 전송되었고 120번 바이트부터 받을 차례라는 것을 의미하기 때문이다.

 

이외에도 Host A에서 Host B로 전송한 메시지가 유실되는 케이스, 메시지에 에러가 존재하는 케이스 등이 있지만 이는 rdt 3.0에서 말한 방식과 동일한 것으로 보인다.

 

수신자(receiver)는 반드시 메시지를 받을 때마다 ACK를 보내야 하는가?

여기서 한 번쯤 생각해볼 점은 ACK를 매번 보낼 필요가 없다는 것이다.

세 번째 예시에서 보면 모든 데이터를 전송하고 나서 마지막 ACK만 제대로 받으면 그 이전의 ACK는 유실되든 말든 큰 문제가 없는 걸로 보인다.

한 번에 ACK를 몰아서 보낸다면 네트워크 자원을 아낄 수 있지 않을까?

 

rfc2581 (ietf.org)

 

rfc2581

 

datatracker.ietf.org

권고안(RFC 1122, RFC 2581)에 따르면 딜레이 하여 ACK를 전송하는 것에 대해 이야기하고 있다. 권고안에서는 일정 간격마다 ACK를 전송하도록 하며, 이렇게 한 번에 몰아서 ACK를 보내주면 오버헤드를 방지할 수 있다.

 

빠른 재전송(Fast retransmit)

그런데 RTT에 비하면 timeout은 상당히 길다. 메시지가 돌아오는 데에 걸리는 시간보다 타이머가 기다리는 시간이 훨씬 더 길다는 것이다. 위에서 TCP 세그먼트 구조를 설명할 때 timeout 길이는 추정된 RTT 값에 특정 값을 더해서 정한다고 설명했다. 여기서 특정 값만큼의 시간이 매우 길기 때문에 timeout이 발생할 때까지 기다렸다가 재전송하기에는 시간이 너무 많이 걸릴 수 있다. 어떻게 하면 timeout을 기다리는 시간을 줄일 수 있을까?

 

timeout이 발생하기 전에 유실이 발생했음을 확인하는 방법이 있는데, 우선 Seq=0부터 패킷을 보내는데 10번 패킷이 유실되었다고 가정하자.

8번 패킷을 보내고 ACK 9, 9번 패킷을 보내고 ACK 10이 돌아오게 된다. 만약 10번 패킷을 보내고 유실되면 ACK 11이 아니라 ACK 10이 돌아오게 되고, TCP는 누적 확인 응답(cumulative ack)을 사용하기 때문에 11번, 12번, 13번 패킷을 전송했을 때 ACK 10, ACK 10, ACK 10으로 같은 바이트에 대해 ACK가 날아오게 된다. 이렇게 같은 바이트에 대해 ACK가 계속해서 발생하면 해당 바이트가 유실되었음을 알 수 있다.

 

이렇게 유실을 판단하는 정책을 '빠른 재전송(Fast retransmit)'이라 하며, 세 번 복제된 ACK가 발생할 경우(three duplicated ACKs) 유실로 판단하고 빠르게 재전송한다.

빠른 재전송이 없어도 정상적으로 통신이 이루어지지만, 빠른 재전송은 더 빠르게 유실을 처리할 수 있게 해 준다.

 

여기서 주의할 점은 세 번 복제된 ACK가 발생할 때 재전송한다고 말했는데, ACK 9, ACK 10, ACK 10, ACK 10, ACK 10으로 ACK 10이 네 번 발생했을 때 재전송을 한다는 점이다. 같은 ACK를 4번 받으면 3번이 복제된 것이므로 헷갈리지 않게 유의하자.

 

 

참고 자료

이동평균 - 위키백과, 우리 모두의 백과사전 (wikipedia.org)

 

이동평균 - 위키백과, 우리 모두의 백과사전

주가 기술적 분석을 위한 이동평균선 사례 이동평균(移動平均, moving average, rolling -, running -)은 전체 데이터 집합의 여러 하위 집합에 대한 일련의 평균을 만들어 데이터 요소를 분석하는 계산이

ko.wikipedia.org

ACK (ktword.co.kr)

 

ACK

Positive Acknowledgement, 긍정 확인응답, NACK, NAK, Negative Acknowledgement, 부정 확인응답, SACK, Selective Acknowledgement, 선택 확인응답

www.ktword.co.kr

 

댓글