9.10. TCP – 신뢰성 있는 바이트 스트림¶
TCP, 즉 전송 제어 프로토콜(Transmission Control Protocol)은 IP 위에서 동작하는 또 다른 전송 계층 서비스입니다. UDP를 “패킷을 보내고 잘 도착하기를 바라는” 방식이라고 표현한다면, TCP는 “두 종단점 사이에 연결을 열고 이를 양방향 바이트 파이프로 다루며, 상대방이 그 바이트를 반드시, 순서대로, 정확히 한 번 받도록 보장하는” 방식입니다. 대부분의 인터넷 트래픽이 TCP를 사용하며, 카메라가 네트워크에서 하는 일의 대부분도 TCP를 사용합니다.
9.10.1. TCP가 IP에 더하는 것¶
TCP는 UDP보다 훨씬 많은 일을 합니다. TCP는 다음을 추가합니다:
연결. 데이터가 흐르기 전에, 두 종단점은 짧은 핸드셰이크를 주고받아 서로 통신하고 있음을 합의합니다. 연결에는 “열림”, “반-닫힘”, “닫힘”이라는 상태가 있으며, 양쪽 모두 이를 추적합니다.
신뢰성 있는 전달. 송신자가 넣은 모든 바이트는 수신자가 확인 응답합니다. 타임아웃 내에 확인 응답되지 않은 것은 다시 전송됩니다. 애플리케이션은 손실된 바이트를 결코 보지 못하며, 프로토콜이 재전송하는 동안의 지연만을 보게 됩니다.
순서대로 전달. 바이트는 보낸 순서 그대로 도착합니다. 설령 패킷이 수신 측에 순서가 뒤바뀐 채 도착하더라도, TCP는 애플리케이션이 읽기 전에 이를 다시 정렬합니다.
흐름 제어. 수신자가 느리면 송신자에게 속도를 늦추라고 알립니다. 연결은 더 느린 쪽의 속도에 맞춰 적응합니다.
혼잡 제어. 중간 네트워크가 패킷을 버리기 시작하면, 송신자는 상황이 회복될 때까지 전송량을 줄입니다. 이는 어느 한 연결이 포화된 링크를 마비시키는 것을 막아 줍니다.
이 모든 것은 자동으로 이루어집니다. 애플리케이션이 사용하는 Python API는 단순히 send(bytes)와 recv(n)뿐이며, 나머지는 모두 TCP가 그 아래에서 처리합니다.
9.10.2. 핸드셰이크¶
TCP 연결은 데이터가 통과하기 전에 세 단계 교환으로 열립니다:
3단계 핸드셰이크. 양쪽이 모두 확인 응답하고 나면 연결이 열리고 데이터가 흐를 수 있습니다.¶
클라이언트가 연결을 열어달라는 SYN(동기화) 패킷을 보냅니다. 서버는 SYN-ACK(동기화 + 확인 응답)로 응답하며 이를 수락합니다. 클라이언트는 마지막으로 ACK를 보내 확정합니다. 이 왕복 이후 양쪽은 연결이 열렸다는 데 합의하며, 어떤 바이트가 송수신되었는지 추적하기 위한 카운터를 동기화합니다.
핸드셰이크는 첫 유효 바이트가 통과하기 전에 1회 왕복 시간(round-trip-time)만큼의 지연 시간을 소모합니다. 로컬 네트워크에서는 1밀리초 정도이지만, 대륙을 가로지르는 연결에서는 대략 100ms입니다. 이것이 TCP의 주요 비용이며, 짧은 단발성 메시지가 때로는 UDP를 쓰는 편이 나은 이유입니다.
9.10.2.1. 종료 핸드셰이크¶
TCP 연결은 종료할 때도 교환을 거칩니다(각 측에서 보내는 FIN). 어느 쪽이든 자신의 연결 절반을 독립적으로 닫을 수 있습니다. 한쪽은 송신을 끝냈지만 다른 쪽은 여전히 통신 중인 반-닫힘 상태도 적법하지만, 흔하지는 않습니다. 애플리케이션은 보통 close()를 호출하기만 하고, 종료 시퀀스는 프로토콜에 맡깁니다.
9.10.3. TCP가 보장하지 않는 것¶
TCP가 한다고 종종 가정되지만 실제로는 하지 않는 몇 가지:
메시지 경계. 애플리케이션은 메시지 스트림이 아니라 바이트 스트림을 보냅니다. 두 번의
send(b"hello")호출이b"hellohello"라는 하나의recv()로 도착할 수도 있고, 크기가 제각각인 두 번의recv()로 도착할 수도 있습니다. 애플리케이션이 메시지 프레이밍을 원한다면 직접 프레이밍을 추가해야 합니다(개행 문자든, 길이 접두어든, 무엇이든). 예를 들어 TCP로 JSON 문서를 보낼 때는 각 문서를 개행 문자나 다른 구분자로 분리해야 합니다.암호화. TCP는 애플리케이션이 준 바이트를 끝까지 평문 그대로 운반합니다. 내용이 기밀이어야 한다면, 애플리케이션이 연결을 TLS로 감싸야 합니다(암호화된 소켓과 TLS 참조).
인증. TCP는 바이트가 손상 없이 도착했는지 확인합니다. 하지만 누가 보냈는지에 대해서는 아무것도 말해 주지 않습니다. 인증 역시 상위 계층의 관심사입니다.
조용한 연결에서의 생존성. 양쪽이 오랫동안 데이터를 보내지 않으면, 연결은 기술적으로는 여전히 열려 있지만 상대방이 충돌했거나 사라졌음을 감지하지 못합니다. 이것이 중요한 경우에는 소켓에서 Keepalive 탐색을 활성화하여 해결할 수 있습니다.
9.10.4. TCP를 사용할 때¶
TCP는 “클라이언트가 서버에 연결을 열고, 약간의 바이트를 주고받은 뒤, 끝나면 연결을 닫는” 형태에 맞는 거의 모든 대화에 적합한 답입니다. HTTP 및 HTTPS 요청, SSH 세션, 데이터베이스 쿼리, 파일 전송, 이미지 업로드 – 모두 TCP 위에서 이루어집니다.
대화가 그 형태에 맞지 않을 때만 UDP를 사용하세요: 손실을 허용할 수 있는 독립적이고 자기 완결적인 메시지, 멀티캐스트 트래픽, 시간 동기화, 이름 조회, 또는 핸드셰이크 비용을 감당할 수 없는 극도로 지연에 민감한 경우입니다.
포트, UDP, TCP가 모두 갖춰지면서 전송 계층 이야기는 마무리되었습니다. 이 둘을 모두 노출하는 Python API는 다음 페이지에 있습니다: 소켓 객체.