네트워크 기초 · TCP 와 UDP · 이론
TCP — 연결과 신뢰성은 어떻게 만들어지는가
한 줄 요약
TCP 는 순서 번호, 확인 응답, 재전송, 그리고 두 종류의 창(윈도)으로 "잃어버리지도 뒤바뀌지도 않는 바이트 스트림"이라는 착각을 만들어 낸다.
왜 이게 필요했나
IP 는 최선을 다할 뿐 아무것도 약속하지 않는다. 패킷은 사라질 수 있고, 순서가 바뀔 수 있고, 중복될 수 있다. 그런데 대부분의 응용은 "보낸 대로 받는다"를 전제로 짜여 있다. 그 간극을 메우는 계층이 TCP 다.
어떻게 동작하나
연결 수립. 클라이언트가 SYN 을 보내고, 서버가 SYN-ACK 로 답하고, 클라이언트가 ACK 를 보낸다. 세 번의 교환이 필요한 이유는 양쪽이 각자의 초기 순서 번호를 상대에게 알리고 확인받아야 하기 때문이다.
연결 종료. 각 방향을 따로 닫으므로 FIN 과 ACK 가 두 번씩, 네 번의 교환이 필요하다. 먼저 닫은 쪽은 TIME-WAIT 상태로 2MSL(최대 세그먼트 수명의 두 배) 동안 남는다. 늦게 도착한 세그먼트가 같은 포트 쌍을 쓰는 다음 연결을 오염시키지 않게 하고, 마지막 ACK 가 유실되면 재전송해 주기 위해서다. 부하가 큰 서버에서 TIME-WAIT 소켓이 수만 개 쌓이는 것은 정상이며, 이를 없애려고 커널 파라미터를 무턱대고 건드리면 다른 문제를 부른다.
신뢰성. 받는 쪽은 잘 받은 곳까지의 위치를 ACK 로 알린다. 보내는 쪽은 일정 시간(RTO) 안에 확인이 없으면 재전송하고, 같은 ACK 가 세 번 중복되면 타임아웃을 기다리지 않고 즉시 재전송한다(빠른 재전송).
두 개의 창. 흐름 제어는 받는 쪽이 광고하는 수신 윈도(rwnd)로 "나는 이만큼밖에 못 받는다"를 알리는 장치다. 혼잡 제어는 보내는 쪽이 스스로 유지하는 혼잡 윈도(cwnd)로 "네트워크가 이만큼밖에 못 견딘다"를 추정하는 장치다. 둘은 목적이 다르다. 실제 전송량은 두 값 중 작은 쪽으로 제한된다. 혼잡 제어는 느린 시작으로 지수적으로 늘리다가 임계치를 넘으면 선형으로 늘리고, 손실이 감지되면 크게 줄인다(AIMD).
현장에서 만나는 모습
연결 실패 메시지 두 가지의 차이가 진단의 절반을 결정한다.
Connection refused 는 상대가 RST 를 돌려줬다는 뜻이다. 패킷이 목적지까지 갔다가 돌아왔다는 증거이므로 라우팅, 보안 그룹, 네트워크 정책은 이미 통과한 것이다. 남는 후보는 목적지 호스트 안쪽이다. 프로세스가 죽었거나, 다른 포트에 붙었거나, 0.0.0.0 이 아니라 127.0.0.1 에만 바인딩했거나, 로드밸런서 뒤에 정상 백엔드가 하나도 없는 경우다. 여기서 흔한 오해를 정정해야 한다. "refused 가 나오니 방화벽"이라는 설명은 대부분 틀렸다. 실무 방화벽은 거의 언제나 조용히 버리는(DROP) 정책이고, 버려진 패킷은 응답이 없으므로 timeout 으로 나타난다.
Connection timed out 은 SYN 재전송을 다 소진하도록 아무 응답이 없었다는 뜻이다. 리눅스는 기본적으로 tcp_syn_retries 만큼 재전송하며 간격을 두 배씩 늘린다. 1+2+4+8+16+32+64 를 더하면 127초다. 애플리케이션 로그의 타임아웃 설정이 30초인데 실제로는 127초 만에 실패했다면 그 설정이 적용되지 않고 있다는 신호다.
리스너가 있는데도 timeout 이 나는 경우도 있다. accept 큐가 가득 차면 커널이 새 SYN 을 조용히 버린다. ss -ltn 으로 리스닝 소켓을 보면 Recv-Q 가 현재 큐 길이, Send-Q 가 최대 크기다. Recv-Q 가 Send-Q 를 넘고 있다면 네트워크가 아니라 애플리케이션 처리량이 원인이다.
이어지는 퀴즈에서 확인할 것
refused 와 timeout 이 각각 어떤 패킷 교환의 결과인지, 흐름 제어와 혼잡 제어의 차이가 무엇인지 설명할 수 있는지 확인한다.