Skip to content
난이도 면접 빈출문제은행 3

TCP와 UDP 2 ​

TCP와 UDP에서 본 연결 수립에 이어, TCP(Transmission Control Protocol, 전송 제어 프로토콜) 연결을 닫는 과정과 그때 생기는 상태를 다룬다. 먼저 닫는 쪽에 생기는 TIME_WAIT과 나중에 닫는 쪽에 생기는 CLOSE_WAIT은 서버 장애를 볼 때 자주 마주친다. 마지막으로 신뢰성 기능이 없는 UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)도 오류 검사는 한다는 점을 본다.

연결 해제: 4-way 종료 ​

TCP 연결은 양방향이라 방향마다 따로 닫는다. 먼저 닫는 쪽을 능동 종료(active close), 나중에 닫는 쪽을 수동 종료(passive close)라고 한다.

다이어그램 로딩…
  1. 능동 종료 쪽이 FIN(Finish, 종료 요청)을 보내 더 보낼 데이터가 없다고 알린다.
  2. 상대는 ACK(Acknowledgement, 확인 응답)로 받았음을 알린다. 이때 상대는 CLOSE_WAIT 상태가 되고, 아직 보낼 데이터가 있으면 마저 보낸다.
  3. 상대 애플리케이션이 소켓(socket)을 닫으면 상대도 FIN을 보낸다.
  4. 능동 종료 쪽이 마지막 ACK를 보내고 TIME_WAIT 상태로 들어간다.

2번 ACK와 3번 FIN을 늘 합치지 못하는 이유는 그 사이에 남은 데이터를 보낼 시간이 필요하기 때문이다. 보낼 것이 없으면 둘이 TCP 세그먼트(segment) 하나에 합쳐지기도 한다. 세그먼트는 TCP가 데이터를 나눠 제어 정보를 붙여 보내는 단위다.

TIME_WAIT ​

능동 종료 쪽은 마지막 ACK를 보낸 뒤 바로 끝내지 않고 TIME_WAIT 상태로 기다린다. 기다리는 시간은 MSL(Maximum Segment Lifetime, 최대 세그먼트 수명)의 두 배인 2MSL이다. MSL은 세그먼트가 네트워크에 남아 있을 수 있는 최대 시간이다. RFC(Request for Comments, 인터넷 표준 문서) 9293은 MSL을 2분으로 잡지만, 실제 TIME_WAIT 대기 시간은 운영체제 구현마다 다르다. 기다리는 이유는 두 가지다.

  • 마지막 ACK가 사라질 때를 대비한다. ACK가 사라지면 상대는 FIN을 다시 보낸다. 연결 정보가 남아 있어야 ACK를 다시 보내 상대를 정상 종료시킬 수 있다. 정보가 없으면 연결을 강제로 끊는 RST(Reset, 연결 재설정) 신호로 답하게 되고, 상대는 정상 종료 대신 연결 오류를 받는다.
  • 옛 연결의 세그먼트가 새 연결에 섞이지 않게 한다. 같은 주소·포트(port) 조합으로 바로 새 연결을 열면, 옛 연결에서 늦게 도착한 세그먼트가 새 연결의 데이터로 오인될 수 있다. 2MSL을 기다리면 옛 세그먼트는 네트워크에서 모두 사라진다.

TIME_WAIT은 먼저 닫는 쪽에 생긴다. 짧은 연결을 많이 여닫으면 그쪽에 TIME_WAIT 소켓이 대량으로 쌓이고, 바깥으로 연결을 많이 거는 쪽이라면 쓸 수 있는 임시 포트(ephemeral port)가 모자랄 수 있다. 임시 포트는 바깥으로 연결을 걸 때 운영체제가 자동으로 골라 주는 출발지 포트인데, TIME_WAIT 동안에는 그 소켓이 쓰던 주소·포트 조합을 다시 쓰기 어렵기 때문이다. 기본 대책은 연결을 매번 새로 만들지 않고 재사용하는 것이다. HTTP의 지속 연결(persistent connection)이나 미리 맺어 둔 연결을 빌려 쓰는 커넥션 풀(connection pool)을 쓰면 닫는 연결 수 자체가 줄어 TIME_WAIT도 덜 쌓인다.

CLOSE_WAIT ​

상대의 FIN을 받고 ACK를 보낸 수동 종료 쪽은 CLOSE_WAIT 상태가 된다. 이 상태는 "상대는 끝냈으니, 우리 애플리케이션이 소켓을 닫기를 기다리는 중"이라는 뜻이다. 커널(kernel)은 애플리케이션이 close()를 호출해야 FIN을 보낼 수 있다. TCP 표준에는 이 상태를 끝내는 타이머가 없어서, 애플리케이션이 닫을 때까지 소켓이 남는다.

그래서 CLOSE_WAIT이 계속 쌓이면 원인은 거의 늘 애플리케이션 코드다. 예외가 난 경로에서 소켓을 닫지 않았거나, 커넥션 풀에서 빌린 연결을 반납하지 않은 경우가 흔하다. 쌓인 소켓은 운영체제가 열린 파일과 소켓에 붙이는 번호인 파일 디스크립터(file descriptor)를 차지하므로 결국 새 연결을 받지 못하게 된다.

UDP 체크섬 ​

UDP는 신뢰성 기능이 없지만 오류 검사는 한다. UDP 헤더(header)는 출발지 포트·목적지 포트·길이·체크섬(checksum)의 16비트 필드 네 개, 합쳐 8바이트다. 체크섬은 헤더와 데이터, 그리고 IP(Internet Protocol, 인터넷 프로토콜) 주소 등을 담은 가상 헤더(pseudo-header)를 더해 계산한 16비트 값이다. 가상 헤더는 실제로 보내지 않고 계산할 때만 붙이는 값인데, 데이터그램이 엉뚱한 주소로 잘못 배달된 것까지 잡아내려고 넣는다. 받는 쪽이 다시 계산해 맞지 않으면 그 데이터그램(datagram)을 버리고, 다시 보내 달라고 요청하지는 않는다. IPv4(Internet Protocol version 4, 인터넷 프로토콜 4판)에서는 체크섬을 0으로 두어 생략할 수 있지만, IPv6(Internet Protocol version 6, 인터넷 프로토콜 6판)에서는 반드시 계산해야 한다. TCP 헤더에도 같은 방식의 체크섬이 있다.

면접 포인트 ​

  • "TCP 연결은 어떻게 끊나요?" → 방향마다 FIN과 ACK를 주고받는 4-way 종료. 2번 ACK와 3번 FIN 사이에 남은 데이터를 보낸다.
  • "TIME_WAIT은 어디에, 왜 생기나요?" → 먼저 닫은 쪽. 2MSL 대기. 마지막 ACK 손실 대비, 옛 세그먼트가 새 연결에 섞이는 것 방지.
  • "CLOSE_WAIT이 쌓이면?" → 애플리케이션이 소켓을 닫지 않는 버그. 코드와 커넥션 반납을 본다.
  • UDP에는 오류 검사가 전혀 없다고 말하면 틀린다. 체크섬은 있고, 깨진 것을 다시 보내 주지 않을 뿐이다.

관련 문서