TCP/IP 프로토콜을 사용한다는 것은 IP를 통해서 데이터의 실질적인 전송을 처리하고, TCP를 통해서 통신의 신뢰성을 보장한다는 의미이다.
통신의 신뢰성을 망치는 요인은 여러가지가 있다. 이미 전송된 패킷이 손실되거나, 패킷의 순서가 바뀌거나, 네트워크의 혼잡으로 인해 패킷이 이동하지 못하거나, 수신 측에서 패킷을 받을 공간이 없을 경우 가 있다. 결국 운영체제의 프로토콜 스택을 거쳐서 만들어진 패킷이 상대측에 제대로 전달되지 못하는 경우 통신의 신뢰성은 없다고 말한다. 앞에서 TCP를 통해서 통신의 신뢰성을 보장한다고 말했다. 위 4가지 문제를 TCP는 어떻게 해결할까?
송신측이 데이터 처리 속도가 더 빠른 경우를 생각해보자. 수신측의 버퍼가 가득찼음에도 송신측에서 패킷을 지속적으로 보낸다면, 이미 버퍼가 찬 상태에서 수신된 패킷은 손실될 것이다. 이를 막기 위해 수신측 버퍼의 용량이 얼마나 남았는지 파악하고 패킷을 보내어 송신측과 수신측의 데이터 처리 속도 차이로 인한 패킷 손실을 막는다.
전송된 패킷에 대한 응답을 받고 나서야 다음 패킷을 보낸다. 딱 봐도 비효율적이라는 것이 느껴진다.
3-way handshake 과정에서 수신측의 window size를 파악하고, 송신측의 window size를 설정한다. 그리고 마지막 전송 Bytes - 마지막 ACK Bytes <= 수신측의 남은 window size 를 만족하는지 확인하고, 이에 맞춰서 데이터를 추가 전송한다. 수신측에서 응답을 보낼 때 남은 window size를 세그먼트에 담아 보내기 때문에 송신측에서는 지속적으로 보내야할 양을 파악할 수가 있다. Stop and Wait 방법과 가장 큰 차이점은 수신측이 처리할 수 있는 데이터의 양을 알고 있다는 것이다. 이로 인해 더 유연한 처리가 가능해진다.
사용하는 네트워크가 혼잡하다면, 송신측에서 보낸 패킷이 수신측에 도달하지 못해서 처리를 할 수 없기 때문에 이를 손실된 데이터로 간주하고 재전송한다. 이렇게 되면 안그래도 혼잡한 네트워크가 더 혼잡한 상태로 된다. 이를 막기 위해 TCP는 송신측에서 보내는 데이터의 전송 속도를 조절한다. 이를 혼잡 제어라고 한다.
처음에 패킷 하나를 보내는 것으로 시작한다. 패킷이 문제 없이 도착한다면, Window Size를 1씩 늘린다. 하지만, 패킷 전송에 실패하거나, timeout이 발생하면 Window Size를 절반으로 줄인다.
이 방식의 특징은 여러 호스트가 네트워크를 공유하고 있다면, 나중에 전송을 시작한 호스트가 처음에는 네트워크 점유에 있어서 불리하지만, 시간이 지날수록 평형 상태로 맞춰진다. 단점으로는 초기에 많은 양의 데이터를 보내지 못하고, Window Size를 줄일 때면, 이미 네트워크가 혼잡해져 있는 상태라는게 문제다.
AIMD와 마찬 가지로 패킷 하나를 보내는 것으로 시작하지만, Window Size를 두 배씩 늘린다. 혼잡 현상이 일어나면, Window Size를 1로 줄인다. 초기에는 네트워크 혼잡에 관한 정보가 없기 때문에 Window Size가 두 배씩 증가하다가 1로 줄어든다. 이후에는 임계값까지는 2배씩 증가하다가 이후에는 혼잡 회피 단계로 전환한다.
혼잡 회피 : Window Size가 임계값보다 클 경우 데이터 손실이 발생할 가능성이 높기 때문에 Window Size를 보수적으로 늘릴 필요가 있다. Window Size를 1씩 늘리다가 timeout의 발생으로 네트워크 혼잡이 감지된다면, Window Size를 1로 줄이고, 임계값을 혼잡이 발생했던 때 Window Size의 절반으로 줄인다.
이제 남은 문제 상황은 패킷이 손실되었거나, 패킷의 순서가 바뀌었을 때이다. TCP는 ARQ(Automatic Repeat Request), 재전송 기반으로 오류를 제어한다. 언제 재전송을 해야하는지가 가장 중요한 포인트라고 볼 수가 있다.
송신측에서 Sequence Number를 부여하여 패킷을 보내고, 이에 수신측은 송신측에게 다음에 보낼 패킷이 무엇인지 ACK를 통해 알려준다. 송신측이 동일한 ACK를 지속적으로 받는다면, 이는 해당 패킷이 제대로 수신측에 도달하지 못했음을 알 수가 있다. 하지만 한 번 중복된다고 바로 재전송을 하지는 않고, 3회 정도 같은 ACK를 받았을 때 해당 패킷을 재전송한다. 왜냐하면 송신측에서 보낸 패킷이 모두 동일한 속도로 수신측에 도달하는 것을 보장하지는 못하기 때문이다.
응답에 대한 요청을 받고 난 뒤 다음 데이터를 보내면, 자동적으로 재전송에 기반한 오류 제어가 가능해진다. 하지만 위에서 말했듯이 TCP는 슬라이딩 윈도우 방식으로 데이터를 보내기 때문에 여기에 맞춰진 오류 제어 방법이 필요하다.
Go Bank N 방식을 사용하면, 수신 측에서 N번 패킷부터 에러가 발생함을 감지하면, 송신측에 N번 패킷부터 다시 전송해달라고 요청하고, 이후 데이터들을 모두 폐기한다. 송신측은 N번 패킷부터 재전송한다.
Go Bank N은 에러가 발생한 패킷 이후에 수신된 패킷들도 모두 폐기한다는 문제점이 있다. 이 문제를 해결하기 위해 Selective Repeat 방식이 등장한다. 문제가 있는 패킷에 대해서만 재전송을 요청한다. 하지만 이 방법을 사용하기 위해서는 별도의 버퍼가 추가적으로 필요하다. 중간에 에러가 발생한 패킷이 빠져 있을텐데 이를 재정렬 해야하기 때문이다.
위 두 방법 중 더 효율적인 것을 선택해서 사용한다. 하지만 불확실한 네트워크를 통해 재전송하는 것보다 수신 측에서 재정렬 하는 것이 더 낫다는 판단 하에 Selective Repeat을 많이 채택하는 것 같다.
https://evan-moon.github.io/2019/11/22/tcp-flow-control-error-control/
https://gyoogle.dev/blog/computer-science/network/%ED%9D%90%EB%A6%84%EC%A0%9C%EC%96%B4%20&%20%ED%98%BC%EC%9E%A1%EC%A0%9C%EC%96%B4.html