TCP 소켓에 존재하는 입출력 버퍼
TCP 소켓의 데이터 송수신에는 경계가 없습니다. 따라서 서버가 한 번의 write 함수호출을 통해 한 번에 40바이트를 전송해도 클라이언트는 네 번의 read 함수호출을 통해 나눠서 수신하는 것이 가능합니다. 어떻게 이게 가능한 것인지 궁금할 수 있습니다. 서버가 40바이트를 보내고 클라이언트가 일단 10바이트만 받았다면, 남은 30바이트는 어디에 있는 것일까요?
사실 write 함수가 호출되는 순간이 데이터가 전송되는 순간이 아니고 read 함수가 호출되는 순간이 데이터가 수신되는 순간이 아닙니다. write 함수가 호출되면 데이터는 출력버퍼로 이동을 하고, read 함수가 호출되면 입력버퍼에 저장된 데이터를 읽어들이게 됩니다. 아래 그림을 봅시다.
위 그림에 보이듯이 write 함수가 호출되면 출력버퍼에 데이터가 전달되어서 상황에 맞게 적절히(상대방의 입력버퍼가 넘치지 않게) 데이터를 상대방의 입력버퍼로 전송합니다. 그러면 상대방은 read 함수를 통해 입력버퍼에 저장된 데이터를 읽습니다. 이러한 입출력 버퍼의 특성 몇 가지를 정리해보겠습니다.
- 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재합니다.
- 입출력 버퍼는 소켓 생성시 자동으로 생성됩니다.
- 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄집니다.
- 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸됩니다.
TCP 내부 동작원리 1: 상대 소켓과의 연결
소켓은 전 이중(Full-duplex) 방식으로 동작하므로 데이터를 양방향으로 주고받을 수 있습니다. 따라서 데이터 송수신에 앞서 준비과정이 필요합니다. TCP 소켓은 연결설정 과정에서 세 번의 대화를 주고받습니다. 그래서 이를 가리켜 'Three-way handshaking'이라고 합니다. 아래 그림을 보면서 더 설명하겠습니다.
먼저 처음 연결요청을 하는데, 이 때 사용되는 메시지를 SYN(Synchronization)이라고 하는데, 이 과정에서 호스트 A가 B에게 하는 말은 다음과 같습니다.
"내가 지금 보내는 패킷에 1000이라는 번호를 부여하니, 잘 받았다면 1001번 패킷을 전달하라고 말해줘"
그 다음에 호스트 B가 A에게 A가 전송한 패킷에 대한 응답으로 ACK 1001과 함께 호스트 B의 데이터 전송을 위한 동기화 메시지 SEQ 2000을 함께 묶어서 보냅니다. 그래서 이러한 유형의 메시지를 SYN+ACK이라고 합니다.
"좀 전에 전송한 SEQ가 1000인 패킷은 잘 받았으니, 다음 번에는 SEQ가 1001인 패킷을 전송해줘. 그리고 내가 지금 보내는 패킷에 2000이라는 번호를 부여하니, 잘 받았다면 2001번 패킷을 전달하라고 말해줘"
이제 마지막으로 A가 B에게 전송한 패킷에 대한 응답으로 ACK 2001과 앞서 보낸 패킷의 SEQ가 1000이었으니 1 증가된 1001을 SEQ에 부여하여 전송하였습니다. 이 과정에서 A가 B에게 하는 말은 다음과 같습니다.
"좀 전에 전송한 SEQ가 2000인 패킷은 잘 받았으니, 다음 번에는 SEQ가 2001인 패킷을 전송해줘"
이렇게 해서 호스트 A와 B가 상호간에 데이터 송수신을 위한 준비가 되었음을 인식하게 되었습니다.
TCP의 내부 동작원리 2: 상대 소켓과의 데이터 송수신
데이터 송수신의 기본 방식은 다음과 같습니다. 그림을 보며 설명하겠습니다.
위 그림은 호스트 A가 호스트 B에게 총 200바이트를 두 개의 패킷에 나눠서 전송하는 과정을 보인 것입니다. 먼저 호스트 A가 100바이트를 하나의 패킷에 실어 전송하였고, 패킷의 SEQ를 1200으로 부여했습니다. 호스트 B는 이를 근거로 패킷이 제대로 수신되었음을 알려야 하기에, ACK 1301을 담은 패킷을 호스트 A에 전송하고 있습니다. 이 때 1201이 아니라 1301인 이유는 ACK 번호를 전송된 바이트 크기만큼 추가로 증가시켰기 때문입니다. 이렇게 ACK 번호를 전송된 바이트 크기만큼 증가시켜주지 않으면 패킷에 담긴 100바이트가 모두 전송되었는지, 아니면 그 중 일부가 손실됐는지 알 방법이 없기 때문에 다음의 공식을 기준으로 ACK 메시지를 전송합니다.
ACK 번호 = SEQ 번호 + 전송된 바이트 크기 + 1
중간에 문제가 발생해서 패킷 전송에 실패한 경우를 살펴보겠습니다.
위 그림에서 호스트 A는 패킷에 100 바이트의 데이터를 실어서 호스트 B로 전송했는데, 중간에 문제가 생겨서 호스트 B에 이가 전송되지 못했습니다. 이 경우 호스트 A는 일정 시간이 지나도 SEQ 1301에 대한 ACK 메시지를 받지 못하기 때문에 재전송을 진행합니다. 이렇듯 TCP 소켓은 ACK 응답을 요구하는 패킷 전송 시에 타이머를 전송시켜서 타임 아웃이 된다면 패킷을 재전송합니다.
TCP 소켓의 내부 동작원리 3: 상대 소켓과의 연결종료
TCP 소켓은 연결종료를 하기 위해 상호간에 합의과정을 거치게 됩니다. 다음은 두 소켓의 대화과정입니다.
- 소켓 A: 저는 지금 연결을 끊고 싶어요.
- 소켓 B: 아 그러신가요 잠시만 기다려 주세요.
- 소켓 B: 저도 준비가 끝났습니다. 연결을 끊읍시다.
- 소켓 A: 알겠습니다. 연결을 끊겠습니다.
이를 그림으로 나타내면 다음과 같습니다.
위 그림에서 FIN은 종료를 알리는 메시지를 뜻합니다. 즉 상호간에 FIN 메시지를 한 번씩 주고 받고서 연결이 종료되는데, 이 과정이 네 단계에 걸쳐 진행되므로 이를 'Four-way handshaking'이라고 합니다. SEQ와 ACK에 대한 이해는 충분히 됐을 것이니 추가적인 설명은 하지 않겠습니다. 위 대화와 그림을 보며 스스로 생각해보세요.
이렇게 TCP 프로토콜의 기본이 되는 TCP 흐름제어(Flow Control)을 설명했습니다.
'네트워크 프로그래밍 > C' 카테고리의 다른 글
[네트워크 프로그래밍/C] UDP 기반 서버, 클라이언트의 구현 (0) | 2022.02.25 |
---|---|
[네트워크 프로그래밍/C] UDP에 대한 이해 (0) | 2022.02.25 |
[네트워크 프로그래밍/C] TCP 기반 서버, 클라이언트 구현 (0) | 2022.02.22 |
[네트워크 프로그래밍/C] TCP와 UDP에 대한 이해 (0) | 2022.02.22 |
[네트워크 프로그래밍/C] 인터넷 주소의 초기화와 할당 (0) | 2022.02.22 |