TCP의 각각의 상태의 의미를
RFC 793의 TCP 기본 연결, 종료 과정을 통해 알아본다.
1. TCP 연결 관련 상태 #
*
RFC 793 문서에 나온 기본적인 TCP 연결 과정
TCP A TCP B
1. CLOSED LISTEN
2. SYN-SENT --> < SEQ=100>< CTL=SYN> --> SYN-RECEIVED
3. ESTABLISHED <-- < SEQ=300>< ACK=101>< CTL=SYN,ACK> <-- SYN-RECEIVED
4. ESTABLISHED --> < SEQ=101>< ACK=301>< CTL=ACK> --> ESTABLISHED
5. ESTABLISHED --> < SEQ=101>< ACK=301>< CTL=ACK>< DATA> --> ESTABLISHED
[PNG image (54.18 KB)]
* 구글에서 적당히 가져온 3-way Handshake 이미지
LISTEN: 호스트가 요청을 받을 수 있도록 연결 요구를 기다리는 상태. 즉 포트가 열려있음을 의미. HTTP(80), MAIL(25), FTP(21), TELNET(23) 등.
SYN_SENT: 호스트가 SYN 패킷을 보내고(연결 요청), 상대방의 SYN+ACK 패킷을 기다리는 상태.
SYN_RECV: 호스트가 연결 요청을 받아 SYN+ACK 패킷으로 응답을 보내고, ACK 패킷을 기다리는 상태. 윈도우와 솔라리스에서는 SYN_RECEIVED로,
FreeBSD는 SYN_RCVD로 표시한다.
ESTABLISHED: 호스트 간에 상호 연결이 된 상태. 이 상태에서 데이터를 주고받을 수 있음.
2. TCP 종료 관련 상태 #
* 정상적인 연결 종료 과정
TCP A TCP B
1. ESTABLISHED ESTABLISHED
2. (Close)
FIN-WAIT-1 --> < SEQ=100>< ACK=300>< CTL=FIN,ACK> --> CLOSE-WAIT
3. FIN-WAIT-2 <-- < SEQ=300>< ACK=101>< CTL=ACK> <-- CLOSE-WAIT
4. (Close)
TIME-WAIT <-- < SEQ=300>< ACK=101>< CTL=FIN,ACK> <-- LAST-ACK
5. TIME-WAIT --> < SEQ=101>< ACK=301>< CTL=ACK> --> CLOSED
6. (2 MSL)
CLOSED
[JPG image (20.76 KB)]
* 4-way Terminate
3/4-way Terminate를 거쳐 종료된다. 3-way Terminate는 3-way Handshake와 비슷하게 FIN+ACK를 주고 받는다. 아직 호스트 B(passive close) 측에서 처리할 게 남았다면 ACK를 먼저 보내고 처리가 끝난 다음 FIN 전송. 이런 경우 4-way Terminate이라고 한다. 호스트 B가 바로 종료하지 않고 종료를 받았다는 ACK를 보내주기 위해 CLOSE_WAIT 상태가 필요하다. (TIME_WAIT는 뒤에 더 자세히 설명)
FIN_WAIT1: 호스트가 연결 종료를 요청하고, 상대방의 ACK 패킷을 기다리는 상태. 원격의 응답은 계속 받을 수 있다.
FIN_WAIT2: 호스트가 상대방의 연결 종료 요청을 기다리는 상태.
CLOSE_WAIT: 연결 종료 요청을 받고 연결이 종료되기까지 상대방의 데이터 전송을 기다리는 상태. 원격으로 부터 FIN+ACK 신호를 받고 ACK 신호를 원격으로 보냈다.
LAST_WAIT: FIN 패킷을 수신하고, 연결 종료에 대한 ACK 패킷을 보내고 나서, 상대방의 ACK 패킷을 기다리는 상태. LAST_WAIT 상태는 일반적으로 매우 짧은 시간 동안 유지되며, 클라이언트가 ACK 패킷을 보내는 속도에 따라 달라질 수 있다.
TIME_WAIT: 연결이 완전히 종료된 후에 원격의 수신 보장을 위해 일정 시간 기다리는 상태. 아래에 더 자세히 설명. FIN 패킷을 수신한 후, ACK 패킷을 보내고 CLOSED 하지 않고 일반적으로 2MSL(Maximum segment lifetime) 동안 이 상태를 유지한다. (Apache에서
KeepAlive를 OFF로 해둔 경우, Tomcat 서버를 쓰는 경우 등에서 이 상태를 자주 볼 수 있다.)
CLOSED: 완전히 연결이 종료된 상태.
- 종료 요청을 한 호스트(active close)에서는 FIN_WAIT1, FIN_WAIT2, TIME_WAIT 상태가,
종료 요청을 받은 호스트(passive close)에서는 CLOSE_WAIT, LAST_WAIT 상태가 표시된다.
2.1. TIME_WAIT 상태를 유지하는 이유 #
TIME_WAIT가 필요한 이유는 2가지가 있다. 《Unix Network Programming》에서는 TIME_WAIT 상태가 있는 이유에 대해 다음과 같이 설명한다.
- to implement TCP's full-duplex connection termination reliably, and
- to allow old duplicate segments to expire in the network.
첫 번째 이유는 원격의 종료까지 확인하여 신뢰성 있는 연결 종료를 위한 것이고, 두 번째 이유는 만료된 연결의 패킷 제거를 위해서인데, 종료한 소켓이 TIME_WAIT 상태에서 금방 사라지지 않는 이유가 두 번째 이유 때문이다. 이런 상황을 가정할 수 있다. 둘이 패킷을 주고 받다가 정상적으로 연결을 끊었다. 그리고 둘이 곧바로 연결을 해서 방금 전과 같은 포트로 연결되었다. 여기서 문제가 발생하는데 이전에 연결이 되었을 때 보낸 패킷이 라우터의 일시적인 오류로 네트워크를 뱅뱅 돌다가 다시 새로운 연결이 되었을 때 도착할 수 있다. 즉, 모든 패킷 순서를 엄격히 보장하는 TCP에서 원하지 않는 데이터가 수신되었으니 네트워크 오류가 발생할 수 있다. 이 때 TIME_WAIT 상태를 유지하면 같은 포트를 다른 프로세스가 다시 이용하는 것을 막는다. 같은 연결이 발생하지 못하도록 방지한다. 그리고 TIME_WAIT 상태는 2 MLS 시간 동안 유지한다. 즉, 네트워크에 패킷이 존재하는 시간보다 두 배 길게 설정된다. 따라서 TIME_WAIT 상태가 끝나면 네트워크 상에는 이전 연결에 보내졌던 패킷이 모두 소멸되었다고 확신할 수 있으므로 새로운 연결을 만들어도 문제가 발생하지 않을 것이다.
- TIME_WAIT로 인해 부하가 걸리지는 않는다. 실제로 웹 서버를 운영하면 TIME_WAIT 상태가 수십, 수백 개 생기기도 한다. 수십 개 정도는 일반적인 수준이다.
- SYN_RECV 상태가 너무 많다면 문제가 생길 수는 있다(DoS 공격을 받고 있을 수 있다.).
TIME_WAIT를 없애는 방법
기술적으로 없애는 방법만 간단히 소개한다. 소켓 옵션 중 linger option을 off 시키면 연결 종료 시 TIME_WAIT 상태를 거치지 않고 CLOSED 상태로 바뀌게 된다. TIME_WAIT가 너무 많아 문제가 생기는 경우는 별로 없다. 이를 적용하면 오히려 통신이 불안정해질 수 있고 그 이유는 위에서 설명했다.
/* 중략 */
int sock
struct linger ling;
ling.l_onoff = 1;
ling.l_linger = 0; /* 0 for abortive disconnect */
/* 중략 */
setsockopt(sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling))
3. 기타 상태 #
CLOSING: 연결은 종료되었으나 전송 도중 데이터가 분실된 상태.
UNKNOWN: 소켓의 상태를 알 수 없음.
솔라리스에서는 다음 2개의 상태를 더 표시한다.
IDLE: 소켓이 열렸지만 binding 되지 않은 상태
BOUND: listen이나 연결을 위한 준비 상태
참조