TCP는 연결 지향형 프로토콜이다.
하지만 이 연결은 TDM 과 같은 실제 연결이 아닌
종단 시스템에서만 동작하는 "논리적" 연결이다.
TCP 연결은 '전이중 서비스'를 제공하는데 만약 A와 B 사이의 연결이 있다면
데이터가 A->B 로 흐르는 동시에 B->A로도 흐를수가 있다.
클라이언트가 소켓을 통하여 데이터를 트랜스포트 계층으로 밀어넣으면
클라이언트의 트랜스포트 계층에서는 데이터를 mutiplexing 한다.
그런후 three-way hanshaking 을 통하여 서버의 프로세스와 연결을 맺고 데이터를 전송하기 시작한다.
이때 three-way hanshaking 중간에 송신 버퍼에 보낼 데이터를 미리 싫어 놓으며
어떤 경우에는 데이터 묶음을 만들어 네트워크로 보내기도 한다.
이는 RFC 793에서 "TCP가 자신이 편한대로 세그먼트의 데이터를 보낸다"고 명시되어 있기에 가능하다.
어플리케이션 계층에서 넘어온 데이터를 세그먼트화 할때 크기의 제한이 있다.
이를 우리는 MSS라고 부른다. 이는 일반적으로 로컬 송신 호스트에 의해 전송될수 있는
가장 큰 링크 계층 프레임의 길이(MTU)에 의해 결정된다.
TCP 세그먼트 + TCP/IP 헤더(통상 40bytes) = MTU가 되어야하며
통상적으로 이더넷에서는 MTU가 1500bytes이기에 MSS=1460bytes이다.
앞선 rdt에서는 순서번호는 0,1 또는 패킷마다 붙여줬다.
하지만 TCP에서는 전체 데이터를 MSS 단위로 자른후 바이트 번호로 붙여준다.
다음과 같은 파일이 있고 MSS=1000이라면 1번째 세그먼트는 0~999바이트까지
2번째는 1000~1999바이트까지 가지고 있다.
이때 seq num은 세그먼트의 첫번째 바이트인 0, 1000, 2000으로 붙여진다.
통신 과정에서 첫번째 세그먼트를 정상적으로 받고 ACK를 보낼때
다음에 받아야할 세그먼트의 첫 바이트인 ACK 1000을 전송한다.
만약 모종의 이유로 seq0인 세그먼트와 seq2000인 세그먼트만 받았다면
seq 2000인 세그먼트는 버퍼링 한후에 ack 1000을 보낸다. (GBN과 유사)
***
특이점으로는 정상적으로 받은후 500ms 대기후에 ack를 전송하는데
이 과정에서 다음 정상 순서의 segment가 온다면 바로 ack 2000을 전송한다.
누적 응답이기에 ack n 이 온다면 n-1 바이트까지는 완전히 정상 전송 됬다고 생각하면 된다.
텔넷의 예를 보자
서버에 C데이터를 요구하면 클라이언트는 제대로 받았다는 신호와 자신의 seq를 보낸다.
제대로 전달 받으면 seq=43,ACK=80 을 전송한다.
만약 2방향이면 어떻게 될까???
클라이언트가 서버에게 요청신호를 보내고 한참을 기다렸는데 서버의 응답이 오지않았다.
클라이언트는 다시한번 서버에게 요청신호를 보냈다.
하지만 이때 서버에서 연결 신호가 뒤늦게 온다면?
클라이언트는 이 응답이 내가 처음에 보낸것에 대한 응답인지
다시 보낸 것에 대한 응답인지 알수가 없으며
서버는 뒤늦게 요청을 한번 더 받고 다시 연결을 만들것이다.
이런 경우에 낭비가 발생할수 있기에 우리는 3-way를 쓴다.
클라이언트는 SYNbit=1 로 만들고 임의의 seq를 서버에게 전달한다.
서버는 해당 패킷을 받고 SYNbit=1과 ACKbit=1 로 만든뒤에 응답하고
클라이언트가 다시 이 응답을 잘 받았다고 돌려준다.
이를통해 위에서 발생한 문제를 해결 가능하다.
RTT를 계산하는 이유는 다음과 같다.
우리는 '일정 수준' 이상으로 ACK가 오지않으면 아 손실이 됬구나를 알아차리는데
이 '일정 수준'을 RTT보다 크게 둬야 하기 때문이다.
하지만 RTT는 네트워크 상태나 패킷의 크기등에 영향을 받기 때문에 완벽하게 알수는 없고
수식을 통해 근사치를 예측한다.
아래 사진은 실제 샘플과 예측 RTT의 값이다.
EstimatedRTT = (1 - α) × EstimatedRTT + α × SampleRTT
(권장되는 α의 값 : 0.125)
여기에 어느정도 여유를 둬서 계산하며 편차를 더해야한다. 편차는 다음과 같이 구한다.
DevRTT = (1 - β) × DevRTT + β × | SampleRTT - EstimatedRTT |
(권장되는 β의 값 : 0.25)
최종 식은 다음과 같다.
TimeoutInterval = EstimatedRTT + 4 × DevRTT
💡 TCP는 IP의 비신뢰적인 최선형 서비스에서
신뢰적인 데이터 전송 서비스(reliable data transfer service)를 제공한다.
즉, 데이터가 손상되지 않았음을 보장하며
중복이 없음과 순서의 유지를 보장해줘야한다.
이를 위해서 타이머를 사용하는데 모든 세그먼트에 타이머를 세팅하는 것은 큰 오버헤드를 발생시킨다.
따라서 TCP는 하나의 타이머만을 두며 ACK를 받지 못한 세그먼트들중 가장 오래된 세그먼트를 추적한다.
이때 타이머의 deadline은 앞서 살펴본 TimeoutInterval이다.
1. 상위 어플리케이션으로 부터 데이터 수신
- TCP는 소켓을 통해 어플리케이션에서 데이터를 받는다.
- mutiplexing을 통하여 세그먼트로 캡슐화한다.
- IP에게 세그먼트를 넘긴다.
의 과정을 거쳐 처리한다.
이때 타이머가 돌아가고 있지 않다면 해당 세그먼트에 대해서 타이머를 실행시킨다.
2. 타임아웃
- 타임아웃이 발생한다면 세그먼트를 재전송하며 해당 세그먼트에 대해서 타이머를 다시 설정한다.
3. ACK 수신
SendBase : 수신 확인응답이 확인되지 않은 / 가장 오래된 바이트의 순서번호
SendBase-1 : 수신자에게서 정확하게 차례대로 수신되었음을 알리는 마지막 바이트의 순서번호
y(받은 ACK) > SendBase이면, ACK는 이전에 확인응답 안 된 하나 이상의 세그먼트들을 확인해준다.
송신자는 자신의 SendBase 변수를 갱신한다.
아직 확인응답 안 된 세그먼트들이 존재한다면 타이머를 다시 시작한다.
TCP 프로토콜이 어떻게 작동하는지 몇가지 시나리오를 통해 알아보자
1. ACK가 손실 됬을때
B는 이미 제대로된 데이터를 보냈지만 ACK 100이 손상이 되 A가 재전송을 한 상황이다.
이때 B는 이미 받은 세그먼트 이기에 ACK100을 다시 보내고 A가 보낸 메시지를 버린다.
2. ACK 100만 손실됬을때
2개의 세그먼트를 보냈고 타임아웃이 발생해 재전송을 하는 상황이다.
이때 TCP는 가장 오래된 세그먼트인 seq 92 만 전송할것이고
B는 자신은 이미 119까지는 정상 수신하였기에 ACK 120만 보낼것이다.
3. ACK 100이 손실났지만 누적 응답이 왔을경우
이전 세그먼트에 대한 ACK가 손실이 되어 못왔지만 다음 세그먼트에 대한 ACK가 온다면
TCP는 누적 응답이기에 ACK120이 왔다는건 119까지는 제대로 받았다는 뜻이여서
문제없이 다음 세그먼트를 보내게 된다.
만약 타임아웃이 발생한다면 TCP는 이전 인터벌의 2배를 준다.
타임 아웃이 발생한것은 뒤에서 살펴볼 혼잡 상태 일 가능성이 높고
이를 해결하기위해 시간을 넉넉히 준다.
그 다음부터는 다시 인터벌을 계산하여 처리한다.
Time-out 주기는 RTT 보다 길다. 그렇기에 오래걸리며 비효율적이다.
따라서 TCP는 재전송을 위한 또 한가지의 방법을 쓴다.
중복된 ACK가 3번 이상온다면 세그먼트가 정상적으로 전송이 안되었다고 생각하고 바로 재전송해주는 것이다.
TCP는 GBN의 누적응답과 SR의 버퍼링을 모두 가지고 있다.
따라서 어느 한 방법이라하기 보다는 혼합형이라 보는것이 적당할 것이다.