[네트워크] TCP의 flow control과 congestion control, fairness
TCP의 흐름제어 (Flow control)란?
cwnd는 무시하고 rwnd만을 고려해 window size W를 어떻게 조정하는지 학습한다.
LastByteRead(A) 호스트 B의 애플리케이션 프로세스에 의해 버퍼로부터 읽힌 데이터 스트림의 마지막 바이트 수
LastByteRcvd(B) 호스트 B에서 네트워크로부터 도착하여 수신 버퍼에 저장된 데이터 스트림의 마지막 바이트 수
LastByteRcvd - LastByteRead ≤ RcvBuffer
rwnd=RcvBuffer(4096 바이트) - (LastByteRcvd - LastByteRead)
즉, lastByteRead lastByteRcvd 의 차이로 가용한 버퍼 사이즈를 알 수 있음 → rwnd 필드에서 sending rate (inflight) 조절하는 것이다. flow control은 버퍼 잔여량을 알려준 후, rwnd 필드의 버퍼 잔여량 만큼만 넣음으로써 sender의 sending rate를 control하는 것이다.
데이터를 빨리 보낸다고 좋은 것이 아니다. 아무리 빨리 보내도 수신자가 처리할 수 없을 정도로 빠르면 결국 처리할 수 없기 때문이다.
A에서 B로 데이터를 전송할 때, sender는 데이터의 전달 속도를 마음대로 조절할 수 있다. 그러나 유의미한 결과를 내려면 B가 받을 수 있는 속도로 전송해야 한다. 모든 호스트는 send 버퍼와 receive 버퍼를 가진다.
예를 들어 A가 10바이트를 전송하려고 하는데 B에 recv 버퍼에 1바이트밖에 빈 공간이 남지 않았다면 수신할 수가 없다. 따라서 TCP에서는 이를 해결하기 위해 피드백시 헤더에 receive 버퍼의 크기를 기록하여 전송한다.
만약 수신 측의 receive 버퍼가 꽉 찼을 경우 송신하는 측에서는 데이터를 전송하지 않는다. 이렇게 전송자가 너무 많이 빨리 전송하여 수신자의 버퍼가 넘치는 일이 없도록 하는 것을 흐름제어(flow control)이라고 한다. 해당 기능은 recieve 버퍼의 크기를 기록하는 간단한 방법으로 구현할 수 있다. 이는 전송량을 제한함으로써 전송속도를 낮추는 것이다.
동작원리는 간단하다. sender 측시 reciever 측의 receive 버퍼의 크기보다 더 많이 보낼 수 없게 하는 것이다.
다시 TCP 세그먼트 구조를 보면, Receive window를 볼 수 있는데 이것이 바로 현재 남은 receive 버퍼의 크기를 가리킨다.
A에서 B로 데이터를 보낸다고 하자. B의 버퍼에서 이용가능한 공간이 2바이트 남았다면 A는 2바이트만 전송 가능하다.
그런데 여기서 문제가 생긴다. B의 버퍼에 공간이 하나도 없으면 A는 데이터를 전송할 수 없다. 그러나 A가 아무런 메시지로 보내지 않으면 언제 B의 버퍼가 비는지 알 수 없다. B에 메시지를 보내고 ACK이 돌아올 때, receive 버퍼의 상태를 헤더를 통해 알 수 있는데, B에서 메세지가 오지 않으면 A는 B의 receive 버퍼의 상태도 알 수 없기 때문이다.
이때, 남은 버퍼의 상태를 어떻게 알릴 수 있을까?
TCP에서는 sender가 정기적으로 더미 데이터를 보낸다. 아무 의미가 없는 1바이트짜리 데이터를 보내는데, 이렇게 세그먼트를 전송하여야 피드백을 통해 receiver의 버퍼에 이용가능한 공간이 얼마나 남았는지 알 수 있기 때문이다.
그렇다면 버퍼간의 연결설정은 어떻게 하는 걸까?
TCP 통신과정에서는 in-order과 양방향 통신을 위해 seq # 가 필요하다.
따라서 A의 sender 버퍼에서 데이터를 전송하면 B의 receive 버퍼가 데이터를 받는다. 따라서 A의 sende 버퍼의 seq #는 B의 receive 버퍼의 seq #와 같다. 이 반대도 성립한다.
네트워크 통신에서 이러한 과정이 자연스럽게 이루어지기 위해서는 무작정 메시지를 회선을 따라 보내기만 하면 되는 것이 아니라, 버퍼를 만들어주고 서로 상대에게 seq #를 알려주는 작업이 필요하다. 이러한 준비작업이 이루어져야만 네트워크 엣지 간에 정상적으로 통신이 이뤄진다. 이러한 준비 작업을 하는 것이 "연결과정(connection)"으로 TCP에서 sender와 receiver는 데이터 세그먼트를 주고받기 이전에 연결을 구축해야 한다.
TCP가 사용하는 ( send ) buffer는 수신 TCP가 순서에 맞게 segment들을 받았는지 확인될 때까지 보낸 segment를 저장하고 있다가 필요시 재전송을 위해 필요하며, ( receive ) buffer 는 sender TCP가 data를 보내는 속도와 상위 어플리케이션이 그 data를 가져가는 속도의 차이를 보완하기 위해 사용된다.
TCP 2-way Handshake하면 안되는 이유는?
TCP 3-way Handshake(연결 시작)란?
TCP에서는 client와 server 간에 연결을 구축하기 위해 3-way handshake 방법을 사용한다. 3-way handshake는 이름 그대로 세번 악수하는 과정으로 연결을 구축한다. 그 세 과정은 다음과 같다.
1) SYN: 클라이언트가 서버에 연결을 요청
클라이언트의 Seq #을 서버에 알려준다. SYN 메세지를 보낸다는 것은 TCP connection을 열기를 요청하는 것이다.
-> 헤더의 SYN 신호를 이용
2) SYN/ACK: 서버가 클라이언트에게서 SYN을 받고 SYN/ACK 세그먼트를 전송
서버의 Seq #를 클라이언트에 알려준다. 이 과정에서 서버가 버퍼를 만든다.
-> 헤더의 SYN과 ACK을 이용
3) ACK: 클라이언트가 SYN/ACK을 받고 서버에 ACK을 전송
이 과정에서는 클라이언트가 서버에 실제 데이터를 담아 보낼 수 있다. 클라이언트가 ACK을 한번더 보내줘야 하는 이유는 서버가 ACK이 없으면 메시지가 전송되긴 했지만 정상적으로 전송되었는지 모르기 때문이다.
TCP 4-way Handshack(연결 끊기)란?
1) FIN: 클라이언트가 서버에게 FIN을 보내 연결 종료를 알린다.
2) ACK: 서버는 클라이언트의 연결 중단 요청을 확인하고 피드백으로 ACK을 보내준다.
3) FIN: 이번엔 서버가 클라이언트에게 FIN을 본 연결 종료를 알린다. 이때 클라이언트는 FIN을 받고 잠시동안 timed wait 상태가 된다.
4) ACK: 클라이언트는 서버의 연결 중단 요청을 확인하고 피드백으로 ACK을 보내준다.
클라이언트가 timed wait 상태인 동안에는 연결 종료를 통지받고도 한참동안 종료되지 않는 이유는, 서버에서 전송한 FIN이 제대로 도착한다는 보장이 없기 때문이다. 문제가 생겨 timeout이 발생하면 서버는 클라이언트에게 다시 FIN을 보낸다.
이때 클라이언트가 닫혀있으면 안되므로 time wait를 하는 것이다.
TCP의 혼잡제어(Congestion control)이란?
rwnd는 무시하고 cwnd만을 고려해 window size W를 어떻게 조정하는지 학습한다.
cf. rwnd는 receiver의 버퍼 여유공간을, cwnd는 중간 라우터의 버퍼 여유공간을 의미한다.
만약 A에서 B로 데이터를 전송하면, 데이터는 바로 상대에게 날아가는 것이 아니라 네트워크를 거치게 된다. 라우터마다 수용할 수 있는 큐의 길이가 정해져있기 때문에, 데이터를 보낼 때는 상대의 버퍼 상태뿐만 아니라 네트워크의 상태(중간 라우터 버퍼)도 고려해야한다.
만약 라우터의 버퍼가 무한하다면 queueing delay가 무한히 증가하지만 loss는 발생하지 않는다.하지만 유한한 버퍼 안에서 sender timeout이 고정된 채 버퍼 사이즈만 늘린다면, loss는 감소하고 queueing delay는 증가하며 불필요한 재전송이 증가한다.
데이터는 얼마까지 보낼 수 있을까? 버퍼, 네트워크마다 한계가 있다면 어느 쪽에 맞춰야 하는가?
네트워크와 receiver 버퍼 중 receiver 버퍼의 상태가 나쁘다면 receiver 버퍼에, 네트워크 상태가 나쁘다면 네트워크에 전송 속도를 맞추게 된다.
즉, 둘 중 상태가 안좋은 쪽에 전송 속도를 맞추어야 한다.
역기서 receive 버퍼의 상태는 흐름제어(Flow control)를 통해 이미 알수 있다. 그러나 흐름제어는 네트워크 상태를 알 수 없는데, 혼잡제어(Congestion control)가 바로 네트워크의 상태를 확인하고 전송속도를 조절하는 역할을 하게 된다.
혼잡제어가 하는 일은 뭘까?
기본적으로 네트워크는 공공의 것으로 소유자가 없다. 누구도 소유하고 있지 않기 때문에 따로 제어하지 않으면 각 기기들은 자유롭게 네트워크를 사용하다가 데이터를 최대한 많이 보내게 된다. 그러나 데이터가 너무 많이 보내지면 문제가 발생한다. 라우터에는 한계가 있기 때문에 데이터 전송량이 많아지면 네트워크의 상태가 안좋아지고 막히게 된다. 데이터 전송이 원활하지 않게 되는 것이다.
여기서 문제점은 TCP는 '데이터 전송이 잘 안되면 재전송을 한다'는 점이다. TCP는 데이터에 오류가 발생하거나 타임아웃이 발생하면 데이터를 재전송한다. 네트워크 상태가 악화될수록 재전송이 잦아져서 더 많은 데이터를 보내고, 상태를 더 악화시키게 된다. 이를 위해 네트워크가 막히지 않게 TCP는 독립적으로 동작하되 서로 양보해주는 경향을 보인다. 네트워크 상황이 안 좋으면 데이터 전송량을 줄이고, 좋으면 전송량을 늘려준다.
이러한 일을 하는 것이 혼잡제어(Congestion control)이다.
혼잡이 발생하는 원인은 뭘까?
1. Link capacity = transmission rate (R)
호스트 A가 라우터를 통해 호스트 B로 메시지를 보내려 할 때
- λin = 애플리케이션이 데이터를 소켓으로 보내는 sending rate (데이터의 양)
- λin = 트랜스포트 계층으로부터 네트워크 안으로 보내는 세그먼트의 sending rate (데이터의 양 재전송 데이터) ⇒ λin = λin + retx ( λ`in > λin )
라우터로 들어오는 데이터의 양이 R, 라우터 output buffer의 transmission rate이 R이면 부하가 발생하지 않음
⇒ 라우터 output buffer의 transmission rate이 라우터의 수신율 보다 작다면 혼잡이 발생할 수 있다. 따라서 link capacity (transmission rate)이 문제가 될 수 o
2. Finite buffer at router
유한한 버퍼를 가진 라우터
- 만약 라우터의 버퍼가 무한하다면? Queueing delay가 무한히 증가할 것
but, loss는 발생하지 않음 (loss는 버퍼 size가 유한하기 때문에 발생) - 유한한 버퍼, sender의 timeout이 고정된 채 버퍼의 사이즈만 늘렸다면?
loss는 감소하고, Queueing delay는 증가할 것, 이 때 timeout 값이 고정되어 있
다면 (premature timeout) unnecessary retransmission 이 증가할 것
결과적으로 throughput이 감소하게 됨
3. multi-hop path
4개의 sender와 유한 버퍼를 가진 라우터, multi-hop 경로 / A → C, D → B 일 때
그렇다면 네트워크 상황을 어떻게 확인할까?
단순하게 생각하면 1) 직접 자세하게 데이터를 보내주거나 2) 간접적으로 네트워크 상황을 유추하는 것이 그것이다.
1. Network-assisted congestion control(L2~L3)
첫 번째 방법은 네트워크에서 직접 데이터를 보내주는 것이다. 라우터에서 직접 큐 상황 등을 데이터로 보내준다. 그 정보를 통해 각 End-system은 전송량을 제어한다. 하지만 라우터는 이미 많은 일을 하고있기에 이 방법은 좋지 않다.
2. End-end congestion control(L4)
이 방법은 간접적으로 네트워크 상황을 유추한다. 우리는 ACK 피드백이 돌아오는 데에 걸리는 시간인 RTT를 알고 있다.
RTT가 예상보다 짧다면 네트워크 상황이 좋은 것이고, RTT가 길거나 아예 유실이 일어나버렸다면 네트워크 상황이 좋지 않는 것이라 볼 수 있다.
만약 이렇게 네트워크 상황을 확인했다면 sender 버퍼에서 Window Size를 적당히 정하여 속도를 조절할 수 있다.
혼잡제어는 어떻게 구현될까?
혼잡제어 과정은 3 main phases로 구성된다.
처음에는 아주 느린 속도로 시작해서 네트워크 병목 현상이 일어날 때까지 계속해서 속도를 올려가는 식으로 전개된다.
1. Slow Start: 작은 값부터 시작
병목현상이 일어나는 대역폭을 알 수 없기에 아주 작은 값부터 시작해서 전송속도를 증가시킨다. 다만 증가할 때는 1,2,4,8,16 MSS과 같이 윈도우 사이즈를 지수배로 증가한다.
2. Additive increase: 천천히 속도를 높임
네트워크에는 임계점(threshold)이 존재해서 그 이상 데이터를 전송할 땐 주의해야 한다. Slow start 과정에서 임계점에 도달하면 Additive increase로 전환하여 16,17,18,19 MSS와 같이 윈도우 사이즈를 천천히 늘린다.
3. Multiplicative decrease: 병목 현상이 발생하면 절반으로 줄인다.
한번에 전송량을 확 줄이는 이유는, 네트워크가 공유자원이므로 패킷 1~2개를 줄이는 것으로는 혼잡 상태를 해소하기 어렵기 때문이다.
-> 윈도우 사이즈를 줄인 다음에는 다시 2번 Additive increase 단계로 돌아가서 전송량을 늘린다.
혼잡제어에서 전송하는 데이터량은 MSS를 단위로 한다.
cf. MSS는 max segment size의 약자로 TCP 세그먼트 하나가 가질 수 있는 최대 데이터 크기(바이트)를 가리킨다.
TCP에서는 데이터 전송량이 증가하다가 패킷유실이 발생하면 Congestion Window size가 절반으로 줄어들게 된다. 그러다가 네트워크가 괜찮아지면 전송량이 또 증가한다.
TCP 혼잡 제어 상태 : 각각은 어떻게 작동합니까?
- AIMD (덧셈 증가 곱셈 감소)
- SS(슬로우 스타트) -> double cwnd in every RTT
- CA (혼잡 회피) -> loss가 생길 경우, ssthresh를 혼잡이 생긴 시점의 cwnd 값의 절반
- ACK를 수신할 때 송신 TCP는 AIMD 및 CA에서 RTT당 cwnd += 1 MSS를 수행하는 반면 송신 TCP는 SS에서 RTT당 cwnd x=2(=ACK당 1MSS)를 수행합니다.
TCP Tahoe vs TCP Reno
TCP Tahoe는 80년대에 등장한 최초의 TCP 혼잡 제어 알고리즘이다.
Tahoe는 문제점을 내포하고 있어서 그 이후에 TCP Reno가 나오게 되었다.
TCP Tahoe
위 그림에서 파랑선이 바로 TCP Tahoe의 동작 방식을 가리킨다. Tahoe는 slow start(SS)로 시작해서 임계점을 넘으면 선형적으로 증가하다가 패킷유실이 발생하면 Congestion Window size가 무조건 1MSS로 감소한다. 이후 초기상태로 돌아와 다시 slow start부터 시작한다. 이때 임계점도 바뀌는데 유실이 발생한 지점의 절반이 된다. 위의 경우 12 MSS에서 유실이 발생했으므로 임계점은 12/2=6이 된다.
TCP Reno
Reno를 설명하기 위해서는 패킷이 유실을 알 수 있는 상황이 하나가 아니라는 점을 알아야한다.
1) 타임아웃 발생
피드백이(ACK)이 돌아오지 않아 타임아웃이 발생하는 케이스로 네트워크 상태가 상당히 안좋은 셈이다.
2) 3 dup ACK
복제된 ACK가 세 번 도달하는 것을 3 duplicate ACK라 한다. 빠른 재전송에 의해 패킷유실을 감지한다. 해당 패킷만 문제가 있고 네트워크 상태는 정상이다.
기본적으로 타임아웃이 발생할 때가 3 dup ACK보다 네트워크 상황이 나쁘다. 따라서 Reno는 패킷 유실 판별 조건에 따라 다르게 행동한다. 3 dup ACK이라면 상황이 좋으므로 Congestion Window size를 절반으로 내린다. Timeout이라면 Tahoe와 동일하게 Congestion Window size를 1MSS로 내린다. 임계점은 Tahoe와 동일하게 작동한다.
TCP Faireness, Throughput을 결정하는 bottleneck에서 banswidth를 얼마나 공정하게 share하고 있을까?
TCP Faireness는 개의 TCP session이 대역폭 인 같은 병목 링크를 공유할 때, 각 연결의 전송률은 가 되야 한다는 것이다. 이는 AIMD를 통해 두 경쟁하는 TCP가 공정하게 전송률을 가지도록 한다.
connection 1이 connection을 더 먼저 시작했을 수도, cwnd가 훨씬 커 bandwidth를 더 많이 차지하고 있는 상황으로 connection 2의 cwnd가 점점 증가하면서 파란 선(R)을 넘고 loss가 생긴다.
loss가 생기면 각 cwnd를 loss가 생긴 시점의 반으로 줄이기 때문에, 결국 두 connection은 R/2만큼의 bandwidth를 쓰게 된다. 모두 TCP connection을 사용하며 경쟁하는 bottleneck link와 host사이의 # of RTT (hops)가 똑같다고 가정하면, 그 TCP connection들은 경쟁하는 R값을 공평하게 나눠 갖게 된다.
공정하지 않은 경우는 언제?
- Not fair because of UDP
UDP도 그 link를 지나감. 버퍼 오버플로우시 TCP는 보내는 데이터 양을 절반으로 줄이나 UDP는 congestion control을 하지 않기에 그렇지 않다.
→ TCP와 UDP가 같은 경로를 공유할 때, 만약 경로가 busy해 output buffer에서 congestion이 발생하면 TCP만 데이터 양을 줄임, 남은 bandwidth를 UDP가 쓰게 됨 → TCP가 불리
- Unfair allocation of R due to parallel TCP connections
TCP connection을 쓰는 HTTP는 baseHTML을 가져온 후 object만큼을 더 가져와야 한다.
이 때, 3가지 종류의 TCP connection을 사용할 수 있음. non persistent with parallel로 연결을 한다면, TCP connection을 동시에 여러 개 열 수 있는 어플리케이션들이 있기 때문에, 특정 bottleneck을 보면, TCP 입장에서 봤을 땐 공평하나(모든 소켓은 공평하게 bandwidth를 나눠 가짐), 5계층의 어플리케이션 입장에서 봤을 땐 공평하지 않음(어떤 app은 11개 사용, 어떤 app은 9개 사용)
- TCP CUBIC
- Link의 성능이 향상되고 가용 가능한 대역폭이 늘어나면서, TCP Reno가 이를 효율적으로 활용하지 못하게 됨.
- cwnd를 절반으로 줄이고 Additive 하게 증가하는 방식은, cwnd를 복구하는데 지나치게 많은 시간이 소요된다는 문제가 있다.
- 패킷 손실 발생시, cwnd를 에 저장하고 multiplicative하게 cwnd를 줄임. 그 후 cwnd를 매우 빠르게 증가시키고, cwnd가 에 가까워지면 증가 속도를 줄이고, cwnd가 에서 멀어지면 증가 속도를 가속화한다.
- 애플리케이션의 관점에서 TCP 공정성을 달성할 수 없습니다
- 네트워크에서 UDP 및 TCP 사용자가 혼합된 경우.
- 일부 응용 프로그램이 여러 병렬 TCP 연결을 여는 경우.
- HoL(Head-of-Line) 차단에는 두 가지 종류가 있습니다
- (type1: HTTP로 인해 발생) 작은 물체는 이전에 머리에 전달된 큰 물체로 인해 표시가 지연됩니다.
- (유형 2: TCP로 인해 발생) 순서가 잘못된 배달 개체는 손실된 메시지가 다시 전송될 때까지 TCP의 수신 버퍼에서 지연됩니다.
- HTTP의 역사
- (type1)과 (type2)는 모두 TCP를 통해 실행되는 HTTP/1.1에서 발생할 수 있습니다.
- TCP를 통해 실행되는 HTTP/2는 (type1) HoL 차단을 해결하지만 여전히 (type2) HoL 차단을 겪습니다.
- (UDP를 통해 QUIC를 통해 실행되는 HTTP/2) 또는 HTTP/3은 두 가지 유형의 HoL 차단을 해결합니다.
- UDP를 사용하는 QUIC(즉, 애플리케이션 계층 프로토콜)를 통합하는 HTTP/3
- 하나의 QUIC 연결에서 각 객체를 별도의 스트림 (다른 스트림 ID를 가진)으로 전달합니다.
- 즉, 스트림(객체) 당 rdt 서비스(no-loss 및 in-order deivery)를 수행한다.
- 따라서 HTTP/3은 (type1) 및 (type2) HoL을 모두 차단합니다.
거리가 길수록 TCP속도를 더 빨리 올리니, 거의 서버에 다오면 TCP를 느리게 위험해서 k를 잘 조정해야한다. -> 요즘 쓰는 방법
(1) Sender TCP 가 segment를 보내놓고 retx 시점을 결정하기 위해 사용하는 타이머의 timeout 값은 네트워크의 실제 RTT 값을 사용한다.
⇒ T. 실제 RTT값을 측정해 timeout 값을 조정함
(2) Sender TCP의 타이머는 가장 최근에 보낸 segment의 첫번째 바이트를 가리키고 있다.
⇒ F. 가장 마지막으로 ACK을 받은 segment의 다음 segment (첫번째 inflight segment)
가장 오래전에 보낸 segment의 첫번째 바이트. 즉, Sendbase에 타이머가 달려있다.
(3) Sender TCP가 3 Dup ACK으로 loss를 판단한 경우가 timeout으로 loss를 판단한 경우 보다 네트워크 상황이 더 나쁘다.
⇒ F. 반대, timeout으로 loss를 판단한 경우가 네트워크 상황이 더 나쁘다.
(4) 슬라이드 #30에서와 같이 호스트 B가 79번째 byte로 시작하는 segment를 보내면서 동시에 ACK number = 43을 함께 보내는 방식을 뭐라고 하는가?
⇒ Piggyback
(5) TCP가 사용 되는 망에서 application user가 느끼는 서비스 불공정성 두 가지 예를 설명하시오.
⇒
(1) UDP와 TCP가 bottleneck link를 공유하는 경우 TCP는 congestion control을 위해 전송율을 낮추는 반면, UDP는 응용이 내려보내는 속도 그대로 전송하므로 UDP user가 TCP user보다 해당 링크 대역폭을 더 많이 사용하게되는 불공정성이 있다.
(2) Bottleneck link를 TCP user만 사용하는 경우에도 응용이 TCP connection을 non-persistent with parallel 옵션으로 동시에 다중의 TCP 소켓을 연다면 persistent TCP connection을 여는 응용보다 어느 시점에 더 많은 링크 대역폭 사용하게된다.
(6) 호스트 A에서 일정 기간 동안 5계층 응용 프로토콜이 전송하는 메시지 량이 100Mbit였다고 가정할 때 UDP를 사용하는 경우보다 TCP를 사용하는 경우 라우터로 실제 전송된 데이터량은 100Mbit를 초과할 수 있다. 그 이유는 무엇인가?
⇒ TCP는 loss 발생 시 retransmission을 지원하므로, 라우터로 실제 전송된 데이터는 애플리케이션 계층에서 보내준 데이터와 재전송되는 데이터를 포함한다.
TCP는 send buffer를 두고 네트워크에서 loss 발생시 retx을 하므로 실제 5계층에서 응용 프로토콜이 전송하고자 하는 데이터 보다 더 많은 량의 데이터를 네트워크로 전송하게 된다.