4-way 종료와 TIME_WAIT
연결을 맺는 것이 3-way였다면, 연결을 끊는 것은 4-way입니다. 왜 종료는 한 번이 더 필요한가, 그리고 종료 후에도 한동안 연결이 완전히 닫히지 않고 TIME_WAIT라는 상태로 남는 이유는 무엇인가. 이 두 가지 질문은 실무와도 직결됩니다.
4-way인 이유
3-way와 달리 4-way가 필요한 것은 TCP의 양방향 독립성 때문입니다. 연결은 클라이언트에서 서버로 가는 방향과 서버에서 클라이언트로 가는 방향이 독립적으로 열려 있습니다. 한 쪽이 "나는 더 보낼 게 없다"고 선언하더라도 반대 방향은 아직 보낼 데이터가 남아 있을 수 있습니다.
그래서 각 방향을 따로 닫습니다. 과정은 이렇습니다.
첫째, 클라이언트가 FIN 패킷을 보냅니다. "나는 보낼 게 없어, 끊을게."
둘째, 서버가 ACK를 보냅니다. "알았어, 받았어." 하지만 서버는 아직 보낼 데이터가 있을 수 있으므로 연결을 바로 닫지 않습니다.
셋째, 서버가 남은 데이터를 다 보내고 나서 FIN을 보냅니다. "나도 이제 끝났어, 끊을게."
넷째, 클라이언트가 ACK를 보냅니다. "알았어, 받았어."
이 네 단계입니다. 두 번째와 세 번째 사이에 서버가 데이터를 더 보낼 수 있는 구간이 있기 때문에 handshake처럼 SYN과 ACK를 하나로 합치기 어렵습니다.
TIME_WAIT는 왜 존재하는가
네 번째 ACK를 클라이언트가 보낸 뒤에도 클라이언트는 즉시 연결을 닫지 않습니다. TIME_WAIT 상태로 일정 시간(보통 2MSL, 최대 약 4분) 머뭅니다. MSL은 Maximum Segment Lifetime, 패킷이 인터넷에서 살아 있을 수 있는 최대 수명입니다.
이 대기가 필요한 이유는 두 가지입니다.
하나는 마지막 ACK가 제대로 도착했는지 보장하기 위해서입니다. 클라이언트가 보낸 마지막 ACK가 서버에 도달하지 못하면, 서버는 FIN을 다시 보냅니다. 클라이언트가 이미 연결을 닫아버렸다면 이 재전송 FIN을 처리할 수 없습니다. TIME_WAIT 상태에서 기다리면서 혹시 FIN이 다시 온다면 ACK를 한 번 더 보낼 수 있습니다.
다른 하나는 지연된 패킷 충돌 방지입니다. 이전 연결에서 인터넷 어딘가를 떠돌던 패킷이 연결을 닫은 직후 도착할 수 있습니다. 만약 같은 IP와 포트로 새 연결이 열렸다면 그 떠돌이 패킷이 새 연결의 데이터로 오해받을 수 있습니다. TIME_WAIT 동안 같은 IP와 포트 조합으로 새 연결을 허용하지 않음으로써 이 문제를 막습니다.
실무에서 나타나는 TIME_WAIT 문제
서버를 재시작하면 포트가 바로 열리지 않아 "Address already in use" 오류가 나는 경험을 한 적 있을 것입니다. 이것이 TIME_WAIT 때문입니다. 직전 연결의 잔상이 아직 그 포트를 붙잡고 있는 상태입니다.
서버 프로그램에서는 SO_REUSEADDR 소켓 옵션을 설정하면 TIME_WAIT 상태의 포트를 즉시 재사용할 수 있습니다. 대부분의 서버 프레임워크가 기본으로 이 옵션을 설정합니다. 그래도 매우 짧은 시간 안에 재시작하면 일시적으로 문제가 생기는 경우가 있습니다.
단순히 "TIME_WAIT는 연결 종료 후 잠깐 남는 상태입니다"라고 외우는 것과, 왜 그 상태가 존재하고 실무에서 어떤 문제로 나타나는지 설명할 수 있는 것은 전혀 다른 깊이입니다.
다음 절에서는 Wireshark로 이 모든 과정을 직접 눈으로 확인합니다.