TCP/IP
TCP(Transmission Control Protocol)는 연결 지향적이고 신뢰성 있는 데이터 전송을 보장하는 프로토콜입니다. 연결 수립 시 3-way handshake, 연결 종료 시 4-way termination을 사용하며 흐름 제어·혼잡 제어·순서 보장을 통해 정확한 데이터 전달을 실현합니다.
핵심 개념
3-way Handshake
TCP 연결 수립 과정입니다. ① 클라이언트가 SYN(seq=x)을 보내 연결을 요청합니다. ② 서버가 SYN+ACK(seq=y, ack=x+1)로 응답합니다. ③ 클라이언트가 ACK(ack=y+1)를 보내 연결이 수립됩니다. 이 과정으로 양방향 통신 채널이 열립니다.
핵심 개념
4-way Termination
TCP 연결 종료 과정입니다. ① 클라이언트가 FIN을 보냅니다. ② 서버가 ACK로 응답합니다(서버가 ACK와 FIN을 합쳐 보내면 3번이 됩니다)(서버는 아직 데이터 전송 가능). ③ 서버가 FIN을 보냅니다. ④ 클라이언트가 ACK를 보내고 TIME_WAIT 상태를 거쳐 완전히 종료됩니다.
핵심 개념
TCP 헤더 구조
출발지·목적지 포트(각 16비트), 시퀀스 번호(32비트), ACK 번호(32비트), 데이터 오프셋, 플래그(SYN·ACK·FIN·RST·PSH·URG), 윈도우 크기(16비트), 체크섬(16비트), 긴급 포인터로 구성됩니다. 최소 20바이트입니다.
핵심 개념
흐름 제어 & 혼잡 제어
흐름 제어는 수신 측 버퍼 오버플로를 방지합니다. 수신 윈도우 크기로 송신 속도를 조절합니다. 혼잡 제어는 네트워크 혼잡을 방지합니다. Slow Start로 시작해 혼잡 윈도우를 조절하며, 패킷 손실 감지 시 전송 속도를 줄입니다.
핵심 개념
TCP vs UDP
TCP는 연결 지향·순서 보장·신뢰성 전송·흐름·혼잡 제어를 제공하지만 오버헤드가 큽니다. UDP는 비연결·순서 미보장·빠른 전송이 특징입니다. 실시간 스트리밍·게임·DNS·VoIP는 UDP를, 파일 전송·HTTP·이메일은 TCP를 사용합니다.
핵심 개념
TCP 상태 머신
TCP 연결은 상태 머신으로 동작합니다. CLOSED → SYN_SENT → ESTABLISHED(handshake 완료) → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED(종료). 서버 측은 LISTEN → SYN_RECEIVED → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED 순서입니다.
TCP가 해결하려는 문제
인터넷의 기반인 IP는 패킷을 최선을 다해 전달할 뿐, 도착과 순서를 보장하지 않습니다. TCP는 그 위에서 번호 매기기, 확인 응답, 재전송, 흐름 제어, 혼잡 제어로 '순서대로 빠짐없이 도착하는 바이트 스트림'처럼 보이게 만드는 프로토콜입니다. 현재 사양은 2022년 RFC 9293에 정리되어 있습니다.
숫자로 확인하기
자주 하는 오해
면접에서 이렇게 물어요
면접 질문
연결을 왜 3-way handshake로 맺나요?
양쪽이 서로의 초기 시퀀스 번호를 확인하고 동기화해야 해서 양방향으로 SYN과 ACK가 오가야 합니다. RFC 9293은 오래된 중복 SYN이 혼란을 일으키지 않게 하는 것도 이유로 설명합니다.
면접 질문
TIME_WAIT 상태는 왜 있나요?
마지막 ACK가 손실되었을 때 상대가 FIN을 다시 보내면 응답할 수 있게 하고, 같은 주소 쌍의 이전 연결에서 늦게 도착한 패킷이 새 연결에 섞이는 것을 막기 위해서입니다. 보통 연결을 먼저 닫은(능동 종료) 쪽이 TIME_WAIT에 들어가고, 동시에 닫으면 양쪽이 들어갑니다.
면접 질문
흐름 제어와 혼잡 제어의 차이는 무엇인가요?
흐름 제어는 수신자의 버퍼를 넘치지 않게 수신 윈도우로 송신 속도를 맞추는 것이고, 혼잡 제어는 네트워크 전체가 막히지 않게 혼잡 윈도우로 송신 속도를 조절하는 것입니다.
면접 질문
TCP와 UDP는 언제 각각 쓰나요?
순서와 신뢰성이 중요한 웹, 파일 전송, 메일은 TCP를 씁니다. 지연이 중요하고 약간의 손실을 허용하는 실시간 음성·영상, 짧은 질의응답인 DNS는 UDP를 흔히 씁니다.
실제 시스템에서는
tcp(7) 매뉴얼은 tcp_keepalive_time의 기본값이 7200초(2시간)라고 설명합니다. ss 명령으로 소켓 상태(TIME_WAIT 등)를 볼 수 있습니다.
함께 보면 좋은 토픽
참고 자료
Java — TCP 서버 / 클라이언트 소켓 (3-way handshake 흐름)
import java.net.*; import java.io.*; /* ── 서버: LISTEN → SYN_RECEIVED → ESTABLISHED ────── */ public class TcpServer { public static void main(String[] args) throws Exception { // ServerSocket.bind() → 서버 LISTEN 상태 ServerSocket server = new ServerSocket(8080); System.out.println("서버 LISTEN — 포트 8080"); // accept()는 블로킹 — 클라이언트 SYN을 기다림 // SYN 수신 → OS가 SYN+ACK 자동 전송 → ACK 수신 // → 3-way handshake 완료 → ESTABLISHED Socket client = server.accept(); // 여기서 handshake 완료 System.out.println("ESTABLISHED: " + client.getRemoteSocketAddress()); try (var out = new PrintWriter(client.getOutputStream(), true); var in = new BufferedReader( new InputStreamReader(client.getInputStream()))) { String msg = in.readLine(); out.println("Echo: " + msg); } // try-with-resources → close() → 4-way termination 자동 수행 server.close(); } } /* ── 클라이언트: CLOSED → SYN_SENT → ESTABLISHED ───── */ public class TcpClient { public static void main(String[] args) throws Exception { // new Socket() → SYN 전송 → SYN+ACK 수신 → ACK 전송 // → ESTABLISHED (3-way handshake를 생성자에서 완료) try (Socket sock = new Socket("localhost", 8080)) { System.out.println("ESTABLISHED"); // TCP 옵션 설정 예시 sock.setTcpNoDelay(true); // Nagle 알고리즘 비활성 sock.setSoTimeout(5000); // read 타임아웃 5초 sock.setKeepAlive(true); // TCP Keep-Alive 활성 var out = new PrintWriter(sock.getOutputStream(), true); var in = new BufferedReader( new InputStreamReader(sock.getInputStream())); out.println("Hello TCP"); System.out.println(in.readLine()); } // 소켓 닫힘 → FIN 전송 → 4-way termination } }
Java에서 new Socket("host", port)를 호출하는 순간 OS가 TCP 3-way handshake를 수행합니다.
개발자는 생성자가 반환되면 이미 ESTABLISHED 상태임을 알 수 있습니다.
ServerSocket.accept()는 OS의 백로그 큐에서 완료된 연결을 꺼냅니다. SYN_RECEIVED → ESTABLISHED 과정은 OS 커널이 자동으로 처리합니다.
Socket.close()를 호출하면 OS가 4-way termination을 수행합니다.
try-with-resources를 사용하면 예외 발생 시에도 FIN이 전송됩니다.
setTcpNoDelay(true)는 Nagle 알고리즘을 끄는 옵션으로, 소량의 패킷을 즉시 전송해야 하는 실시간 애플리케이션에 사용합니다.
Python — TCP 상태 확인 및 Raw 시퀀스 번호 분석
import socket import struct import subprocess # ── TCP 상태 확인 (ss 명령어 래핑) ───────────────────── def show_tcp_states(): result = subprocess.run( ['ss', '-tn', 'state', 'established'], capture_output=True, text=True ) print(result.stdout) # Recv-Q Send-Q LocalAddr:Port PeerAddr:Port # Recv-Q: 수신 버퍼 미처리 데이터 (0이어야 정상) # Send-Q: 미전송 데이터 (0이면 전송 완료) # ── TCP 헤더 파싱 (raw socket, root 권한 필요) ───────── def parse_tcp_header(raw: bytes): # IP 헤더 이후 TCP 헤더 파싱 ip_ihl = (raw[0] & 0x0F) * 4 # IP 헤더 길이 tcp = raw[ip_ihl:] # !HHLLBBHHH: big-endian, 각 필드 언팩 src_port, dst_port, seq, ack_num, \ offset_flags, flags, window, checksum, urg = \ struct.unpack('!HHLLBBHHH', tcp[:20]) syn = (flags & 0x02) != 0 ack = (flags & 0x10) != 0 fin = (flags & 0x01) != 0 rst = (flags & 0x04) != 0 print(f"포트: {src_port} → {dst_port}") print(f"seq={seq}, ack={ack_num}") print(f"SYN={syn} ACK={ack} FIN={fin} RST={rst}") print(f"window={window}, checksum=0x{checksum:04X}") if syn and not ack: print("→ 3-way handshake Step 1: SYN") elif syn and ack: print("→ 3-way handshake Step 2: SYN+ACK") elif fin: print("→ 4-way termination: FIN") # ── TIME_WAIT 확인 ────────────────────────────────────── # $ ss -tn state time-wait # TIME_WAIT는 2MSL(~60초) 후 자동 해제 # SO_REUSEADDR 옵션으로 TIME_WAIT 중 포트 재사용 가능 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(('', 8080))
ss -tn 명령어로 현재 시스템의 TCP 연결 상태를 확인할 수 있습니다. Recv-Q와 Send-Q가 지속적으로 0이 아니라면 버퍼 병목을 의심해야 합니다.
raw socket으로 TCP 헤더를 파싱하면 SYN·ACK·FIN 플래그를 비트 마스킹으로 추출할 수 있습니다. Wireshark·tcpdump가 이 방식으로 동작합니다.
SO_REUSEADDR 옵션을 서버 소켓에 설정하면 TIME_WAIT 상태의 포트를 즉시 재사용할 수 있습니다.
개발 서버 재시작 시 "Address already in use" 오류를 방지합니다.
TIME_WAIT는 OS가 관리하며 약 60초(2MSL) 후 자동 해제됩니다.
대용량 단기 연결이 많은 서버에서는 TIME_WAIT 소켓이 누적되어 포트 고갈이 발생할 수 있습니다.
Q1
TCP 3-way Handshake의 순서는?
Q2
TCP 3-way Handshake의 순서는?
Q3
TCP 연결 종료가 4단계(4-way)인 이유는?
Q4
TCP 연결 종료가 4단계(4-way)인 이유는?
Q5
TCP가 UDP보다 신뢰성이 높은 이유로 가장 적절한 것은?
Q6
TCP가 UDP보다 신뢰성이 높은 이유로 가장 적절한 것은?