지난 포스트에서 unreliable channel에서는 어떤일이 생기는 지 알아보았다. packet error, packet loss 등이 생길 수 있었고 Packet error는 Error detection, feedback, retransmission, sequence#로, Packet loss는 Timer로 해결할 수 있음을 알았다.
그래서 완성된 것이 rdt3.0이나, 실제 TCP는 이것보다 매우 복잡하다. 그렇다면 이것을 실제로 사용할 수 있는지 성능을 생각해봐야할 것이다.
전체 네트워크 사용량 중에 sender가 사용하는 비율이 크면 클수록 좋다. 왜냐하면 고속도로 16차선 중 차가 모두 달려야 좋은 것이지 차가 한 대만 달리고 있다면 효율적이지 않다. 따라서 우리가 설계한 rdt3.0도 패킷을 하나씩만 주고 받으므로 효율적이지 못함을 알 수 있다.
따라서 우리가 지향해야할 것은 아래의 그림 중 오른쪽 그림이라고 할 수 있다. 한번에 쏟아내듯이 보내는 것이다.
이러한 신뢰성있는 파이프라인을 구성하기 위한 approach로 go-Back-N, selective repeat이 있다. 실제로 TCP가 이 두 방식중 하나를 따를 것이다.
가장 중요한 것은 이제는 한꺼번에 많은 패킷을 쏟아낸다는 것이다. 그렇다면 한꺼번에 얼마나 많은 패킷을 보낼것인지에 대한 기준이 있어야한다. 이 기준이 window이다. 따라서 window 사이즈 만큼은 feedback 받지 않고 그냥 보낼 수 있는 것이다.
만약 패킷이 0번부터 12번까지 순서대로 전송해야한다하고, window가 4라면 0,1,2,3까지는 그냥 보낼 수 있는 것이다 .
ACK(n) :
ACKs all pkts up to, including seq # n - "cumulative ACK"
ACK(11)은 11번까지 ACK로 잘받았다. 그러니 12번이제 내놓으란 얘기다. 즉 축적된 ACK이다.
각각의 패킷엔 timer가 달려있고, feedback 받기 전에 timer가 터진다면 재전송한다. 예를 들어 0,1,2,3을 전송하는데 0에서 timer가 터졌다면 윈도우에 있는 0,1,2,3 모두 재전송한다.
timeout(n): retransmit pkt n and all higher seq # pkts in window
receiver가 정말 멍청해서 자기가 받아야할 sequence number만 주구장창 기다리는 것이다. 만약 reciever가 0번을 기다리고 있다가 받았다면, 이걸 application layer에 넘겨 준다. 이후에 1번을 이제 기다리며 잘 받은 0과 ACK를 보낸다. 만약 sender가 1,2,3을 그 후에 보냈는데 2,1,3순으로 도착했다고 가정하자. 그럼 receiver는 2가 왔을 때 기다리던 1번이 아니므로 2를 버리고 ACK 0을 반환한다. 그 다음 1이 도착했을 때 받아서 ACK 1을 보낸다. 이후에 또 2번을 기다리는 것이다.
window size = 4이다. 따라서 0,1,2,3을 쏟아부어 보낸것이다. 이때 각 패킷에 timer가 붙어있는다. 0번을 보내서 받았다면 timer 제거 후 윈도우가 전진해 4를 보낼 수 있다. 그림에선 1번도 잘 주고 받았다. 따라서 윈도우가 전진해 5번까지 보낼 수 있는것이다. 중간에 2번이 loss 된 것을 볼 수 있다. 그렇다면 그 다음에는 계속 ACK 1이다. 2번을 받지 못했기 때문에 그 이후에 도착한 3,4,5를 받지 못해 버린 후 ACK 1을 계속 반환한다. 그러다가 시간이 지나 2번 timer가 터지면 2번 위의, window 내에 있는 패킷들이 모두 재전송된다.
만약에 sender와 receiver 사이에 아무 error도 일어나지 않아 평온하다면, window는 계속 전진할 것이다. 하지만 만약 중간에 6번이 없어져 유실이 생겼다면 ACK가 5까지 제대로 되고 6이 유실되었으니 ACK 5가 7,8,9에서 또 반환될 것이다. 그리고 6의 timer가 터져 6,7,8,9가 재전송이 된다. 유실되면 N개만큼 돌아와서 다시 재전송한다고 해서 GO-BACK-N인 것이다.
window란 단순히 한꺼번에 보낼 수 있는 단위를 얘기하기도 하지만 윈도우안에 있는 패키지들은 버퍼에 저장하고 있어야한다. 윈도우 안에 있다는 것은 아직 receiver가 받았는지 못받았는지 확인하지 못했다는 것이다. 따라서 재전송을 위해서 버퍼에 저장해 놓고 있어야 한다.
단점은 그러면 무엇일까? 위의 예시에서 살펴보듯이 유실된건 6번 하나인데 그 뒤에 도착한 7,8,9가 버려지고 6,7,8,9 모두 재전송되었다. 예시로 들었던 것은 window의 크기가 4였지만, 실제로는 window가 훨씬 클 것이다. 이런 경우에선 하나가 유실 되었다고 해서 그 많은 패킷을 재전송 하는 것은 효율성이 떨어진다.
그래서 receiver가 조금 더 발전한 버전이 Selective Repeat이다.
문제가 있을 때 재전송을 해주되 selective하게 재전송해주는 것이다. 이렇게 Selective하게 재전송하기 위해서는 ACK의 의미가 위에서와는 반대일 것이다. ACK7이면 7번 받았으니 timer 해제를 한다. 물론 앞의 5,6 등이 무사히 도착하지 않아도 된다. 이 접근법에서는 receiver의 역할이 생긴다.
receiver가 2번 패키지를 기다렸지만 3번을 받았다면 3번을 버퍼에 저장하는 것이다. 2번을 받을 때 까지. 대신, 3번 받았을 때 ACK를 해주는 것이다.
이번에도 window 크기를 4라고 가정하자. 0,1,2,3을 한꺼번에 보낸다. 0을 받아 application layer에 올려주고 ACK 0을 해준다. 1번도 똑같다. 하지만 2번이 유실되고 3번이 전달 된 것을 볼 수 있다. 이때 3번을 버리는 것이 아니라 buffer에 저장하고 ACK3을 보낸다. 만약 Go-Back-N이었다면 3번을 버리고 ACK1을 보냈을 것이다. 결론적으로 ACK3을 보냈으므로 3번에 있던 timer는 제거한다. 이를 window size내의 5번까지 진행하고 이후 2번 timer가 터지면 2번만 재전송한다. 지금 buffer에는 3,4,5가 저장되어있다. 따라서 2번이 제대로 도착하면 2,3,4,5를 application layer에 올려주고 ACK2를 반환한다.
따라서 유실된 패킷인 2번만 재전송하는 방법이다.
유실된 패킷만 전송하기 때문에 네트워크에 부담이 덜 되는 대신 receiver가 조금 더 일해야한다.
sequence number에 대해 생각해보자. packet의 sequence number가 계속 늘어난다면 이것은 결국 header에 field로 들어가므로 작으면 작을 수록 적다. 따라서 최소한의 범위의 sequence number를 사용해야한다. 만약 window가 3인 경우에 sequence number의 범위를 어떻게 사용해야할까? sequence number를 4개 정도 사용하면 되지 않을까?
위의 그림을 보면 ACK가 모두 유실된 것을 볼 수 있다. 그렇다면 window에는 0,1,2가 아직 있을 것이다. 그럼 0,1,2의 timer가 터지고 0,1,2가 재전송될 것이다. 그렇다면 receiver는 3을 받아야하지만, 3뒤가 0이므로 0을 받아 저장할 것이다. 하지만 실제로는 중복된 패킷이다. 따라서 window size가 N일 때 sequnce number가 N+1이면 안된다는 것을 알 수 있다. 그렇다면 sequence size를 늘려야하는데, window size와의 관계에서 최소한의 sequence size는 뭘까?
selective repeat의 단점은 모든 패킷에 timer를 다는 것이다. 동시 실행되는 프로세스가 많은 모두 TCP connection을 가지고 있는데 모두 timer를 달고 있다는 건 말이 안된다.
TCP는 Selective repeat과 Go-Back-N의 장점만 섞어 만든 프로토콜이다.
one sender, one receiver
프로세스 한쌍 끼리의 통신을 책임지는 것인데 엄격하게 말하면, socket 한쌍 끼리의 통신을 책임지는 것이다. 프로세스 하나에 여러 소켓이 있을 수 있기 때문이다.
실제로는 모두가 sender이면서 receiver이다.
buffer의 크기는 window크기이다. (sender에선 재전송을 위해, receiver에선 순서가 바르게 오지 않은 패킷들을 저장해놓기 위해서) 하지만 sender이면서 receiver이므로 각자 buffer를 두개씩 가지고 있는 셈이다.
no "message boundaries"
TCP congestion and flow control set window size
bi-directional data flow in same connection
handshaking (exchange of control msgs) init's sender, receiver state before data exchange
sender will not overwhelm receiver
receiver의 소화능력에 맞게 보내는 것이다.
application layer의 전송단위는 message이다. 즉, Http request, reponse는 모두 message이다. 편지지에 적힌 말들이라고 생각하면 된다. 따라서 socket, interface를 통해 TCP와 소통하게 된다. TCP의 전송단위는 segment이다. 편지 봉투에 적힌 부가적인 정보가 header이다.
segment는 header + message로 구성된다.
TCP에선 우편배달부 역할을 하므로 message에 무슨 내용이 있든 아무 상관없다. 즉, header에 무슨 내용이 적혀있는지가 중요하다. 위의 그림에서 application data와 header의 비율이 반반이지만 실제로는 data가 훨씬 많은 비중을 차지 하며 header는 오버헤드이므로 적을수록 좋다.
각각 포트 번호가 16비트임을 알 수 있으며 이는 포트 범위가 0 ~ 2^16-1임을 나타낸다. 이론상으로 한 컴퓨터내에서 동시에 동작할 수 잇는 네트워크 애플리케이션의 수가 0 ~ 2^16-1라는 것이다.
source port는 보내는 사람에 해당하는 포트이다. checksum은 네트워크로 넘어오는 동안 error가 있었는지 error detection을 하기 위해 있는 것이다.
Receive window : 내 receive buffer에 얼마만큼 빈공간이 있는지이다.
A가 client, B가 server라고 하고 echo server라고 하자. 그림을 통해 데이터 교환이 TCP를 통해 이루어질 때, seq#와 ACK는 어떻게 사용되는지 알 수 있다.
C라는 문자(1 byte)를 보내려고 한다. 처음엔 application layer에서 socket을 통해 transport layer로 내려와 segment를 형성한다.
TCP에서 사용하는 sequence number는 제일 첫 byte의 순서번호이다. 예를 들어 메세지가 100byte짜리라면 9번까지를 한덩어리로 10byte를 TCP에 내리면 sequence number는 제일 앞인 0이다. 그렇다면 두번째 덩어리는 10번 부터 19번까지 10byte를 또 내리는데 이때 sequence number는 10이다.
ACK
TCP에서 ACK10은 9번까지는 완벽하게 받았으니 10번 내놓아라 하는 이야기이다.
따라서 위의 그림에서는 send buffer에 C가 있고 이 C의 byte 번호가 42번인 것이다. 또한 ACK가 79인 것은 78번까지 받았으니 79를 받아야한다는 것이다. B는 그럼 42번을 받았으니 43번을 받을 차례다. 따라서 ACK 43을 보내는 것이다.
만약 전달하는 문자가 C가 아닌 Computer라고 해보자. 이때 A가 보낼 때의 sequence number와 ACK 번호는 바뀌지 않는다고 가정하자. 즉, C가 42번 byte에 있는 것이다.
그럼 A의 8byte가 B에 와서 42번 부터 49번까지 받게 되는 것이다. 제대로 받았으므로 이는 위로 올리고 다음에 받아야 하는것은 50번부터 이다. 따라서 ACK 50으로 feedback 해준다. 그렇다면 sequence number는 무엇일까? 79번이다.
A와 B에는 각각 send buffer, receive buffer가 있다.
A의 send buffer는 A가 만들어서 번호를 관리한다. 이 번호는 B의 receive buffer와 같다. 즉, A의 sequence number이다. 그렇다면 A의 receive buffer의 번호는 B의 send buffer의 index 번호인 것이다.
즉, A는 78번까지 받고 79번을 기다려야 하는 상황이었던 것이다.
그렇다면 보내고자 하는 echo와 실제 하고자 하는 말은 어떻게 구분할 것인가? B가 자신이 만든 message인지 echo인지는 header만 보고 알 수 없다. TCP에선 이러한 것에 관심가지지 않고 message는 application layer에서 알 수 있는 것이다. 프로토콜은 header가 있어야한다. 위의 그림에서는 C만을 보냈지만 message 자체도 data와 header로 구성된다.
누군가는 sender, receiver였는데 바로바로 ACK만 해주면 되는데 이제는 모든 사람이 sender, receiver이므로 ACK만도 가능하지만 ACK와 동시에 data를 보낼 수 있다. 따라서 ACK를 먼저 보낼지, data가 생성될 때 까지 기다렸다가 ACK를 할지의 경우가 있다. 이러한 경우 후에 포스팅 하겠지만, 보통 바로 ACK를 보내지 않고 특정 timing을 기다렸다가 같이 보내는 것을 권장한다. 같이 보낼 수록 이득이기 때문이다. 또한 TCP는 cummulative ACK이기 때문에 실제로 오는 데이터를 일일이 ACK해줄 필요가 없다. 왜냐하면 1,2,3,4를 받고 ACK5만 보내면 4까지 잘 받았다는게 되기 때문이다.
중간에 segment가 유실되면 우리는 timer를 통해 이를 감지한다고 했었다. 그렇다면 timeout value를 얼마만큼 세팅해야할까? timeout value의 장단점은 이미 얘기 했었다. 짧으면 대처가 빠른대신 네트워크 오버헤드가 크고 길땐 대처가 느린대신 네트워크 오버헤드가 더 작았다. 유실을 확신하면서도 timeout value를 작게 하는 것이 우리의 목표일 것이다.
segment가 목적지에 갔다가 돌아오는 시간을 RTT라고 한다. 따라서 timeout을 RTT로 생각해보면 되지 않을까? 부터 시작해보자.
A와 B가 고정되어 있는 상황인데 RTT 값이 모든 segment에 대해 고정되어있다면 좋겠지만 사실 그렇진 않다. 모든 segment에 대해 다 다를 수 밖에 없다. 왜냐하면 경로가 다르기 때문이다. 경로가 같아서 같은 router를 지나가도 queueing delay가 있기 때문에 다를 수 밖에 없다.
위의 파란점은 RTT를 나타내는데 제각기인 것을 알 수 있다. 따라서 이를 바로 쓰면 편차가 너무 크기 때문에 보정해서 사용하는 것이다.
따라서 보정된 RTT 값은 위와 같이 계산하는데 최근 RTT값을 더 비중있게 반영한 것을 볼 수 있다.
하지만 이 RTT 값을 사용하기엔 너무 타이트하기 때문에 우리는 여기에 4배정도 더한값을 사용한다.
TCP는 Cumulative acks를 사용하며 timer를 하나만 사용한다. 이는 Go-Back-N과 같다. 하지만 다른 점은 Go-Back-N에서는 timer가 터지면 window내에 있는 모든 segment를 재전송했지만 TCP에서는 해당 timer가 터진 segment만 재전송한다.
그림을 보며 이해해보자.
A의 send buffer에서 92 ~ 99번까지의 data를 보낸것이다. 따라서 B는 이를 받아서 올리고 ACK 100번을 보낸다. 하지만 loss 되었으므로 timeout으로 인해 재전송하고 이를 반복한다.
두번째 상황에선 92 ~ 99, 100 ~ 119까지의 data를 보냈다. 따라서 각각 ACK 100, 120을 보낸다. 그런데 ACK들이 늦게 가서 처음 보냈던 92 ~ 99가 timeout 나서 이를 재전송한다. 따라서 B는 이를 받지만 이미 119까지 잘 받았으므로 ACK 120을 보낸다. 이제서야 A는 B가 120까지 잘 받았음을 확인하고 A의 send buffer가 모두 비우는 것이다.
세번째 상황에선 첫번째 ACK가 유실되고 두번째 ACK만 도착했으나, Cumulative ACK이므로 재전송하지 않아도 된다. 왜냐하면 두번째 ACK를 통해 119까지 잘 전달 된 것을 확인할 수 있기 때문이다.
RTT 에 아주 넉넉한 margine을 잡아 실제로 timer가 터지긴 하지만 굉장히 긴시간이다. 뭔가 좀 더 TCP를 smart 했음 좋겠다. 어떻게 할까?
timer의 기본동작은 위와 같은데, 우리는 세번째 시나리오를 통해 Cumulative ACK의 힘을 보았다. 이러한 것을 통해 위에서 이야기 했던 기다렸다가 ACK하는 것을 권장하는 이유를 알 수 있다.
timeout 터져야 segment의 유실을 판단한다. 너무 길기 때문에 실제 유실이 일어나면 우리는 꽤 오랜시간을 기다렸다가 재전송을 하게 된다. 잘 생각해보면 timer가 터지기 전에 segment 유실을 파악할 수 있다.
예를 들어 window size가 커서 segment 100개를 한번에 보낸다고 가정하자.
segment가 1번부터 시작한다고 할 때 10번 segment만 유실 되었다면 timer는 하나기 때문에 10번까지 timer가 와서 터져야 알 수 있다. 하지만 timer가 터지기 전에 우리는 알 수 있다. 생각해보면 1부터 100까지 순서대로 쏟아 보내는데 10번빼고 B로 도착할 것이다. 그럼 ACK 2, ACK 3, ACK 4, ACK 5, ACK 6, .. 이 올 것이다. 근데 10이 유실 되었기 때문에 ACK 10이 반복적으로 계속 올 것이다. 따라서 ACK 10이 계속 오는 상황은 B가 9까지 계속 받아서 올리다가 11부터 받으면서 buffer에 저장하면서 ACK를 계속 보내는 것이다. 따라서 이렇게 반복적으로 오는 경우 유실되었음을 알 수 있다. 따라서 중복된 ACK를 4번(처음 ACK 10을 받고 중복된 ACK를 3번 더)받으면 유실로 판단해 재전송 할 것을 권고하고 있다. 따라서 이것을 timer가 터지기 전에 재전송한다고 해서 fast retransmit 기법이라고 한다. fast retransmit는 꼭 필요한 기능은 아니지만, 더 빠르게 효율적으로 보내기 위한 optimize기법이다. 하지만 timer는 필수적인 기능이다.
TCP는 두 프로세스 사이에 데이터를 주고 받는 것인데, 각자 자기자신이 보내는 data의 속도나 양을 조절할 수 있다. 하지만 이것이 의미가 있으려면 받는 사람의 상황에 맞춰야한다.
데이터는 receive buffer에 담겨있다가 application layer에서 read를 하면 올라가는 것이다. 만약 아직 read 하지 않아서 receive buffer에 가용 영역이 얼마 없다면 A에선 많이 보내도 해당 공간만큼만 수용할 수 있다. 따라서 A에선 B가 수용할 수 있는 용량만큼 보내야 의미가 있는 것이다. 이것이 flow control이다.
따라서 이러한 내용을 담은 것이 TCP segment header의 receive buffer size 필드이다.
따라서 A가 보낼 때는 A의 receive buffer의 빈 공간을 report하고 B가 보낼 땐 B의 receive buffer의 빈 공간을 report하는 것이다.
보내는 양과 보내는 속도의 차이는 무엇일까?
TCP에서는 일정양을 동시에 보내고 마는 것이아니라 계속계속 보낸다.
보통 속도는 10Mbps, bit/sec로 표기한다. 단위시간당 데이터양으로 나타내며 이게 높으면 속도가 높은 것이다. 큰 관점에서 본다면 같은 개념이다. 보내는 양이 많을 수록 속도가 크고 양이 적을 수록 속도가 느리다.
남아있는 공간을 report한다고 했는데, 결국 이것은 읽는 속도와도 연관이 있다. 왜냐하면 read를 안하면 쌓이기 시작한다. 자꾸 쌓이면 남는 공간은 점점 줄어들 수 밖에 없다. 그럼 빈공간이 하나도 없다면 0byte라고 report될 것이다. 그럼 A는 전송을 멈춘다.
극단적인 예시로 B는 받기만 하는데, receive buffer 공간이 0이라고 report가 나가게 되면, A는 B가 공간이 생길 때 까지 안보내면서 기다린다. B는 read를 기다리다가 read가 되어 빈공간이 생긴다. 이때 A는 B의 빈공간을 알아챌 수 있을까? A는 이미 0이라고 feedback을 받았으므로 알아차릴 수 없다. 따라서 TCP는 남는 공간이 0이라고 feedback을 받으면 주기적으로 의미없는 segment를 보내 남는 공간이 생겼는지 확인한다.
양쪽에서 데이터를 주고 받고 있는데, 이를 위해 필요한 구조체는 실질적으로 양쪽에 buffer 두개가 있는 형태이고 나의 sequence number, 상대방의 sequence number를 tracking 해야한다. 따라서 데이터를 주고 받기 전에 이러한 과정들이 이루어지고 이게 TCP connection을 위한 준비 동작이다.
처음에 data는 없고 header의 SYN 비트가 1인 SYN 패킷을 보낸다. 평상시에는 0으로 세팅되어있다. 이때 나의 첫 sequence number를 알려주며 내가 연결하고 싶음을 알리는 동작과정이다.
상대방은 연결하자는 의미로 응답을 보내는데 이게 SYNACK이며, 자신의 sequence number도 알리게 된다.
SYNACK를 받으면 ACK를 또 보내게 되는데 이제는 SYN 비트가 0이다. 또한 데이터를 함께 실어 보낼 수 있다.
따라서 3번의 동작과정이 있으므로 3-way handshake라고 한다.
굳이 3-way handshake를 하는 이유는 각자입장에서 생각해봐야한다.
2-way handshake를 사용한다고 가정해보자.
클라이언트 입장에서 생각해보자.
자신이 연결을 요청했고, 그에 대한 응답이 와서 연결이 되었음을 알 수 있다.
하지만 서버입장에서 생각해본다면, 클라이언트의 연결을 받고 서버도 클라이언트로 연결 요청을 보냈으나 응답이 없다면 연결되었는지 알 수 없다. 따라서 3-way handshake를 사용하는 것이다.
3번째 응답이 돌아올때 까지 receiver는 buffer를 만들지 않고 있는다.
이렇게 데이터를 주고 받다보면 필요한 데이터를 모두 보내고 connection을 close해야될 때가 온다.
close하고 싶을 땐 FIN을 보내면 된다. 따라서 클라이언트 서버 모두 FIN을 보내야 끝난다.
클라이언트 FIN을 보내고 ACK를 받고 서버도 FIN을 보내고 ACK를 받아도 둘 다 바로 release 하지 말고 일정 시간을 기다린다. 그림에 보이는 timeout 동안이다. 왜냐하면 마지막 ACK가 유실되면 서버는 timeout 되니 다시 FIN을 보내게 되는데 이미 클라이언트가 대기 하지 않고 이미 close 했다면 서버는 받지 않는 클라이언트에게 계속 FIN을 보내야 하는 상황이 오는 것이다.
서버의 timeout 시간을 알 수 없으므로 넉넉하게 잡아야한다.
실제로 클라이언트가 보내는 속도는 receiver의 상황과 네트워크의 상황에 좌우된다. 즉, 네트워크의 능력치 한계와 receiver 능력치의 한계가 있을텐데 우리는 어디에 맞춰서 보내야하는가?를 생각해봐야한다.
클라이언트는 보낼 때 둘 중에서 상황이 더 안좋은 것에 맞춰 보내야한다. 하지만 상황이 안좋은 것을 알려면 실시간으로 계속 tracking해야한다. 이때 서버의 상황은 직접적인 feedback(flow control 메커니즘)이 있으니 알 수 있는데, 네트워크 상황은 congestion control을 통해 알 수 있다.
우리는 각자 본인의 속도가 빠르길 원한다. 따라서 모든 사람은 데이터를 많이 쏟아붓길 원한다. 이러면 데이터를 많이 보내니 네트워크는 막히기 시작한다. 이 상황에서 데이터를 더 많이 붓는다면 상황이 악화된다. TCP 입장에서 생각해본다면, 패킷이 가다가 느려서 timeout이 되면 재전송을 하게 된다. 이를 통해 상황이 악화된다. 그렇다면 어떻게 해야할까? 네트워크가 막히면 안된다. 따라서 TCP는 네트워크가 막히지 않게 해야된다. 이는 곧 내가 데이터를 마구잡이로 보내면 안되고, 항상 막히지 않게 보내야한다는 것이다.
따라서 TCP는 네트워크 상황이 좋으면 속도를 올리고 안좋으면 속도를 줄인다. 하지만 이는 개념적인 이야기이다. 네트워크 상황이 좋아지고 안좋아짐을 어떻게 알 것인가?
크게 두가지 접근 방식이 있다.
네트워크에서 나한테 일어난 정보를 주겠다는 것이다. 라우터들이 현재 자신의 큐에 들어있는 상황등을 나에게 직접적으로 알려주는 것이다. 하지만 라우터들은 포워딩 하기 바쁘므로 실제로 구현되지 않는 방식이다.
네트워크 내부의 상황을 알려줄 수 없으니 알아서 유추하라는 것이다. 예를 들어 segment를 보내고 ACK가 늦게 오는지, 아니면 아예 안오는지를 살펴보는 것이다. 즉, ACK의 상황을 보고 유추한다. 유추하는 것이기 때문에 정확하진 않다.
너무 많이 보내면 파이프가 터진다. 하지만 문제점은 이 파이프의 굵기를 알 수가 없다. 따라서 파이프 경로의 굵기 보다 작은 양을 보내야하며 더 크면 터진다.
결국 각기 다른 굵기의 파이프 경로를 지나는데 가장 약한 곳은 가장 얇은 굵기를 가진 파이프일 것이다. 따라서 이 포인트를 알아야하는데, 네트워크로부터 report를 받지 못하니 알 수 없다. 그래서 양 끝에서 보내는 것을 토대로 알아내야한다.
그러면 처음에 얼마나 물을 보내야할까? 쏟아부어버리면 모두가 쓰는 네트워크가 혼란스러워지므로 안된다. 따라서 처음엔 조금 보내고 점진적으로 늘려가야한다.
실제로 TCP는 세가지 페이즈로 이루어진다.
효율성이 낮으므로 조금씩 많이 보내기 시작하는 것이다. slow start라고 했지만, 실제로 증가하는 속도는 exponential 하다.
2. Additive increase
많이 보내다가 thrahold를 만나면 linear하게 증가하기 시작한다.
고속도로가 꽉 막혔을 때 차량을 한대 줄이는 것으로는 도로가 뚫리지 않는다. 한번에 많은 차량이 없어져야 도로가 뚫릴것이다. 따라서 window size를 절반으로 줄이는 것이다.
MSS(Maximum segment size)는 segment를 전송하는 최대 크기이며 500바이트이다.
제일 처음 window size 자체가 1MSS로 세팅되어있는 것이다. 이게 잘 오면 2배로 늘어나 2MSS가 되어 2개를 보낸다. 따라서 segment를 전송하는 갯수의 단위가 바로 MSS인 것이다.
위의 그림을 보면 최대로 보낼 수 있는 지점이 보인다. 그렇다면 계속 그 지점대로 보내면 되는 것 아닌가? 싶을 수 있으나 불가능하다. 해당 지점을 알 수 없기 때문이다.
전송속도는 Conj
window size / RTT로 추측할 수 있다.
RTT는 window size만큼 보내기 때문이다. 실제로 변화가 심한것은 RTT 보다는 window size이다. 결국 전송속도는 window size에 의해 좌우되며 이를 결정하는 것이 네트워크이다. 네트워크가 많이 붐비면 window size는 작아지고 한가하면 window size를 늘린다. 즉, 공용으로 사용하는 네트워크로 인해 개인의 속도가 결정되며 이는 서로가 서로간에 영향을 미친다는 것이다.
x축은 time이고 y축은 conjestion window size이다.
하늘색이 TCP1의 행동이다. 처음에 아무것도 모르니 1MSS로 시작한다. 즉, 500byte이다. 위 그림에서 slow start는 Threshold 까지 이다. 뒤부터 선형적으로 증가되기 때문이다.
기존의 Threshold가 18이었는데 패킷 유실이 탐지 되면 Threshold값을 지금 패킷 유실이 탐지된 이 순간의 window size의 절반으로 바꾼다. window size가 12이므로 Threshold를 6으로 바꾸는 것이다. 이것이 80년대 초 구현된 TCP 첫번째 버전 Tahoe이다.
두번째 버전이 나온 이유를 알아보자. Tahoe는 네트워크 혼잡의 조짐이 보였을 때 Slow start부터 시작했다. TCP에서 패킷 유실은 timer를 통해 감지했다. 더 정확히 얘기하자면 timeout 현상이 발생했을 때 패킷 유실임을 탐지했다. 또한가지 조건은 3duplicate ACK이다. 즉, 패킷 유실을 판단하는 조건이 두가지 였다.
이 두 경우가 네트워크 관점에서 보았을 때 같은지 생각해봐야한다.
3 duplicate ACK를 받은 상황은 다 잘가는데 운나쁘게 하나만 문제가 생긴상황이다. timeout은 그 패킷도 안갔고 그 이후에 다른 패킷들도 안갔기 때문에 생긴상황이다. 즉, timeout이 더 안좋은 상황인 것이다. 상황이 둘 다 다르기 때문에 대처도 다르게 할 필요가 있다.
그래서 3 duplicate ACK를 받았다면, 다시 slow start를 하지 않고 window size를 절반으로 줄이고 linear로 increase하고 timeout 이라면 밑바닥 부터다시 시작하는 것이다.
젤 처음 Threshold를 어떻게 잡아야할까? 터지는 순간에 절반으로 잡으면 된다. 사실 구현하는 사람마음이다. TCP라는 것은 한 컴퓨터 내에 있는 여러 TCP 중 하나일 뿐이므로 다른 TCP꺼를 쓸 수도 있다.
A와 B사이의 전송량을 조절하는 것을 이야기 했다. 하지만 네트워크는 둘만 사용하는 것이 아니다. 네트워크를 사용하는 각각의 사람들이 congestion
예를 들어 두 컴퓨터가 있고 각자 TCP를 사용하는데, 같은 네트워크를 사용한다. 이 네트워크가 처리할 수 있는 것은 r이다. 그럼 A와 B가 각각 congestion control할 것이고 1/2r씩 사용하는지 궁금할 것이다. 신기하게도 둘은 fair하게 1/2r씩 쓴다.
위의 사진을 보면 결국 fair 하도록 수렴한다.
여기서 한가지 맹점은, 결국 진짜로 fair한가?
A가 TCP를 2개 열고 B가 TCP를 1개 열었다면 A가 더 많이 사용하는 것이다. 왜냐하면 TCP 끼리의 fair이기 때문이다.