1. TCP의 segment 헤더

- TCP의 segement의 헤더는 다음 필드로 이루어져 있다.

  • source port 번호

  • destination port 번호

  • sequence number

  • ACK number: TCP의 센더와 리시버는 모두 반대로 리시버이자 센더이기도 하므로 그 segment의 헤더에는 항상 ACK number가 포함된다. 이때, TCP의 ACK number는 cumulative하다는 점은 go-back-N의 ACK(n) 메시지와 유사하지만 go-back-N의 경우와 달리 ‘0번부터 n-1번 메시지까지 모두 정상적으로 수신했으며 이제 n번 메시지를 기다리고 있음’이라는 뜻을 가진다.

  • checksum

2. TCP의 reliable data transfer

- TCP는 다음과 같은 방식으로 reliable data transfer를 구현한다.

(1) 센더는 여러 개의 메시지를 동시에 송신하며, 송신할 때 타이머를 하나 설정한다. 이때 설정하는 타이머의 시간은 다소 넉넉하게 잡는다.

(2) 리시버는 센더 측 메시지를 수신했다면 feedback으로 ACK(n)을 센더 측에 송신한다. (이는 0번부터 n-1번 메시지까지 정상적으로 수신했음을 뜻한다.)

  • 리시버 측은 selective repeat 방식과 유사한 버퍼를 사용한다. 즉, (a)메시지가 번호순대로 정상적으로 수신됐다면 application 계층으로 보내고 (b)번호순이 아닌 메시지가 들어왔다면 일단 이를 버퍼에 저장해 둔다.

- 만약 센더 측에서 메시지를 수백 개 전송한다 할 때, timeout이 일어나기엔 한참 오랜 시간이 남았는데도 불구하고 매번 리시버 측에서 메시지를 수신할 때마다 ACK(n)를 전송한다면 이는 불필요한 네트워크 자원 낭비가 일어나는 것이라고 볼 수 있다. 따라서 인터넷 표준(RFC)은 ACK 메시지를 충분히 긴 시간 간격을 두고 송신할 것을 권고하고 있다.

- fast retransmit: 만약 timeout이 발생하기 전에 메시지 유실을 알아챌 수 있는 방법이 있는데도 불구하고 timeout이 일어날 때까지 기다렸다가 재송신을 한다면 이는 시간낭비다. TCP의 feedback 메시지는 cumulative하므로, 리시버 측에서 sequence number가 똑같은 ACK 메시지를 여러 번 전송했다면 (굳이 timeout이 일어날 때까지 기다리지 않아도) 그 숫자의 메시지가 유실되었음을 알 수 있다. 이처럼 timeout 이전에 메시지 유실을 알아채고 센더 측이 메시지를 재전송하는 것을 fast retransmit이라 한다.

3. TCP의 connection management

1) connection 구축(3-way handshake)

- 센더와 리시버 사이 통신이 이뤄지기에 앞서 가장 먼저 양측은 각자 입력/출력버퍼를 각각 만들어주고 또 앞으로 어떤 sequence number부터 송신하게 될지 등의 문제를 서로 확인하는 작업이 필요하다. 이처럼 통신에 앞서 사전 준비를 거치는 것을 ‘connection을 구축’한다고 한다.

- TCP의 connection 구축은 다음 단계를 통해 이루어진다.

(1) 클라이언트가 서버를 향해 SYN 메시지(synchronize에서 따온 표현으로, connection 구축을 요청하는 메시지)를 전송하고, 이를 수신한 서버는 그에 대한 SYN/ACK를 전송한다.

  • 이 과정에서 클라이언트와 서버는 반대편을 향해 각자의 sequence number를 전송한다.

  • SYN 메시지는 세그먼트의 헤더의 SYN 부분 비트를 1로 설정하는 방식으로 표현한다. SYN/ACK 메시지는 세그먼트의 헤더의 SYN 부분 비트를 1로, ACK 부분 비트도 1로 설정하는 방식으로 표현한다.

(2) SYN/ACK를 수신한 클라이언트는 그에 대한 ACK를 전송한다.

  • SYN/ACK의 수신으로써 클라이언트는 (a)서버가 자신의 sequence number를 정상적으로 수신했다는 사실과 동시에 (b)서버의 sequence number를 알 수 있고, 이는 클라이언트가 TCP connection이 구축된 상태로 넘어가기 위해 필요한 모든 정보를 다 가진 셈이다.

  • 그러나 단순히 SYN/ACK를 송신하기만 했을 뿐인 서버는 아직 클라이언트로부터 아무 메시지를 받지 못했기 때문에, 클라이언트가 서버의 sequence number를 정상적으로 수신했는지 여부를 알 수 없다. 만약 클라이언트가 서버의 sequence number를 정상적으로 수신하지 못했다면 서버 입장에서는 다시 SYN/ACK를 보내야 하므로, 아직 이 상태만으로는 서버는 TCP connection이 구축된 상태로 넘어갈 수 없다.

  • 따라서 클라이언트는 SYN/ACK 수신 후 반드시 그에 대한 ACK를 서버에 전송해야 하며, 이때 서버는 이 단계에서 전송된 ACK를 수신함으로써 비로소 클라이언트가 서버의 sequence number를 정상적으로 수신했음을 알고 TCP connection이 구축된 상태로 넘어갈 수 있다.

  • 암벽등반가가 빌레이어(belayer, 안전로프를 잡고 있는 암벽등반가의 평지의 파트너)와 안전로프로 서로 연결돼 있음을 확인한 뒤에야 비로소 암벽등반을 시작할 수 있다는 사실로부터 3-way handshake의 각 단계의 필요성을 이해할 수 있다. 예를 들어 암벽등반가가 빌레이어에게 안전로프를 붙잡아달라고 요청을 하고(SYN) 빌레이어가 이에 응해 ‘붙잡고 있다’라고 답했을 때(SYN/ACK) 암벽등반가는 빌레이어에게 ‘네가 붙잡고 있다는 사실을 확인했다(ACK)’라는 의사를 전달해야 하는데, 만약 이를 전달하지 않는다면 빌레이어 입장에서는 ‘암벽등반가가 내가 안전로프를 붙잡고 있다는 사실을 아직 알지 못해 암벽 등반을 시작하지 못할 것’이라 볼 수밖에 없어 같은 이야기를 반복해 말할 수밖에 없기 때문이다.

2) connection 종료(4-way handshake)

- 센더와 리시버 사이 통신이 더 이상 이뤄지지 않는 경우 구축된 connection을 종료시켜야 한다. TCP의 connection 종료는 다음 단계를 통해 이루어진다.

(1) 클라이언트가 서버를 향해 FIN 메시지(connetion 종료를 요청하는 메시지)를 전송하고 FIN_WAIT_1 상태가 된다.

(2) FIN 메시지를 수신한 서버는 일단 그에 대한 ACK를 전송하고(이와 함께 서버는 CLOSE_WAIT 상태가 된다), 만약 클라이언트에게 그 전부터 보내던 데이터가 남아있다면 이의 전송을 마저 한다. 모든 전송이 끝나면 서버는 그때 클라이언트를 향하여 FIN 메시지를 전송하고 LAST_ACK 상태가 된다.

  • 3-way handshake에서는 서버가 SYN을 받은 뒤에 그 뒤의 응답 메시지를 한 번만(SYN/ACK) 전송하여 connection의 구축 과정을 마쳤지만, connection 종료에서는 서버가 ACK를 전송한 다음에 FIN을 추가로 전송한다. 이처럼 connection 종료는 3-way handshake보다 전송하는 메시지 개수가 하나 더 많기 때문에 이를 4-way handshake라고 한다. (이는 상기한 바와 같이 서버가 클라이언트로부터 FIN을 받은 시점에 서버는 아직 클라이언트에 보내야 할 데이터가 남아있을 수 있어 FIN을 받았다 해서 곧바로 자신도 connection 종료 상태가 될 수 없기 때문이다.)

(3) 클라이언트는 ACK 메시지 수신 후 FIN_WAIT_2 상태가 되며, 이후 서버로부터 최종적으로 FIN 메시지를 수신한 클라이언트는 그에 대한 ACK를 전송하고 TIME_WAIT 상태가 된다.

  • 클라이언트는 자신이 보낸 FIN 메시지에 대한 ACK를 수신하고 그에 대한 ACK를 전송한 이후에도 바로 connection 종료 상태가 되지 않고 TIME_WAIT 상태가 되었다 일정 시간이 경과한 후 그 다음에 비로소 connection 종료 상태가 된다. 이는, 만약 클라이언트가 전송한 ACK가 도중에 유실되어 서버가 이를 수신하지 못해 다시 FIN 메시지를 보낼 경우 그에 대한 ACK를 다시 보내줘야 하기 때문이다.

4. TCP의 흐름제어와 혼잡제어

1) 흐름제어(flow control)

- 센더에서 리시버 측으로 전송하는 메시지의 크기를 결정함에 있어 고려해야 할 첫째 요소는 ‘어떤 크기의 메시지를 전송해야 리시버 측이 충분히 받아들일 수 있는지’이다. 센더가 리시버에게 보낸 메시지는 일단 리시버 측 버퍼에 도달은 할 것이나, 만약 리시버 측 애플리케이션이 버퍼에 수용된 메시지를 읽어들이는 속도가 센더로부터 받아오는 속도보다 빠르면 리시버 측 버퍼가 이를 모두 받아들일 수 없어 버퍼 오버플로우(=메시지 손실)가 발생하기 때문이다. 이러한 일이 발생하지 않으려면 결국 센더 측에서 리시버 측이 버퍼에서 메시지를 처리하는 속도에 맞춰 메시지를 보내야 한다. 이처럼 센더 측에서 리시버 측의 메시지 처리 속도에 맞춰 메시지를 보내도록 하는 것을 흐름제어라 한다.

- TCP 명세에 따르면, 센더 측이 메시지 전송 시 리시버 측은 feedback을 하면서 이때 보내는 메시지에 리시버 측의 가용 버퍼 크기 정보를 담아 전송하도록 하며, 이를 수신한 센더는 이를 반영해 그 가용 버퍼 크기보다 적거나 그와 같은 크기의 데이터만을 전송하여 flow control을 수행한다.

  • 센더 측의 1byte 더미 데이터 전송: 이러한 방식으로 flow control을 할 경우, 만약 리시버 측 가용 버퍼 크기가 0이라면 센더 측은 이를 고려해 계속 메시지를 송신하지 않게 되는데, 이렇게 되면 센더 측은 영원히 리시버 측 가용 버퍼 크기가 늘어나는지 알 수 없는 문제가 발생한다. 이러한 문제에 대비하여, TCP 명세에서는 리시버 측 가용 버퍼 크기가 0이라 하더라도 센더 측이 일정 시간 간격으로 1byte 크기의 더미 데이터를 보내 리시버 측에 가용 버퍼 크기 정보를 요청해 얻도록 한다.

2) 혼잡제어(congestion control)

- 센더에서 리시버 측으로 전송하는 메시지의 크기를 결정함에 있어 고려해야 할 또 다른 요소로 ‘네트워크 망의 상태’가 있다. (1)네트워크 이동 경로 상의 라우터의 버퍼를 초과해서 메시지를 보내면 메시지가 유실될 수 있으며 (2)ACK 메시지가 리시버 측으로부터 전송되었으나 네트워크 상태 때문에 센더 측으로의 회신이 지연되고 있음에도 불구하고 time out이 발생해 자꾸 센더가 같은 메시지를 반복해 보내 네트워크 망 부하를 가중하는 경우가 생길 수도 있기 때문이다. 특히 후자의 경우 TCP에서 이를 고려하여 설계를 하지 않는다면 네트워크 망 부하로 메시지 수신이 지연되고 있는 것인데 네트워크 망에 부하를 가중하는 일이 발생하게 된다.

- 현재 네트워크 상태가 센더 측의 메시지를 얼마나 많이 리시버 측에 전달할 수 있는지를 네트워크를 구성하는 장치가 직접 파악하여 센더 측에 피드백을 줄 수 있다면 좋겠지만(network-assisted congestion control), 인터넷을 구성하는 라우터는 이런 기능을 제공하지 않는다. 따라서 인터넷의 경우 센더와 리시버가 상대편으로부터 오는 메시지의 내용과 RTT 등의 정보만을 가지고 네트워크 상태를 유추하여 다음 차례에 보낼 메시지의 양을 조절하는 수밖에 없다(end-end congestion control).

- congestion control은 보통 다음 세 단계로 이루어진다.

(1) slow start

  • 당장 현재 네트워크 상태가 어떠한지 알 수 없으므로, 일단은 아주 작은 양을 전송해보는 것에서부터 시작하여 그것이 제대로 도착했을 때 거기서부터 전송량(window의 크기)을 늘려나간다. 초기 전송량이 아주 작아 slow start라고 부르지만, 전송량을 느린 속도로 늘리면 네트워크 상태가 그 이상을 허용한다 하더라도 그에 다가가는 속도가 아주 느리므로 보통 전송량을 전 단계의 두 배씩 늘려나간다. 그래서 아주 빠른 시간 내에 네트워크 허용량의 한계치에 다가가는 경향이 있다.

  • congestion control에서 사용하는 데이터량 단위는 흔히 MSS(maximum segment size)라 하여, TCP 세그먼트 하나가 가질 수 있는 최대 데이터 크기(IPv4 기준 약 500B)를 사용한다.

(2) additive increase

  • 전송량이 네트워크가 허용하는 한계치에 가까워졌다고 보는 일정 기준치(threshold)를 넘어서면 전송량을 전 단계의 지수배로 늘려나가는 게 아니라 선형적으로 늘려가는 단계(additive increase)로 들어선다.

  • 초기 threshold를 정하는 방법은 따로 정해진 바가 없으나, 약 4KB 정도를 쓰는 경우가 많다.

(3) multiplicative decrease

  • 데이터 유실이 발생하면 데이터 전송량을 1MSS로 줄이고 threshold를 이전의 절반으로 줄이고 slow start 단계로 돌아간다(TCP Tahoe).

  • 데이터 유실이 일어나는 상황은 네트워크 상태가 나빠 전송한 메시지에 대한 ACK가 전혀 돌아오지 않는 경우(타임아웃)와 네트워크 상태는 정상이고 일부 패킷 유실만 일어난 경우(중복된 ACK 메시지가 3개 도착)로 나눌 수 있다. 전자의 경우라면 데이터 전송량을 1MSS까지 줄이는 게 맞겠지만, 후자의 경우라면 굳이 데이터 전송량을 1MSS까지 줄이는 것은 오히려 시간낭비다. 후자의 경우라면 threshold를 이전의 절반으로 줄이고 데이터 전송량을 그 threshold에서부터 다시 시작하는 게(=additive increase 단계) 더 낫다. 이러한 방식으로 congestion control을 하는 것을 TCP Reno라 한다.

5. TCP의 전송 속도

- TCP의 전송 속도는 \( {ACK\, 메시지\, 없이\, 보낼\, 수\, 있는\, 메시지\, 크기(=window의\, 크기)} \over {ACK\, 메시지가\, 돌아오는\, 데\, 걸리는\, 시간(=RTT)}\) 으로 정의할 수 있다.

- RTT는 네트워크 사정에 따라 크게 달라지지 않는 값이지만, window의 크기는 전적으로 네트워크 사정에 의존한다. 이로 보아, TCP의 전송 속도는 센더나 리시버의 요인이 아니라 오로지 네트워크 사정에 의해 결정된다는 것을 알 수 있다.

6. TCP의 네트워크 자원 배분의 공정성

- 하나의 네트워크 망에 여러 개의 TCP connection들이 있을 때, 어느 한 TCP connection이 망을 독점하여 다른 TCP connection들이 starvation 상태에 빠지거나 하지는 않을지 하는 의문이 있을 수 있다. 결론부터 이야기하면 그런 일은 일어나지 않으며 네트워크 망에 접속된 TCP connection들은 모두 공평하게 네트워크 자원을 배분 받는다. (단, 어떤 호스트가 지나치게 많은 TCP connection을 가지고 있어 그 호스트가 네트워크 자원을 상대적으로 더 많이 사용하는 경우가 있을 수는 있다.)

- 하나의 네트워크 망에 두 개의 TCP connection이 있는 모델을 통해 TCP의 자원 배분 공정성 문제를 이해할 수 있다. 예를 들어 1번 커넥션이 네트워크 자원을 80% 사용하려 하고 2번 커넥션이 네트워크 자원을 40% 사용하려 한다면 가용 네트워크 자원을 초과하므로 데이터 유실이 발생하게 되고, 그 결과로 각 커넥션은 네트워크 자원 사용량을 절반으로 줄이게 된다(multiplicative decrease). 그 다음부터 각 커넥션이 네트워크 자원 사용량을 점차 늘려나가다(additive increase) 다시 절반으로 줄이는 과정을 반복하게 되고, 최종적으로 각 커넥션의 네트워크 자원 사용량은 각 50%로 균형을 이루게 된다.