TCP 소켓에 존재하는 입출력 버퍼

소켓의 타입에서 설명하였듯이, TCP 소켓에는 데이터의 경계라는 개념이 존재하지 않는다. 데이터의 경계가 존재하지 않는다는 것은 TCP 프로토콜이 자의적으로 다음의 동작을 수행할 수 있음을 의미한다.

  1. 한 번에 대량의 데이터를 write() 할 때, 여러 개의 작은 단위로 쪼개 나누어 송신할 수 있다.
  2. 여러 번 소량의 데이터를 write() 할 때, 하나의 큰 단위로 합쳐 한 번에 송신할 수 있다.

이것이 가능한 이유는 read(), write()가 호출되는 즉시 데이터가 송수신되는 것이 아니기 때문이다. TCP 소켓에는 입출력 버퍼가 존재하며, 소켓을 대상으로 read(), write()가 호출되면 아래와 같이 동작한다.

  • write()의 경우 : 데이터를 출력 버퍼로 전달 후, 상황에 맞게 적절히(한번에 보내든 나눠서 보내든) 데이터를 상대방의 입력 버퍼로 전송한다.
  • read()의 경우 : 입력 버퍼에 저장된 데이터를 읽어들인다.

또한 TCP 소켓의 입출력 버퍼는 아래와 같은 특성을 지닌다.

  1. 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재한다.
  2. 입출력 버퍼는 소켓 생성 시 자동으로 생성한다.
  3. 소켓을 닫아도 출력 버퍼에 남아있는 데이터는 계속해서 전송이 이뤄진다.
  4. 소켓을 닫으면 입력 버퍼에 남아있는 데이터는 소멸되어 버린다.

TCP 흐름제어(flow control)

슬라이딩 윈도우(Sliding Window) 프로토콜

입출력 버퍼의 존재를 알았으니, 다음과 같은 상황을 가정해볼 수 있다.

클라이언트의 입력 버퍼의 크기보다 큰 데이터를 서버 측에서 전송하였다.

만약 위와 같은 상황이 발생한다면 버퍼 크기의 초과분만큼 데이터가 손실되는 문제가 발생할 것이다.
이러한 문제를 해결하기 위해 TCP에는 슬라이딩 윈도우 프로토콜이 존재한다. 이 프로토콜은 데이터의 흐름을 제어하여 데이터가 버퍼의 크기를 초과하여 송신되지 않도록 한다.

슬라이딩 윈도우 프로토콜에 입각하여, 양측 소켓은 아래와 같이 현재 버퍼에 얼마만큼의 데이터를 더 저장할 수 있는 지에 관한 메세지를 주고받으며 송수신한다.

  • 소켓 A : 50 byte까지는 수용할 수 있다.

  • 소켓 B : 확인

  • 소켓 A : 방금 20 byte를 비워 70 byte까지는 수용할 수 있다.

  • 소켓 B : 확인

write()가 반환되는 시점
write()가 반환되는 시점은 상대 호스트로 데이터의 전송이 완료되는 시점이 아닌, 전송할 데이터가 출력버퍼로 이동이 완료되는 시점이다. 그러나 TCP의 경우는 출력 버퍼로 이동된 데이터의 전송을 보장하기 때문에 write()의 데이터 전송이 완료되어야 반환이 된다.라고 표현한다.
따라서 이 표현에 대한 정확한 이해가 필요하다.

Three-way handshaking

Three-way handshaking은 tcp 소켓이 상대 소켓과 연결하는 방식을 의미하며, 연결 설정 과정에서 총 3번의 메세지를 주고 받아 이러한 이름이 붙었다.

소켓은 전 이중(Full-duplex) 방식으로 동작하므로 양방향으로 데이터를 주고받을 수 있다. 따라서 데이터 송수신에 앞서 준비과정이 필요하다. 먼저 연결 요청을 하는 호스트 A가 호스트 B에게 다음 메세지를 전달하고 있다.

[SYN] SEQ: 1000, ACK: -

SYNSynchroniztion의 줄임 말로써, 데이터 송수신에 앞서 전송되는 동기화 메세지라는 의미를 담고 있다.
SEQSequence의 줄임말이다. 소켓 프로그래밍과 TCP/IP 프로토콜에서 SEQ는 데이터 패킷의 순서를 나타내는 데 사용된다.
ACKAcknowledgment의 줄임 말로써, 데이터를 성공적으로 수신했음을 송신자에게 알리는 메세지를 의미한다. 위 메세지는 처음으로 보내는 메세지이므로 ACK 메세지가 비어있는 상태이다.
SYN과 ACK를 묶어 보내는 형식의 메세지를 SYN+ACK 메세지라고 한다.

위 예시의 세 메세지는 각각 다음과 같은 의미를 가지고 있다.

[SYN] SEQ: 1000, ACK: -

내가 지금 보내는 패킷에 1000이라는 번호를 부여한다.

[SYN+ACK] SEQ: 2000, ACK: 1001

내가 지금 보내는 패킷에 2000이라는 번호를 부여한다. 응답 시 다음 번엔 1001번(방금 보낸 패킷에 1을 더한 값) 패킷을 송신하라

[SYN] SEQ: 1001, ACK: 2001

내가 지금 보내는 패킷에 1001이라는 번호를 부여한다. 응답 시 다음 번엔 2001번(방금 보낸 패킷에 1을 더한 값) 패킷을 송신하라

위의 메세지를 성공적으로 주고받으면 소켓들은 성공적으로 연결 설정을 마치게 된다.
패킷에 번호를 부여하고, 이 번호 정보를 상대방에게 알리는 이유는 데이터의 손실을 막기 위해서이다.

데이터의 송수신

데이터를 송수신할 시에는 Three-way handshaking 때와 마찬가지로 SEQ과 ACK를 주고받는다. 단, SYN+ACK의 형태로 보내지는 않으며, 송신하는 측은 SEQ, 수신하는 측은 ACK를 보낸다.

응답하는 쪽은 다음의 공식을 기준으로 ACK 메시지를 전송한다.

ACK 번호 SEQ 번호 + 전송받은 바이트 크기 + 1

마지막에 1을 더한 이유는 Three-way handshaking에도 보았듯이, 다음 번에 전달할 SEQ의 번호를 알리기 위함이다.

데이터의 손실에 대한 재전송을 위해서, TCP 소켓은 ACK 응답을 요구하는 패킷 전송 시에 타이머를 동작시킨다. 그리고 해당 타이머가 Time-out! 되었을 때 패킷을 재전송한다.

Four-way handshaking

연결을 끊을 때, 한 쪽이 일방적으로 연결을 끊어버리면, 상대방이 데이터가 남아있을 때 문제가 되기 때문에 상호간에 연결 종료의 합의과정을 거치게 된다. 이를 Four-way handshaking이라 한다.

소켓 A가 종료 메시지를 소켓 B에게 전달하고, 소켓 B는 해당 메시지의 수신을 소켓 A에게 알린다.
그리고 이어서 소켓 B가 종료 메시지를 소켓 A에게 전달하고, 소켓 A는 해당 메시지의 수신을 소켓 B에게 알리며 종료의 과정을 마치게 된다. 위 그림에서 패킷 안에 삽입되어 있는 FIN은 종료를 알리는 메시지를 뜻한다.
즉, 상호간에 FIN 메시지를 한번씩 주고 받고서 연결이 종료되는데, 이 과정이 네 단계에 걸쳐서 진행되기 때문에 이를 가리켜 Four-way handshaking이라고 한다.


참고자료

윤성우의_열혈_TCP_IP_소켓_프로그래밍