Error Control 역시 Datalink layer에서 제공하는 것과 유사하게 기능한다. 그러나, Datalink layer에서는 순서가 꼬이게 도착할 일은 없기 때문에 Receiver는 어느 프레임이 안 들어왔는 지를 바로 알 수 있지만, 인터넷을 거치는 Transport layer에서는 순서가 바뀐 채로 수신될 수 있기 때문에 약간의 차이를 가진다.
먼저 Datalink layer에서는 CRC로 프레임 데이터의 무결성을 검증했다면, TCP에서는 Checksum을 사용하여 Segment의 무결성을 검증한다. 또한, Accumulative한 Ack를 사용하여 가장 마지막에 들어온 Ack 번호보다 작은 번호를 가지는 Ack들은 받지 못했더라도 정상적으로 받았다고 간주할 수 있다. 만약 제대로 데이터가 들어오지 않았다면 해당 Segment의 SN을 담아 Selective하게 Ack를 보내면 Sender는 이 Ack를 받아 문제가 생긴 Segment를 재전송한다.
이때, 문제가 생기는 상황을 두 가지로 나누어 생각해볼 수 있다.
1번 상황이라면 초반에 오버플로우가 났는데, 계속해서 Segment가 들어와서 Loss가 난 상황(congestion)이라고 생각할 수 있고, 2번 상황은 예기치 못한 에러로 인해 특정 Segment가 사라졌다고 생각할 수 있다.
이런 상황 모두에서 TCP는 Error가 생겼다고 판단되면, 즉 도중에 특정 Segment가 사라졌다고 판단되면 문제가 생긴 해당 Segment를 재전송하여 문제를 해결한다. 이를 위해 1번 상황을 처리할 때는 Retransmission time-out(RTO) timer를 사용하고, 2번 상황을 처리할 때는 Fast retransmission으로 핸들링이 가능하다.
이런 상황이 보통 일반적인 상황이다. Client가 Server에게 Segment를 보내고, Ack를 받는다. 이 과정은 Piggybacking 기능을 사용하여 통신할 수 있고, 상대가 보낸 데이터에 대해 내가 보낼 데이터가 없다면 Ack만 보내 화답한다.
여기서 500ms만큼의 시간을 기다리는 이유는 Packet이 늘어나는 상황을 막기 위해 Nagle's algorithm 등으로 데이터의 입력을 기다리는 시간이지만, 여기서는 크게 고려하지 않겠다. 또한, 2개의 Segment를 받으면 Sender에게 Ack를 보내주도록 되어있지만, 그것 역시 여기서는 크게 고려하지 않겠다.
이런 상황이 바로 RTO가 timeout된 경우가 된다. Sender가 보낸 Segment가 유실되어 이에 대한 Ack가 RTO가 timeout 될 경우 들어오지 않는다면, 다시 Sender는 해당 Segment를 재전송한다. 그림에서는 Ack가 timeout 되기 이전에 도착한 것으로 되어있지만, timeout이 먼저 일어났다고 생각하면 된다.
이 상황은 Fast Retransmission이 일어나는 경우이다. Sender가 보낸 Segment 중 하나가 유실되었을 때, 이에 대한 동일한 Ack가 3번 수신된다면 Sender는 해당 Segment에 문제가 생겼다고 판단하여 다시 해당 Segment를 재전송하게 된다. 이 과정은 RTO가 timeout 되기 이전에 이루어진다.
이번에는 반대로 Ack가 사라졌다고 해보자. 그러나, Sender는 Window size만큼은 Ack 없이도 문제없이 보낼 수 있기 때문에 Ack가 사라져도 문제없이 Segment들을 계속 보낼 수 있다. 이후 Ack를 Sender가 받게 된다면 Ack는 cumulative하기 때문에 Sender는 Receiver가 모든 Segment를 잘 받았음을 알 수 있게 된다.
이 상황은 Ack가 사라졌는데, 하필 Sender가 추가적으로 보낼 Segment가 없어서 추가적인 Ack가 도착하지 않는 상황이다. 이렇게 되면 RTO가 timeout 됨에 따라 Sender가 중복된 Segment를 다시 보내게 되고, Receiver는 이미 잘 받았었으니까 이걸 무시하고 다시 Ack를 보내주면 된다.
만약 Receiver가 rwnd를 0으로 두고 Ack를 보냈다고 해보자. 이후, Receiver의 버퍼에 자리가 생겼다면 rwnd을 제대로 설정해서 다시 Ack를 보내게 되는데, 이 Ack가 사라진다면 어떻게 될까? Sender는 Window size가 0이라서 Segment를 더 보낼 수 없고 Receiver는 Sender가 데이터를 보내지 않으니 서로가 서로에 의해 아무 동작도 없이 가만히 있게 되는 Deadlock 상황이 발생한다.
이걸 해결하기 위해서 TCP는 Persistence timer를 두어 해결한다. 이 타이머는 Sender가 rwnd가 0인 Ack를 받았을 때 동작하는데, 이는 추후 포스트에서 자세히 다룰 예정이다.
앞에서 다뤘던 상황들은 모두 Cumulative Ack 방식으로 동작했다.
이 그림만 봐도 301~400번대의 Segment가 사라지니 Receiver는 Ack 301만 보내기 때문에 그 뒤로 들어온 데이터들을 받았는 지 못 받았는 지에 대한 것을 Sender가 확인할 수 없었다.
이런 동작보다 Sender 입장에서 Receiver가 어떤 Segment는 잘 받고 어떤 Segment를 못 받았는 지 알 수 있다면 못 받은 Segment만 바로 전송하면 되기 때문에 훨씬 효율적으로 동작할 수 있을 것 같다. 이 기능을 지원해주는 것이 Selective Ack이고 이건 SACK option으로 구현 가능하다.
SYN Segment를 보내 TCP Connection을 포함할 때, 이 세그먼트에 SACK-permitted option을 설정하여 같이 전송한다. 이 의미는 "나는 SACK 기능을 사용할 수 있어" 라는 의미가 된다. 이에 대해 Receiver는 Ack를 보낼 때 자신도 SACK를 사용할 수 있다면 Ack Segment에 역시 SACK-permitted option을 활성화시켜서 보내게 된다. 클라이언트와 서버 모두에서 SACK 기능을 지원한다면 데이터 전송 과정 중에서 SACK 옵션을 사용할 수 있다.
예시 시나리오는 다음과 같다.
다음과 같은 상황을 생각해보자. 이렇게 보내게 된다면, Receiver는 Sender에게 자신이 받은 Block의 범위를 알려줄 수 있다. 즉, Sender는 SACK 옵션에 포함되어 있지 않은 범위의 Segment는 전달이 되지 않았다고 판단하고 다시 재전송해주면 된다.
또한, 이렇게 중복된 Segment가 들어와도 그냥 자신이 받은 범위를 SACK에 담아 Sender에게 알려준다.
혹은 이렇게 중복된 Block 범위가 나타나게 하여 Sender로 하여금 중복된 Segment를 보냈다는 것을 알게 해준다.