TCP의 각각의 상태의 의미를 RFC 793의 TCP 기본 연결, 종료 과정을 통해 알아본다.
1. TCP 연결 관련 상태 #
* RFC 793 문서에 나온 기본적인 TCP 연결 과정
* 구글에서 적당히 가져온 3-way Handshake 이미지
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: 호스트 간에 상호 연결이 된 상태. 이 상태에서 데이터를 주고받을 수 있음.
SYN_SENT: 호스트가 SYN 패킷을 보내고(연결 요청), 상대방의 SYN+ACK 패킷을 기다리는 상태.
SYN_RECV: 호스트가 연결 요청을 받아 SYN+ACK 패킷으로 응답을 보내고, ACK 패킷을 기다리는 상태. 윈도우와 솔라리스에서는 SYN_RECEIVED로, FreeBSD는 SYN_RCVD로 표시한다.
ESTABLISHED: 호스트 간에 상호 연결이 된 상태. 이 상태에서 데이터를 주고받을 수 있음.
2. TCP 종료 관련 상태 #
* 정상적인 연결 종료 과정
* 4-way Terminate
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: 완전히 연결이 종료된 상태.
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로 인해 부하가 걸리지는 않는다. 실제로 웹 서버를 운영하면 TIME_WAIT 상태가 수십, 수백 개 생기기도 한다. 수십 개 정도는 일반적인 수준이다.
- SYN_RECV 상태가 너무 많다면 문제가 생길 수는 있다(DoS 공격을 받고 있을 수 있다.).
기술적으로 없애는 방법만 간단히 소개한다. 소켓 옵션 중 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))