이제까지 신뢰적인 데이터 전송의 원칙을 다루었다. 이제 TCP~! 연결형, 신뢰적인 트랜스포트 프로토콜을 공부해보자.
3.5.1 TCP 연결
데이터 전송을 보장하는 파라미터들을 각자 설정하기 위해 세그먼트들을 보내야한다. 그래야 핸드셰이크가 되니깐.
TCP 연결은 회선 교환 네트워크에서와 같은 종단 간의 TDM이나 FDM이 아니다. 대신 연결은 두 종단 시스템의 TCP에 존재하는 상태를 공유하는 논리적인 것이다. TCP는 오직 종단에서만 동작하고 중간의 네트워크 요소(라우터, 링크 계층 스위치)에선 동작 않으므로, 중간의 네트워크 요소들은 TCP 연결 상태를 유지하지 않는다. 사실, 중간 라우터들은 TCP 연결을 전혀 감지 못한다. 즉, 이들은 연결은 보지 못하고 데이터그램만 본다.
TCP 연결은 전이중 서비스(full-duplex service)를 제공한다. 만약 호스트 a와 b사이에 TCP 연결이 있다면, 애플리케이션 계층 데이터는 b → a, a → b 가 동시에 가능하다! 또한 TCP 연결은 항상 단일 송신자와 단일 수신자 사이의 점대점이다. 단일 송신 동작으로 한 송신자가 여러 수신자에게 데이털르 전송하는 멀티캐스팅은 TCP에서는 불가하다. TCP에서 두 호스트는 동료고 셋은 만원이다.
이제 TCP 연결이 어케 설정되는지 보자. 한 호스트에서 동작하는 프로세스가 다른 호스트의 프로세스와 연결을 초기화하기를 원한다고 가정하자. 클라 프로세스는 서버의 프로세스와 연결을 설정하기 원한다고 TCP 클라에게 먼저 알린다. 2.7.2에서 파이썬 클라 프로그램에 다음 명령으로 이 기능 수행했음.
clientSocket.connect((serverName, serverPort))
클라의 트랜스포트 계층은 서버의 TCP와의 TCP 연결 설정을 진행한다. 이 절의 마지막에 연결 설정 절차를 자세히 설명함. 지금은 클라가 먼저 특별한 TCP 세그먼트를 보낸다는것을 아는 정도로 충분. 서버는 두 번째 특별한 TCP 세그먼트로 응답. 마지막으로, 클라가 세 번째 특별한 세그먼트로 응답한다. 처음 2개의 세그먼트에는 페이로드, 즉, 앱 계층 데이터가 없다. 세 번째 세그먼트는 페이로드를 포함할 수 있다. 두 호스트 사이에 3개의 세그먼트가 보내지므로 흔히 three way handshake 라 부른다.
일단 TCP 연결이 되면, 두 앱 프로세스는 서로 데이터를 보낼 수 있다. 클라 프로세스에서 서버 프로세스로의 데이터 송신을 고려해보자. 클라 프로세스는 소켓을 통해 데이터의 스트림을 전달한다. 데이터가 소켓 통해 전달되면, 이제 데이터는 클라에서 동작하고 있는 TCP에 맡겨진다. 그림 3.28에 보이듯이 TCP는 초기 세 방향 핸드셰이크 동안 준비된 버퍼중의 하나인 송신 버퍼로 데이터 보낸다. 때때로 TCP는 송신 버퍼에서 데이터 묶음을 만들어 네트워크로 보낸다. TCP 명세서(RFC 793)에서 TCP ‘자신이 편한 대로 세그먼트의 데이터를 전송’해야 한다고 기술하고 있으며, TCP가 언제 버퍼된 데이터를 전송해야하는지는 미기술되어있다. 세그먼트로 모아 담을 수 있는 최대 데이터 양은 최대 세그먼트 크기(maximum segement size, MSS)로 제한된다. MSS는 일반적으로 로컬 송신 호스트에 의해 전송될 수 있는 가장 큰 링크 계층 프레임의 길이(최대 전송 단위 maximum trasmission unit, MUT)에 의해 일단 결정되고, 후에 TCP 세그먼트와 TCP/IP 헤더 길이가 단일 링크 계층 프레임에 딱 맞도록 하여 정해진다. 이더넷과 PPP 링크 계층 프로토콜은 모두 1500 바이트의 MTU 갖는다. 따라서 MSS의 일반적 값은 1460바이트다. 출발지에서 목적지까지 모든 링크상에 전송될 수 있는 가장 큰 링크 계층 프레임인 경로 MTU를 찾고 이 경로 MTU 값에 근거한 MSS 설정을 위한 방식들도 제안되고 있다. MSS가 헤더를 포함하는 TCP 세그먼트의 최대 크기가 아니라, 세그먼트에 있는 앱 계층 데이터에 대한 최대 크기라는 점 주의(혼동되지만 우린 이 용어 써야됨)

TCP는 TCP 헤더와 클라이언트 데이터를 하나로 짝지어 TCP 세그먼트를 구성한다. TCP가 세그먼트를 수신했을 때, 그림 3.28처럼 세그먼트 데이터는 TCP 연결의 수신 버퍼에 위치한다. 앱은 이 버퍼로부터 데이터 스트림을 읽는다. 연결의 양 끝은 각각 자신의 송신 버퍼와 수신 버퍼 가지고있다.
TCP 연결은 한쪽 호스트에서의 버퍼, 변수, 프로세스에 대한 소켓 연결과 다른 쪽 호스트에서의 버퍼, 변수, 소켓 연결의 집합으로 이루어진다는 사실을 알 수 있다.
이런 버퍼, 변수 같은건 호스트 간 네트워크 요소인 라우터, 스위치, 리피터의 연결에는 해당 안된다.
3.5.2 TCP 세그먼트 구조
TCP 세그먼트는 헤더 필드 + 데이터 필드. MSS는 세그먼트의 데이터 필드의 크기를 제한한다. TCP가 이미지 같은 큰 파일 전송할 때, 일반적으로 MSS 크기로 파일을 분절한다.(통상 MSS 보다 작은 마지막 분절은 제외). 그러나 많은 대화식 앱은 MSS보다 작은 양의 데이터를 전송한다. 예를 들면, 텔넷과 ssh에 전송되는 세그먼트는 단지 21바이트 정도.
그림 3.29는 TCP 세그먼트의 구조를 보여준다. UDP 처럼 헤더는 상위 계층 앱으로부터 다중화와 역다중화를 하는데 사용되는 출발지와 목적지 포트번호를 포함한다. 또 UDP 처럼 헤더는 체크섬 필드를 포함한다. TCP 세그먼트 헤더는 또한 다음과 같은 필드를 포함한다.

- 32 비트 순서 번호 필드와 32비트 확인응답 번호 필드는 전에 논의한것처럼 신뢰적인 데이터 전송 서비스 구현에서 TCP 송신자와 수신자에 의해 사용된다.
- 16비트 수신 윈도 필드는 흐름 제어에 사용. 이는 수신자가 받아들이려는 바이트의 크기를 나타내는 데 사용된다는 것을 곧 보게 될 것
- 4비트 헤더 길이 필드는 32비트 워드 단위로 TCP 헤더의 길이를 나타냄. TCP 헤더는 TCP 옵션 필드 덕에 가변적인 길이가 될 수 있다. 일반적으로 옵션 필드는 일반적인 TCP 길이가 20바이트 되도록 비어있다.
- 선택적, 가볍적인 길이의 옵션필드는 송신자와 수신자가 최대 세그먼트 크기(MSS)를 협상하거나 고속 네트워크에서 사용하기 위한 윈도 확장 요소로 이용된다. 타임스탬프 옵션 또한 정의됨.
- 플래그 필드는 6비트를 포함한다. ACK 비트는 확인응답 필드에 있는 값이 유용함을 가리키는데 사용. 즉, 이 세그먼트는 성공적으로 수신된 세그먼트에 대한 확인응답을 포함한다. RST, SYN, FIN 비트는 연결 성정, 해제에 사용된다. PSH 비트가 설정될 때, 이건 수신자가 데이터를 상위계층으로 즉시 전달해야한다는걸 가리킨다. 마지막으로, URG 비트는 이 세그먼트에서 송신 측 상위 계층 개체가 ‘긴급’으로 표시하는 데이터임을 가리킨다. 이 긴급 데이터의 마지막 바이트의 위치는 16비트 긴급 데이터 포인터 필드에 의해 가리켜진다. TCP는 긴급 데이터가 존재할 때 수신 측 상위계층 개체에 통지해야하고 긴급 데이터의 끝에 대한 포인터를 전달한다.(PSH, URG 그리고 긴급 데이터에 대한 포인터는 실제 사용되지 않지만 완벽하게 하도록 이런 필드를 언급한것)
순서 번호와 확인응답 번호
48~
- TCP 헤더에서 가장 중요한 필드 두 가지는 순서 번호 필드와 확인 응답 번호 필드.
- TCP는 데이터를 구조화되어 있지 않고 단지 순서대로 정렬되어 있는 바이트 스트림 으로 본다.
- 세그먼트에 대한 순서 번호 == 세그먼트에 있는 첫 번째 바이트의 바이트 스트림 번호
- 호스트가 자신의 세그먼트에 삽입하는 확인응답 번호는 상대 호스트로 부터 기대하는 다음 바이트의 순서 번호
- TCP는 스트림에서 첫 번째 잃어버린 바이트까지의 바이트들까지만 확인응답하기 때문에, TCP는 누적 확인응답(cumulative acknowledgement)를 제공한다고 한다.
- 세그먼트가 순서에 맞지 않게 수신되었을 때 행동 후보
-
- 수신자가 순서 안 맞는 세그먼트 버린다
-
- 순서 바뀐거 들고 있고, 빈 공간에 채울 세그먼트 기다린다(이쪽이 효율적이며, 실제로 선택한 방식)
-
- 실제 구현에서 TCP 의 시작 순서 번호는 각 호스트마다 랜덤이다.
텔넷: 순서 번호화 응답확인 번호 사례연구
49~
- 텔넷은 원격 로그인을 위해 사용되는 유명한 애플리케이션 계층 프로토콜
- TCP 상에서 동작
- 텔넷은 대화형 애플리케이션
- 패스워드를 포함한 텔넷 연결에서 전송된 데이터는 암호화가 되지 않아서 도청 공격에 취약하므로 텔넷보다 SSH 프로토콜이 선호됨

- Seq는 송신자가 보내는 데이터 바이트의 번호를 나타내는 32비트 숫자
- 데이터의 순서를 보장하고, 재전송 시 위치를 식별
- Ack는 수신자가 다음에 받아야 할 바이트 번호를 알려주는 32비트 숫자
- “여기까지 받았으니 그 다음부터 보내세요”라는 의미
3.5.3 왕복 시간(RTT) 예측과 타임아웃
왕복 시간 예측
재전송 타임아웃 주기의 설정과 관리
3.5.4 신뢰적인 데이터 전송
- 네트워크 계층은 비신뢰적
- 인터넷 프로토콜은 데이터그램 전달 보장하지 않는다.
- 데이터그램 순서대로 전달 보장하지 않는다.
- TCP는 IP의 비신뢰적인 최선형 서비스에서 신뢰적인 데이터 전송 서비스를 제공한다.
- 프로세스가 자신의 수신 버퍼로부터 읽은 데이터 스트림이 손상, 손실, 중복이 없고 순서가 유지됨을 보장한다.
- 이 장에서 설명하는 TCP는 단일 재전송 타이머를 쓴다. 각각의 세그먼트마다 타이머 다는게 아닌. 타이머 관리 오버헤드 방지 차원
- 송신자의 SendBase 는 수신 확인응답이 확인되지 않은 가장 오래된 바이트의 순서 번호(그러므로 Sendbase - 1 은 수신자에게서 정확하게 차례대로 수신되었음을 알리는 마지막 마지막 바이트의 순서 번호)
몇 가지 흥미로운 시나리오

타임아웃 주기의 두 배로 설정
대부분 TCP 구현에서 사용하는 몇 가지 수정사항.
- 첫 번째는 타이머의 종료 후 타임아웃 주기의 길이에 대한 것
- 이 수정에서는 타임아웃이 발생할 때마다 TCP는 아직 확인응답이 안 된 가장 작은 순서 본호를 가진 세그먼트를 재전송한다. 그러나 TCP는 재전송마다 마지막
EstimatedRTT와DevRTT로부터 타임아웃 값을 가져오는게 아닌 타임 아웃 주기를 이전의 2배로 설정한다. - 이 수정은 제한된 형태의 혼잡 제어 제공
- 타이머 종료는 주로 네트웍 혼잡에 의해 발생한다. 즉, 출발지-목적지 사이의 경로에서 하나이상의 라우터 버퍼에 도착한 많은 패킷은 손실이나 오랜 큐 대기의 원인
- 혼잡할때 지속해서 재전송 고집하면 혼잡이 더 심화 될 것
- 그래서 재전송대신 도 긴간격으로 재전송하도록 한 것.
빠른 재전송
59
-
타임아웃 유발하는 재전송의 한 문제는 타임아웃 주기가 때때로 비교적 긴 것.
-
다행히, 송신자는 종종 중복 ACK에 의해서 타임아웃전에 패킷 손실을 발견한다.
-
중복 ACK는 송신자가 이미 이전에 받은 확인응답에 대한 재확인응답 세그먼트 ACK다.
-
중복 ACK 대한 송신자 반응 이해위해, 먼저 수신자가 중복된 ACK 보낸 이유 알아야한다.
-
다음 표는 TCP 수신자의 ACK 생성 정책을 요약한 것.

-
수신자는 여튼 뭐든 데이터 받으면 데이터 스트림 순서 번호, 간격을보고 문제가 있으면 그냥 자기가 원하는 순서가 포함된 확인응답을 송신자한테 보내버린다.

-
위 그림을 보면 3개의 세그먼트가 수신자에게서 무시가되고 100번째를 보내라고 계속 확인응답을 한다.
-
두 번째 세그먼트 잃어버린 경우를 보면 그냥 타이머 만료전에 재전송한다. 3개의 중복 ACK를 수신했을때!
-
이를 빠른 재전송
GBN인가 SR인가?
TCP는 GBN, SR 중 뭔가?
TCP 확인응답은 누적되고 올바르게 수신되지만, 순서가 잘못된 세그먼트는 수신자가 개별적으로 ACK 받지 않는점 기억하길 바람.
그림3.33(3.19도 참고)에서 볼 수 있듯이, TCP 송신자는 전송했지만 확인응답이 안 된 바이트의 가장 작은 순서번호(SendBase)와 전송 될 다음 바이트의 순서 번호(NextSeqNum)을 유지해야함.
위 관점에서는 TCP가 GBN 과 비슷해보임. 그러나, 몇가지 차이 있다.
많은 TCP 구현에서 올바르게 수신됬지만 순서가 바뀐건 버퍼링 함. 예를 들어, 100번까지 잘 옴. 그런데 여기서 80이 빠짐. 이때 GBN은 80부터 100까지 죄다 다시 재응답. 반면, TCP는 80만 재응답
TCP에 수정 제안된 선택적 확인응답이라고 하는 것은 TCP 수신자가 마지막으로 수신된 ‘순서가 맞는’ 세그먼트에 대해 누적 확인응답하기보다는 ‘순서가 틀린’ 세그먼트에 대해 선택적으로 확인응답하게 한다.
선택적 재전송과 결합하면 TCP 원래의 SR 프로토콜과 유사하다. 그래서 TCP의 오류 복구 메커니즘은 GBN과 SR 프로토콜의 혼합으로 분류하는게 적당함.
3.5.5 흐름 제어
61
TCP 연결이 순서대로 바이트를 수신할 때 TCP는 데이터를 수신 버퍼에 저장한다.
수신 버퍼가 오버플로우 되는 것을 방지하기 위해 애플리케이션에게 흐름 제어 서비스(flow control service)를 제공한다. 이와 같이 흐름 제어는 속도를 일치시키는 서비스. 읽는 속도와 전송하는 속도를 같게 한다.
흐름 제어와 혼잡 제어는 수행되는 동작들이 비슷하지만(송신자의 억제), 흐름 제어와 혼합 제어는 명백히 각기 다르 목적으로 수행된다.
이번 장에 나오는 TCP 구현은 TCP 수신자가 순서가 바뀐 세그먼트를 버린다고 가정한다.
TCP는 송신자가 수신 윈도라는 변수를 유지하여 흐름 제어를 제공한다. 수신 윈도는 수신 측에서 가용한 버퍼 공간이 얼마나 되는지를 송신자에게 알려주는데 사용된다. TCP는 전이중이므로 연결의 각 측의 송신자는 별개의 수신 윈도를 유지한다.
TCP(Transmission Control Protocol)는 신뢰성 있는 데이터 전송을 보장하기 위해 다양한 메커니즘을 사용하는데, 그 중 하나가 바로 **흐름 제어(Flow Control)**입니다. 흐름 제어는 송신자가 수신자를 과도하게 빠르게 데이터를 보내 압도하는 것을 방지하여, 수신자가 데이터를 처리하고 버퍼링할 충분한 시간을 확보하도록 돕는 기능입니다.
쉽게 말해, “빨리 줘!”라고 외치는 수신자의 처리 속도에 맞춰 송신자의 속도를 조절하는 것입니다.
TCP 흐름 제어의 핵심 원리: 슬라이딩 윈도우 (Sliding Window) 및 수신 윈도우 (Receive Window)
TCP 흐름 제어는 주로 슬라이딩 윈도우(Sliding Window) 프로토콜과 수신 윈도우(Receive Window, RWIN) 개념을 통해 이루어집니다.
-
수신 윈도우 (Receive Window, RWIN):
- 수신자는 자신의 버퍼(데이터를 임시로 저장하는 공간)에 남아있는 여유 공간의 크기를 송신자에게 알려줍니다. 이 여유 공간 크기가 바로 수신 윈도우 크기입니다.
- 이 값은 TCP 헤더의 “Window Size” 필드에 포함되어 ACK 패킷을 통해 송신자에게 전달됩니다.
-
슬라이딩 윈도우 (Sliding Window):
- 송신자는 수신 윈도우 크기만큼의 데이터만 송신할 수 있습니다. 즉, 아직 수신자로부터 ACK(승인)를 받지 못한 상태에서도 수신 윈도우 크기 내에서는 계속해서 데이터를 보낼 수 있습니다.
- 데이터가 성공적으로 수신되고 ACK를 받으면, 송신자의 윈도우는 “슬라이딩” 즉, 앞으로 이동하면서 새로운 데이터를 보낼 수 있는 공간이 확보됩니다.
TCP 흐름 제어 동작 방식 (단계별 설명)
- 초기 연결 설정 (3-way handshake): 연결이 설정될 때, 양측은 초기 윈도우 크기를 교환합니다.
- 데이터 전송:
- 송신자는 자신의 송신 윈도우(처음에는 수신자의 초기 수신 윈도우 크기) 범위 내에서 데이터를 보냅니다.
- 송신 윈도우는 아직 ACK를 받지 못한 데이터의 양을 나타내며, 이 크기는 수신 윈도우 크기에 의해 제한됩니다.
- 수신자의 ACK 및 윈도우 업데이트:
- 수신자는 데이터를 받으면 이를 버퍼에 저장하고 처리합니다.
- 데이터를 성공적으로 받으면, 수신자는 송신자에게 ACK 패킷을 보냅니다.
- 이 ACK 패킷에는 다음에 받기를 기대하는 시퀀스 번호뿐만 아니라, 현재 수신자의 버퍼에 남아있는 여유 공간(수신 윈도우 크기) 정보도 함께 포함됩니다.
- 송신자의 윈도우 조정:
- 송신자는 수신자로부터 받은 ACK 패킷에 포함된 새로운 수신 윈도우 크기를 확인합니다.
- 송신자는 이제 이 새로운 수신 윈도우 크기를 자신의 최대 송신 가능한 윈도우 크기로 설정하고, 그 범위 내에서만 데이터를 보냅니다.
- 버퍼 고갈 시:
- 만약 수신자의 버퍼가 가득 차서 더 이상 데이터를 받을 수 없게 되면, 수신자는 수신 윈도우 크기를 ‘0’으로 설정하여 송신자에게 ACK를 보냅니다.
- 송신자는 이 ‘0’ 윈도우 크기를 받으면, 수신자가 데이터를 받을 준비가 될 때까지 데이터 전송을 일시 중단합니다.
- 버퍼 여유 공간 확보 시 (Zero Window Probe):
- 수신자가 버퍼의 데이터를 처리하여 여유 공간이 생기면, 수신자는 새로운 수신 윈도우 크기(0보다 큰 값)를 송신자에게 다시 ACK로 보냅니다.
- 송신자는 이 새로운 윈도우 크기를 받으면, 다시 데이터 전송을 재개합니다. (만약 수신자가 윈도우 업데이트 ACK를 보내지 않을 경우, 송신자는 주기적으로 작은 “제로 윈도우 프로브(Zero Window Probe)” 패킷을 보내 수신자의 상태를 확인합니다.)
핵심 요약
- 목적: 송신자가 수신자를 압도하지 않도록 속도 조절.
- 주요 메커니즘:
- 수신 윈도우(Receive Window, RWIN): 수신자의 버퍼 여유 공간을 송신자에게 알림.
- TCP 헤더의 Window Size 필드: RWIN 정보를 실어 나름.
- 슬라이딩 윈도우: 송신자가 RWIN 범위 내에서만 데이터를 보내도록 함.
- 동작: 수신자가 ACK를 보낼 때마다 현재 자신의 버퍼 상황(RWIN)을 알려주며, 송신자는 이 정보를 바탕으로 자신의 송신 속도와 양을 조절한다.
흐름 제어 vs. 혼잡 제어 (Confusion Alert!)
많은 사람들이 흐름 제어와 혼잡 제어를 혼동합니다.
- 흐름 제어 (Flow Control): 송신자와 수신자 간의 속도 불균형을 조절하는 데 목적이 있습니다. 즉, 수신자의 처리 능력에 맞추는 것입니다.
- 혼잡 제어 (Congestion Control): 네트워크(라우터, 링크 등)의 혼잡을 피하여 전체적인 네트워크 성능을 최적화하는 데 목적이 있습니다. 즉, 네트워크 자체의 용량에 맞추는 것입니다.
TCP는 이 두 가지를 모두 고려하여 송신할 데이터의 양을 결정합니다. 실제로 송신자가 보낼 수 있는 최대 데이터 양은 **min(수신 윈도우 크기, 혼잡 윈도우 크기)**에 의해 결정됩니다.
3.5.6 TCP 연결 관리
TCP 연결이 어떻게 설정되고 해제되는지 더 자세히 알아본다.
인기있는 SYN 플러드 공격을 비롯한 일반적인 네트워크 공격은 대부분은 TCP 연결 관리의 취약점을 악용한다.

TCP가 연결되는 과정
- 단계: 클라측 TCP는 서버 TCP에게 특별한 세그먼트 송신. 이 세그먼트에는 애플리케이션 계층 데이터를 포함하지 않는다. 그러나 세그먼트의 헤더에 SYN 비트라 불리는 하나의 플래그 비트를 1로 설정한다. 이런 이유로 이 특별한 세그먼트를 SYN 세그먼트라 부른다. 추가로 클라는 최초의 순서 번호 client_isn을 임의로 선택하고, 최초의 TCP SYN 세그먼트의 순서 번호 필드에 이 번호를 넣는다. 이 세그먼트는 IP 데이터그램 안에서 캡슐화 되고 서버로 송신된다. 보안 이슈가 생길 수 도 있으니 client_isn 임의 추출하는것 유의하기
- 단계: TCP SYN 시그먼트를 포함하는 IP 데이터그램이 서버 호스트에 도착하면, 서버는 데이터그램으로 부터 TCP SYN 세그먼트를 추출한다. 그리고 연결에 TCP 버퍼와 변수를 할당한다. 그리고 클라 TCP로 연결 승인 세그먼트를 송신한다. 또한 이 연결 승인 세그먼트도 앱 계층 데이터를 포함하지 않는다. 그러나 세그먼트 헤더안에 3개의 중요한 정보를 포함한다. 첫째, SYN 비트는 1로 설정. 둘째, TCP 세그먼트 헤더의 확인응답 필드는 client_isn + 1 로 설정. 마지막으로, 서버는 자신의 최초 순서 번호(server_isn) 선택하고, TCP 세그먼트헤더의 순서 번호 필드에 이 걸 넣는다.
- 이런거다. “나는 당신의 최초 순서 번호 client_isn을 가지고 연결을 시작하자는 당신의 SYN 패킷을 수신함. 나는 이 연결 설정에 동의. 나 자신의 최초 순서 번호는 server_sin”
- 이 연결 승인 세그먼트는 때때로 SYNACK 세그먼트로 불림
- 단계: 연결 승인 세그먼트 수신하면, 클라는 연결에 버퍼와 변수를 할당함. 그 다음 클라 호스트는 서버로 또 다른 세그먼트 송신. 이 마지막 세그먼트가 서버의 연결 승인 세그먼트를 확인하는 것. 연결이 설정 되었기에 SYN 비트는 0이됨. 이 단계에서는 클라에서 서버로의 데이터를 세그먼트 페이로드에서 운반 가능
네, TCP 연결을 끊는 방법에 대해 아주 명확하게 설명해 드리겠습니다.
TCP 연결을 끊는 것은 단순히 “선을 자르는 것”이 아니라, “이제 통신을 마칩시다”라고 양측이 정중하게 합의하는 과정에 가깝습니다. 이 과정을 이해하는 것은 네트워크 프로그래밍의 기본 중 기본입니다.
TCP 연결을 끊는 방법은 크게 두 가지로 나뉩니다.
- 정상적인 연결 종료 (Graceful Closure): 4-Way Handshake
- 강제적인 연결 종료 (Abrupt Closure): RST 패킷
1. 정상적인 연결 종료: 4-Way Handshake
가장 일반적이고 표준적인 방법입니다. 마치 전화를 끊을 때 “나 이제 끊을게” → “응, 알았어” → “너도 할 말 다했지?” → “응, 나도 끊을게” 하고 서로 확인 후 끊는 것과 같습니다. 데이터가 유실될 위험 없이 안전하게 연결을 종료하는 것이 목적입니다.
과정 (A가 B에게 연결 종료를 요청하는 경우)
-
A → B : FIN (Finish)
- A가 B에게 “나는 더 이상 보낼 데이터가 없어. 연결을 끊고 싶어” 라는 의미로
FIN플래그가 설정된 패킷을 보냅니다. - A는 이제 데이터를 보낼 수는 없지만, B로부터 오는 데이터는 여전히 받을 수 있는 상태(HALF-CLOSE)가 됩니다.
- A가 B에게 “나는 더 이상 보낼 데이터가 없어. 연결을 끊고 싶어” 라는 의미로
-
B → A : ACK (Acknowledgement)
- B는 A의
FIN패킷을 받고, “알았어, 너가 연결 끊고 싶어하는 거 잘 받았어” 라는 의미로ACK패킷을 응답으로 보냅니다. - 이때까지는 B는 아직 A에게 보내야 할 데이터가 남아있을 수 있습니다. 그 데이터를 마저 보냅니다.
- B는 A의
-
B → A : FIN
- B도 자신의 모든 데이터를 다 보냈다면, “나도 이제 보낼 데이터가 없어. 연결을 끊을 준비가 됐어” 라는 의미로
FIN패킷을 A에게 보냅니다.
- B도 자신의 모든 데이터를 다 보냈다면, “나도 이제 보낼 데이터가 없어. 연결을 끊을 준비가 됐어” 라는 의미로
-
A → B : ACK
- A는 B의
FIN패킷을 받고, “알았어, 너도 준비됐구나. 그럼 진짜로 연결을 종료하자” 라는 의미로 마지막ACK패킷을 보냅니다. - 이 마지막
ACK패킷을 받은 B는 즉시 연결을 닫습니다. - A는 혹시 모를 네트워크 오류로 B가 마지막
ACK를 받지 못했을 경우를 대비해, 일정 시간 동안 기다린 후(이를 TIME_WAIT 상태라고 합니다) 연결을 완전히 닫습니다.
- A는 B의
프로그래밍에서의 구현
대부분의 언어에서 소켓(Socket) 객체의 close() 또는 disconnect() 메소드를 호출하면 이 4-Way Handshake 과정이 내부적으로 자동으로 일어납니다.
close(): 읽기와 쓰기 채널을 모두 닫습니다. 일반적으로 이 함수를 호출하면 운영체제가 알아서 4-Way Handshake를 시작합니다.shutdown(): 읽기 또는 쓰기 채널 중 하나만 선택적으로 닫을 수 있습니다. 예를 들어,shutdown(SHUT_WR)을 호출하면 “더 이상 보낼 데이터는 없지만 받는 건 계속할게” (HALF-CLOSE) 상태를 명시적으로 만들 수 있으며, 이는 4-Way Handshake의 1단계(FIN 보내기)를 유발합니다.
2. 강제적인 연결 종료: RST (Reset) 패킷
이것은 비정상적이거나 예외적인 상황에서 연결을 즉시, 일방적으로 끊어버리는 방법입니다. “대화 중에 그냥 전화를 끊어버리는 것”과 같습니다.
과정
- 한쪽에서
RST플래그가 설정된 패킷을 상대방에게 보냅니다. RST패킷을 받은 쪽은 더 이상 확인(ACK) 응답 없이 그 즉시 연결을 끊습니다.- 4-Way Handshake와 같은 복잡한 절차가 없으므로 매우 빠릅니다.
- 주의: 버퍼에 남아있던 데이터나 전송 중이던 데이터는 모두 유실됩니다.
RST가 발생하는 주요 상황
- 존재하지 않는 포트로 접속 시도: 서버에 열려있지 않은 포트로 클라이언트가 접속을 시도하면, 서버는 “그런 포트 없어”라는 의미로
RST를 보냅니다. - 비정상적인 종료: 한쪽의 애플리케이션이 오류로 강제 종료되어 소켓이 닫히면, 운영체제는 연결되어 있던 상대방에게
RST를 보내 연결이 비정상적으로 끊겼음을 알립니다. - 오랜 시간 유휴 상태: 방화벽 등에서 너무 오랫동안 아무런 통신이 없는 TCP 연결을 타임아웃으로 판단하고 강제로 끊어버릴 때
RST를 양쪽에 보낼 수 있습니다.
핵심 요약
| 구분 | 정상 종료 (4-Way Handshake) | 강제 종료 (RST Packet) |
|---|---|---|
| 목적 | 데이터 유실 없는 안전한 연결 종료 | 신속하고 즉각적인 비정상 연결 단절 |
| 과정 | FIN, ACK 패킷을 주고받는 4단계의 “합의” 과정 | 일방적인 RST 패킷 전송으로 즉시 종료 |
| 데이터 신뢰성 | 높음. 모든 데이터 전송을 보장 | 낮음. 전송 중이거나 버퍼에 있던 데이터 유실 가능 |
| 주요 사용처 | 모든 정상적인 통신 종료 시 (예: socket.close()) | 예외 처리, 비정상 종료, 존재하지 않는 서비스 접속 시 |
개발자로서 대부분의 경우 socket.close()를 호출하여 정상적인 종료 방식을 사용하게 되며, 강제 종료는 네트워크 에러 로그(예: “Connection reset by peer”)를 분석하거나 특정 예외 상황을 처리할 때 마주하게 됩니다.