업데이트됨 1개월 전
인터넷을 통해 데이터를 전송할 때, 우리는 패킷을 잃어버리고 순서를 뒤섞고 때로는 같은 것을 두 번 전달하는 네트워크에 그 데이터를 맡깁니다. TCP의 역할은 그 혼돈을 보이지 않게 만드는 것입니다. TCP는 모든 패킷 헤더에 두 개의 숫자를 담아 이를 해냅니다. 하나는 "이것은 1000번 바이트입니다"라고 말하는 시퀀스 번호이고, 다른 하나는 "999번 바이트까지 모두 받았으니, 1000번을 보내주세요"라고 말하는 확인 응답 번호입니다.
이것이 전체 메커니즘입니다. 재전송, 재정렬, 중복 감지 등 나머지 모든 것은 이 두 숫자에서 비롯됩니다.
모든 바이트에는 번호가 있다
TCP는 패킷 단위로 생각하지 않습니다. 바이트 단위로 생각합니다.
데이터를 전송할 때, TCP는 그것을 연속적인 스트림으로 처리합니다. 그 스트림의 각 바이트에는 시퀀스 번호가 있습니다. 시퀀스 번호 1000부터 시작하는 500바이트짜리 세그먼트를 전송한다면, 1000번부터 1499번 바이트를 보낸 것입니다. 다음 세그먼트는 1500번에서 시작합니다.
이 바이트 단위 추적이 TCP를 신뢰할 수 있게 만드는 핵심입니다. 수신자는 어떤 바이트를 받았고 어떤 바이트가 없는지 정확히 압니다. 10001499번 바이트와 20002499번 바이트가 도착했지만 1500~1999번이 오지 않았다면, 수신자는 무엇이 빠져 있는지 정확히 알고 기다릴 수 있습니다.
확인 응답의 관례
여기서 TCP의 우아함이 드러납니다. 확인 응답 번호는 "받았습니다"가 아니라 "다음에 무엇이 필요한지"를 말합니다.
1000~1499번 바이트를 받았다면, 1500으로 확인 응답합니다. 마지막으로 받은 바이트인 1499가 아니라, 다음에 기대하는 바이트인 1500입니다. 이 작은 관례가 모든 것을 단순하게 만듭니다. 송신자는 항상 알 수 있습니다. 확인 응답이 1500이라면, 1500 이전의 모든 것이 안전하게 도착한 것입니다.
확인 응답은 누적됩니다. 세 개의 세그먼트를 연속으로 받았다면, 하나의 확인 응답으로 모두 처리할 수 있습니다. 3000번 바이트에 대한 확인 응답은 1000~2999번 바이트가 모두 도착했음을 암묵적으로 확인합니다. 확인 응답 자체가 손실되더라도, 다음 확인 응답이 같은 내용을 포함합니다.
신뢰성이 생겨나는 방식
송신자는 전송한 모든 것의 복사본을 보관합니다. 확인 응답이 오면 그 데이터를 버릴 수 있습니다. 수신자가 갖고 있기 때문입니다. 타임아웃 기간 내에 확인 응답이 오지 않으면, 송신자는 다시 전송합니다.
수신자는 시퀀스 번호를 사용해 혼돈 속에서 순서를 재조립합니다. 세그먼트는 네트워크를 통해 서로 다른 경로를 거쳐 어떤 순서로든 도착할 수 있습니다. 20002499번 바이트가 10001499번보다 먼저 도착할 수도 있습니다. 수신자는 그것들을 버퍼에 담고 시퀀스 번호를 확인하며 올바른 순서로 애플리케이션에 데이터를 전달합니다.
중복은 감지하기 쉽습니다. 같은 시퀀스 번호를 두 번 받았다면, 이미 그 데이터를 갖고 있는 것이므로 중복된 것을 버리면 됩니다.
연결이 임의의 번호로 시작하는 이유
TCP 연결은 0부터 번호를 세지 않습니다. 세 방향 핸드셰이크 중에, 각 측은 임의의 초기 시퀀스 번호(ISN)를 선택합니다.
이것은 미묘한 문제를 방지합니다. 어제 같은 두 기기와 포트 사이에 연결이 있었다고 상상해 보세요. 그 오래된 연결의 패킷 일부가 네트워크에서 지연되었습니다. 새 연결이 0부터 시작한다면, 그 오래된 패킷들이 유효해 보이는 시퀀스 번호를 가질 수 있습니다. 임의의 ISN은 이러한 충돌 가능성을 극히 희박하게 만듭니다.
핸드셰이크 중에 양측이 서로의 ISN을 교환합니다. 이제 각 측은 상대방의 바이트 스트림이 어디서 시작하는지 압니다. TCP는 전이중 통신입니다. 양측이 동시에 송수신하며, 각자 독립적인 시퀀스 번호 공간을 유지합니다.
1970년대 설계 결정이 지금도 인터넷을 운영한다
시퀀스 번호는 32비트 정수입니다. 0부터 4,294,967,295까지입니다. TCP가 설계될 당시, 4.3GB를 전송하는 데는 영원히 걸릴 것 같았습니다. 시퀀스 공간이 절대 순환하지 않을 것처럼 보였습니다.
오늘날, 10Gbps 연결은 4초도 안 되어 그 카운터를 순환시킵니다.
프로토콜은 적응했습니다. TCP 타임스탬프와 다른 메커니즘들이 이제 "4초 전의 시퀀스 번호 1000"과 "지금의 시퀀스 번호 1000"을 구별하는 데 도움을 줍니다. 하지만 원래의 32비트 필드는 그대로입니다. 하위 호환성은 강력한 힘입니다.
동작 방식 살펴보기
서버의 ISN이 50000인 상태에서 2000바이트 파일을 다운로드한다고 가정해 봅시다:
- 서버가 50000~50999번 바이트를 전송합니다 (시퀀스 번호 50000)
- 클라이언트가 51000으로 확인 응답합니다 ("받았습니다, 51000번부터 보내주세요")
- 서버가 51000~51999번 바이트를 전송합니다 (시퀀스 번호 51000)
- 클라이언트가 52000으로 확인 응답합니다 ("모두 받았습니다")
두 번째 세그먼트가 손실된다면:
- 서버가 50000~50999번 바이트를 전송합니다
- 클라이언트가 51000으로 확인 응답합니다
- 서버가 51000~51999번 바이트를 전송합니다 (전송 중 손실)
- 클라이언트는 계속 51000으로 확인 응답합니다
- 서버가 타임아웃되어, 51000~51999번 바이트를 재전송합니다
- 클라이언트가 52000으로 확인 응답합니다
클라이언트는 손실된 패킷을 알지 못했습니다. 51000이 도착할 때까지 "51000번 바이트가 필요합니다"라고 계속 말했을 뿐입니다.
우아한 단순함
TCP는 가장 단순한 메커니즘을 택했습니다. 바이트에 번호를 부여하고, 수신을 확인하는 것. 그리고 그것으로 신뢰할 수 없는 네트워크 위에서 신뢰할 수 있는 통신을 하기에 충분하게 만들었습니다.
복잡한 협상도 없습니다. 정교한 오류 코드도 없습니다. 그저 "1000~1499번 바이트입니다"와 "1500번부터 보내주세요"가 전부입니다. 나머지는 자연스럽게 따라옵니다.
TCP 시퀀스 번호에 관한 자주 묻는 질문
TCP는 왜 패킷이 아닌 바이트에 번호를 매기나요?
패킷은 네트워크에서 분할되거나 합쳐지거나 크기가 바뀔 수 있습니다. 바이트는 그렇지 않습니다. 바이트 스트림 자체에 번호를 매김으로써, TCP는 데이터가 어떻게 패킷화되는지와 무관하게 작동합니다. 1000~1999번 바이트를 담은 세그먼트는 하나의 패킷으로 전송되든 세 개로 분할되든 정확히 그 바이트들을 포함합니다.
시퀀스 번호가 순환하면 어떻게 되나요?
4.3GB의 데이터가 전송되면, 시퀀스 번호는 4,294,967,295에서 0으로 순환합니다. TCP는 모듈러 연산을 사용해 이것을 투명하게 처리합니다. 현대 TCP는 또한 타임스탬프를 사용해 같은 시퀀스 번호를 가진 오래된 패킷과 새 패킷을 구별합니다. 이는 순환이 몇 초 만에 일어나는 빠른 네트워크에서 매우 중요합니다.
공격자가 시퀀스 번호를 예측할 수 있나요?
초기 TCP 구현은 예측 가능한 ISN을 사용했기 때문에, 자신이 참여하지 않은 연결에 패킷을 주입하는 공격이 가능했습니다. 현대 시스템은 암호학적으로 안전한 난수로 ISN을 생성하여 예측을 사실상 불가능하게 만듭니다.
왜 마지막으로 받은 바이트 대신 다음에 기대하는 바이트를 확인 응답하나요?
모호함을 없애기 위해서입니다. "1499를 확인 응답한다"고 하면, 1499번 바이트를 받았다는 것인지, 1499까지 모든 것을 받았다는 것인지, 1499를 포함해서 모든 것을 받았다는 것인지 불명확합니다. "1500을 확인 응답한다"는 것은 명확합니다. 다음에 1500번 바이트를 보내주세요.
이 페이지가 도움이 되었나요?