TCP 연결은 단순히 열려 있거나 닫혀 있는 게 아니에요. 사실은 그 사이에서 수많은 ‘상태’를 넘나드는 섬세한 살아있는 존재에 가까워요.TCP 3-way handshake에서 우리는 연결을 맺는 인사를 봤고, TCP Teardown과 TIME-WAIT에서는 안전하게 헤어지는 법을 봤어요. 근데 여기서 이런 궁금증이 생기지 않으세요?
“좋아요, 인사는 이해했어요. 근데 그 인사를 주고받는 동안 내 컴퓨터 안에서는 정확히 어떤 ‘상태’가 기록되고 있는 건가요?”이 글이 특히 필요한 순간은 상태 이름이 실제 화면에 보일 때예요.
- 왜
CLOSE-WAIT가 오래 남아 있지? TIME-WAIT가 많다고 꼭 문제라고 봐야 하나?- 같은 TCP 연결이라도 왜 한쪽은
FIN-WAIT-2, 다른 쪽은CLOSE-WAIT처럼 서로 다른 상태를 보고 있지?
CLOSED 에서 시작하고 다시 CLOSED 로 돌아오기까지 어떤 길을 거치는지 촘촘하게 해부해볼게요.
여기서는 연결 수립(Handshake)과 종료(Teardown) 과정에서 변하는 상태들에 집중해요. 데이터가 흐르는 ESTABLISHED 상태 내부의 윈도우 조절이나 혼잡 제어 알고리즘은 여기서 다 다루지 않아요. 지금은 “내 연결이 지금 어느 지점에 서 있는가” 를 선명하게 만드는 데 집중할게요.
그래서 TCP 상태 머신은 한마디로 뭐예요?
TCP 상태 머신은 내 엔드포인트가 지금 연결의 어느 단계에 있는지 기록해두는 내부 진행표예요. 중요한 건, 이 상태가 패킷 안에 적혀서 오가는 정보가 아니라는 점이에요. 각 컴퓨터가 자기 메모리 안에서 “나는 지금 기다리는 중인지, 이미 연결됐는지, 닫는 중인지” 를 따로 기록해두고, 오가는 SYN / ACK / FIN 같은 신호를 보고 다음 상태로 넘어가요. 즉 이 글은 상태 이름을 외우는 글이라기보다, 기본편에서 본 handshake / teardown 장면이 내 컴퓨터 안에서는 어떤 진행표로 번역되는지 읽는 글에 가까워요.
그리고 상태가 이렇게 잘게 나뉘는 이유도 먼저 잡아두면 좋아요. TCP는 단순히 “열림 / 닫힘” 두 칸만으로는 부족해요. 누가 먼저 열었는지, 누가 먼저 닫는지, 마지막 신호가 어디까지 전달됐는지에 따라 서로 다른 행동이 필요하거든요. 그래서 상태를 촘촘하게 쪼개서 각 상황마다 다음 행동을 다르게 정해두는 구조가 필요해졌어요.
TCP 상태 머신 전체 그림
TCP의 일생을 한 장의 지도로 그리면 이렇게 생겼어요. 복잡해 보이지만, 왼쪽은 연결되는 과정, 오른쪽은 연결을 끊는 과정이라고 생각하면 마음이 편해져요. 이 그림에서 잡아야 할 핵심은 “모든 연결이 이 모든 화살표를 다 밟지는 않는다” 는 점이에요. 일반적인 클라이언트는CLOSED -> SYN_SENT -> ESTABLISHED 경로를 타고, 서버는 LISTEN -> SYN_RECEIVED -> ESTABLISHED 경로를 타게 되죠. 다만 여기서 말하는 클라이언트 / 서버는 가장 흔한 역할 비유에 가까워요. TCP 상태 자체는 애플리케이션 역할보다 누가 먼저 열고, 누가 먼저 닫느냐 에 더 직접 연결돼요.
연결의 시작: CLOSED에서 ESTABLISHED까지
TCP 3-way handshake가 일어날 때 우리 내부에서는 이런 일이 벌어져요.- LISTEN: 서버가 특정 포트를 열고 손님을 기다리는 상태예요.
- SYN-SENT: 클라이언트가 “연결하고 싶어요(SYN)“라고 신호를 보내고 답장을 기다리는 상태예요.
- SYN-RECEIVED: 서버가 SYN을 받고 “나도 준비됐어(SYN-ACK)“라고 답한 뒤, 마지막 확인(ACK)을 기다리는 상태예요.
- ESTABLISHED: 드디어 서로 확인이 끝났어요! 이제 진짜 데이터를 주고받을 수 있는 상태예요.
SYN-RECEIVED 상태에서 클라이언트로부터 ACK를 받으면 비로소 ESTABLISHED로 넘어간다고 명시되어 있어요. 서버 입장에서는 이 단계가 되어야 비로소 “연결된 소켓”이 완성되는 거죠.
핸드셰이크를 상태 기준으로 다시 보면, 기본편에서 봤던 SYN, SYN-ACK, ACK 가 사실은 각 엔드포인트의 내부 상태를 한 칸씩 밀어 움직이는 신호였다는 게 더 또렷해져요.
ESTABLISHED는 왜 이렇게 오래 머물까요?
대부분의 연결은 실제로 이 상태에서 가장 오래 머물러요. 파일을 내려받든, 웹페이지를 열든, API를 부르든 진짜 일은 거의 다ESTABLISHED 안에서 일어나거든요.
여기서는 상태 머신 자체가 주제라서 ESTABLISHED 안쪽의 세부 알고리즘을 길게 열지는 않을게요. 다만 이 상태가 단순한 “연결 완료” 표지판이 아니라, ACK를 주고받고, 재전송을 판단하고, 윈도우를 광고하고, 데이터를 순서대로 맞추는 운영의 본무대 라는 감각은 꼭 잡고 가면 좋아요. 이 안쪽 숫자들이 실제로 어떻게 일하는지는 TCP 재전송과 신뢰성과 TCP 헤더는 왜 이렇게 칸이 많을까요?에서 같이 이어볼 수 있어요.
연결의 종료: ESTABLISHED에서 다시 CLOSED까지
헤어지는 과정은 조금 더 복잡해요. TCP Teardown에서 본 4-way teardown이 여기서 상태로 나타나요. 먼저 연결 종료를 요청한 쪽(Active Close)과 요청을 받은 쪽(Passive Close)의 상태가 달라요.Active Close 쪽 (먼저 FIN을 보낸 쪽)
- FIN-WAIT-1: “나 이제 그만할게(FIN)“라고 말하고 상대의 확인을 기다려요.
- FIN-WAIT-2: 상대가 “응, 알았어(ACK)“라고 한 것까지 들었어요. 이제 상대방도 그만하겠다는 인사를 하길 기다려요.
- TIME-WAIT: 상대방의 마지막 인사(FIN)까지 확인하고 “너도 잘 가(ACK)“라고 답했어요. 하지만 혹시 모를 재전송이나 낡은 패킷을 위해 잠시 자리를 지켜요.
Passive Close 쪽 (FIN을 받은 쪽)
- CLOSE-WAIT: 상대방의 종료 인사를 받았어요. “응, 확인했어(ACK)“라고 답하고, 우리 쪽 애플리케이션도 종료 준비를 마칠 때까지 대기해요.
- LAST-ACK: 우리도 이제 준비가 끝나서 “나도 이제 갈게(FIN)“라고 말하고 마지막 확인을 기다려요.
FIN을 보낸 직후, 상대도 거의 동시에 FIN을 보내서 양쪽이 서로 먼저 닫으려는 신호가 겹쳤을 때 잠깐 거치는 예외 경로예요. 자주 보는 상태는 아니지만, 상태 머신 그림에 들어간 이유는 이런 겹침도 TCP가 처리해야 하기 때문 이라고 보면 돼요.
이 그림을 보면 왜 종료 쪽이 더 복잡한지 보이죠. 시작할 때는 서로 준비만 확인하면 됐는데, 끝낼 때는 누가 먼저 입을 닫았는지, 반대 방향 데이터는 아직 남아 있는지, 마지막 인사가 진짜 전달됐는지 까지 챙겨야 하거든요.
상태 요약 표
RFC 9293의 설명을 바탕으로 각 상태를 한 표에 모아봤어요.근데 왜? 상태를 이렇게까지 나눠야 했을까요?
1. 연결은 “상대적”이기 때문이에요
내 컴퓨터가ESTABLISHED라고 해서 상대방도 반드시 ESTABLISHED인 건 아니에요. 예를 들어 내가 마지막 ACK를 보냈지만 상대방에게 닿지 않았다면, 나는 ESTABLISHED라고 생각해도 상대방은 여전히 SYN-RECEIVED에 머물러 있을 수 있죠. 상태 머신은 이런 불확실한 상황에서도 각자가 어떻게 행동해야 하는지 가이드라인을 줘요.
2. TIME-WAIT는 “미아 패킷”의 무덤이에요
RFC 1337과 RFC 9293에서 중요하게 다루는 게 바로TIME-WAIT예요. 만약 이 상태 없이 바로 CLOSED가 된다면, 예전 연결에서 헤매던 패킷이 뒤늦게 나타나서 새로 열린 같은 포트의 연결을 오염시킬 수 있거든요. 2MSL(Maximum Segment Lifetime) 동안 기다리는 건, 인터넷 어딘가에 살아있을지 모르는 미아 패킷이 확실히 사라질 시간을 주는 거예요.
그럼 진짜 화면에서는 어떻게 보일까요?
말로만 하면 재미없죠? 지금 바로 터미널에서ss나 netstat 명령어로 내 컴퓨터의 TCP 상태들을 구경할 수 있어요.
- LISTEN: 내 컴퓨터가 80번 포트(HTTP)를 열고 손님을 기다리고 있네요.
- ESTAB: 51515 포트로 구글 서버(443)와 한창 대화 중이에요. (
ESTABLISHED의 줄임말이에요) - TIME-WAIT: 방금 대화가 끝난 51516 포트 연결이 안전 정리를 위해 잠시 남아 있어요.
잘못 읽기 쉬운 함정 세 가지
하나, ESTABLISHED면 무조건 데이터가 흐르고 있다고 믿기. 아니에요.ESTABLISHED는 단순히 “대화할 통로가 열려 있다” 는 뜻이에요. 아무 데이터를 안 보내고 가만히 있어도(IDLE) 이 상태는 유지돼요. 그래서 Keep-Alive 같은 기술이 필요한 거죠.
둘, TIME-WAIT가 많으면 무조건 장애라고 생각하기.
정반대예요. TIME-WAIT는 TCP가 정상적으로, 아주 정중하게 연결을 닫았을 때 남는 흔적에 가까워요. 물론 이게 너무 많아서 포트가 부족해지면 운영상 병목이 될 수는 있지만, 상태 이름 자체가 곧장 고장을 뜻하는 건 아니에요.
셋, 모든 연결이 모든 상태를 다 밟는다고 착각하기.
아니에요. 어떤 연결은 LISTEN을 전혀 안 거치고, 어떤 연결은 CLOSING을 평생 못 보고 끝나요. 심지어 RST(Reset)가 날아오면 이 복잡한 상태들을 다 무시하고 한 번에 CLOSED로 튕겨 나갈 때도 있어요. 상태 머신은 가능한 길들의 지도라고 보는 편이 더 정확해요.
자, 정리해볼까요?
- TCP는 연결의 각 단계마다 엔드포인트 내부의 메모리에 ‘상태’를 기록하며 움직여요.
- 연결 시작:
LISTEN,SYN-SENT,SYN-RECEIVED를 거쳐ESTABLISHED로 들어가요. - 연결 종료: 누가 먼저 닫느냐에 따라
FIN-WAIT-*,CLOSE-WAIT,LAST-ACK,TIME-WAIT같은 갈래가 생겨요. - TIME-WAIT는 먼저 연결을 끊은 쪽이 낡은 패킷의 혼선을 막기 위해 안전 대기하는 필수 상태예요.
- 상태는 상대방과 공유하는 게 아니라 각자 로컬에서 관리하며 패킷으로 서로의 기분을 맞춰가는 거예요.
SYN-SENT와 SYN-RECEIVED라는 구체적인 이름으로 보이기 시작하셨나요? 그리고 TCP Teardown과 TIME-WAIT에서 봤던 마지막 작별 인사도, 이제는 FIN-WAIT, CLOSE-WAIT, LAST-ACK, TIME-WAIT 같은 이름으로 더 또렷하게 보일 거예요.
이어서 보면 좋은 글
SYN,SYN-ACK,ACK가 상태를 어떻게 한 칸씩 밀어 움직이는지 다시 큰 그림으로 보고 싶다면 — TCP 3-way handshake는 왜 세 번이나 주고받을까요?FIN,CLOSE-WAIT,LAST-ACK,TIME-WAIT흐름을 종료 장면 중심으로 다시 보고 싶다면 — TCP Teardown과 TIME-WAIT - 대화가 끝난 뒤의 깔끔한 마무리LISTEN,ESTABLISHED,CLOSE-WAIT,TIME-WAIT가 실제ss/netstat화면에서는 어떤 줄로 보이는지 보고 싶다면 — ss와 netstat에서 TCP 상태는 어떻게 읽어야 할까요?- 이 상태 변화를 일으키는
SYN,ACK,FIN,RST같은 신호를 캡처 표기 중심으로 읽고 싶다면 — TCP 플래그는 어떻게 읽어야 할까요? - 상태 안쪽에서
seq,ack,window같은 숫자가 실제 TCP 헤더의 어느 칸에 들어가는지 다시 보고 싶다면 — TCP 헤더는 왜 이렇게 칸이 많을까요? - 이런 상태 변화가 실제 tcpdump 한 줄에서는 어떤 식으로 찍히는지 바로 이어서 보고 싶다면 — tcpdump 한 줄은 어떻게 읽어야 할까요?