앞서 TCP는 수신자의 성능을 초과하지 않는 선에서 데이터를 전송한다고 했다. 이를 더 자세히 얘기하면 다음과 같다.
TCP 연결이 성립되면 전송자와 수신자 모두 Send 버퍼, Receive 버퍼를 갖는다. Receive 버퍼에는 순서대로 오지 않은 패킷들을 저장해놓는다.
수신자는 패킷을 받으면 Receive 버퍼에 저장해놓고 어플리케이션 계층으로 필요할 때 마다 올려준다.
전송자는 Window size 만큼의 데이터만 전송할 수 있다. 하지만 Receive 버퍼에 남은 공간이 적다면 아무리 데이터를 더 보내봤자 전부 폐기된다.
따라서 실질적으로 Receive 버퍼에 남은 공간 만큼만 데이터를 보내는 것이 의미가 있다. 이러한 과정을 TCP의 Flow Control (흐름 제어) 이리고 한다.
흐름 제어를 하기 위해서 수신자는 자신이 현재 얼마만큼의 데이터를 받을 수 있는지를 전송자에게 알려줘야한다. 이때 사용하는 헤더 필드가 바로 앞서 얘기했던 Receive buffer size 필드이다. 이름에서 알 수 있듯이 Receive 버퍼의 남은 공간 크기를 보내준다.
네번째 줄 오른쪽에 Receive window 필드가 이 역할을 맡는다.
흐름 제어는 데이터의 전송 속도와 양을 모두 조절한다고 볼 수 있다.
만약 수신자가 많은 양의 데이터를 수용할 수 있어서 데이터를 많이 보내면 그만큼 데이터가 전송되는 속도가 빠르다고 볼 수 있다. (한번에 많은 데이터를 보낼 수록 전체 데이터가 더 빨리 전송된다.)
반대로 작은 양의 데이터를 보내면 그만큼 전송속도가 느리다고 볼 수 있다. (데이터를 조금씩 보내므로 전체 데이터가 그만큼 더 늦게 전송된다.)
결론적으로, 흐름 제어란 수신자의 버퍼가 넘치지 않도록 데이터 전송을 조절하는 작업이다.
흐름 제어는 결국 어플리케이션 계층에서 전송 계층의 데이터를 읽는 속도와도 관련이 있다. 어플리케이션 계층에서 전송 계층의 데이터를 빠르게 읽으면 그만큼 Receive 버퍼의 공간이 많이 생긴다. 따라서 수신자는 더 많은 양의 데이터를 수신할 수 있다.
극단적인 예시로, 만약 어플리케이션이 모종의 이유로 전송 계층의 데이터를 읽지 못하고 있어서 Receive 버퍼가 가득 차있다고 가정해보자. 이렇게되면 전송자에게 보내지는 Receive buffer size는 0바이트가 된다. 따라서 전송자는 데이터를 보내는 일을 잠시 멈추고 Receive 버퍼에 공간이 생길 때 까지 대기한다.
문제는 추후에 Receive 버퍼에 공간이 생겼음에도 전송자가 이를 인지하지 못한다는 것이다. 왜냐하면 수신자가 일단 데이터를 수신해야 그 응답으로 ACK를 보내고, 그 ACK 세그먼트의 헤더에 있는 Receive buffer size를 토대로 데이터의 전송 양을 결정할 수 있다. 하지만 가장 마지막에 보낸 ACK의 Receive buffer size는 0이다. 이 이후로는 아무런 패킷 교환이 없었으므로 전송자는 아직 Receive 버퍼에 공간이 생겼다는 사실을 알지 못한다.
그렇다면 TCP의 흐름 제어는 이러한 상황을 어떻게 해결할 수 있을까?
이러한 Corner case에 대응하기 위해서 TCP에서 전송자는 수신자에게 주기적으로 세그먼트를 전송한다. 만약 Receive buffer size가 0이라면 이때 보내는 세그먼트의 Data 부분은 빈 공간으로 둔다. 즉, 의미 없는 세그먼트를 주기적으로 보내는 것이다.
이렇게 의미 없는 세그먼트라도 주기적으로 보내야 수신자가 ACK를 보내게 되고, 이 ACK의 헤더에 Receive buffer size를 토대로 수신자의 Receive 버퍼 크기를 파악할 수 있기 때문이다.
이때 보내는 의미 없는 더미 세그먼트는 1바이트 정도의 크기를 갖는다.
TCP에서 연결이 성립되기위해 먼저 세팅되어야 할 것은 다음과 같다.
이를 세팅하는 과정을 TCP Connection Management라고 한다.
연결 제어는 TCP 세그먼트의 헤더 부분 중 SYN, ACK, FIN 필드를 통해 이루어진다.
TCP는 총 3차례에 걸쳐 SYN, ACK을 주고 받으며 연결을 성립한다.
클라이언트와 서버가 3-way-handshake를 통해 TCP 연결을 성립한다고 가정해보자.
클라이언트는 먼저 시퀀스 넘버를 정하고, 서버에게 TCP Connection을 열겠다는 요청을 보낸다. 이 요청을 SYN msg라고 한다. 이 메시지는 세그먼트로써 SYNbit를 포함하고 있다. SYNbit는 평소에는 0이지만 이렇게 새로운 연결을 시도할 때만 1로 바뀐다. 동시에 시퀀스 넘버도 같이 알려준다.
서버는 SYN을 수신하고 서버를 열겠다는 의사 표시로 SYNACK 피드백을 보낸다. SYNACK 역시 SYNbit가 1이며 서버의 시퀀스 넘버도 같이 보내준다. 동시에 ACKbit로 1을 보내고, 클라이언트로부터 받은 시퀀스 넘버에 +1을 하여 ACK을 보낸다. 또한 서버는 이때 버퍼를 생성한다.
서버로부터 SYNACK을 받은 클라이언트는 서버가 열렸음을 인지하고, 이에 대한 응답으로 ACK을 보낸다. 이때 보내는 ACK에는 데이터가 포함될 수 있다. 또한 이때부터 SYNbit는 0이 된다.
서버는 클라이언트로부터 받은 ACK을 토대로 클라이언트 역시 준비가 되었음을 인지한다.
이처럼 총 3차례에 걸쳐서 연결을 성립하는 과정을 3-way-handshake라고 한다.
굳이 3차례에 걸치는 이유는 2번 과정에서 클라이언트는 서버에게 메시지가 성공적으로 전송된다는 것을 인지하지만 서버는 아직 자신이 보낸 메시지가 클라이언트에게 정상적으로 전송되는지 모른다. 따라서 클라이언트가 한번 더 응답 메시지를 보냄으로써 서버 역시 자신의 메시지가 클라이언트에게 제대로 전송되고 있다는 것을 인지하게 된다.
TCP는 데이터 통신이 끝나면 연결을 종료해야한다.
연결을 성립하는 과정은 3-way handshake로 이루어진다면, 연결을 종료하는 과정은 4-way handshake로 이루어진다.