cd ../
Network·2026-02-10·5 min read·# entry/019

TCP 서버의 내부 동작 심층 분석: 데이터 복사부터 흐름 제어까지

서버가 파일을 전송할 때 커널 내부에서는 무슨 일이 일어날까? I/O 성능의 핵심인 Zero-copy와 TCP 속도를 결정짓는 Sliding Window 메커니즘을 파헤칩니다.

우리가 흔히 말하는 웹 서버(Web Server) 는 OS 입장에서는 하나의 프로세스(Process) 에 불과합니다. 그리고 이 프로세스가 소켓(Socket)을 통해 데이터를 내보내는 행위는, 결국 파일(File)을 읽고 쓰는 I/O 작업의 연장선입니다.

서버의 디스크에 잠들어 있는 파일이 랜선을 타고 나가기까지, 커널 내부에서 벌어지는 '데이터의 이동''TCP의 기다림' 을 단계별로 분석해 봅니다.

1. 데이터 이동의 비용: Buffered I/O vs Zero-copy

서버가 클라이언트에게 파일을 전송하려면, 하드디스크(HDD/SSD)에 있는 데이터를 메모리로 읽어와야 합니다. 이 과정에서 데이터는 커널 영역과 유저 영역을 넘나들며 여러 번 복사됩니다.

전통적인 방식: Buffered I/O (Standard I/O)

일반적인 read()write() 시스템 콜을 사용할 때 발생하는 과정입니다.

1

1. DMA (Direct Memory Access)

하드웨어 드라이버가 디스크의 데이터를 읽어 커널 영역의 PageCache로 복사합니다. (CPU 개입 없음)

2

2. Kernel to User (CPU Copy)

커널은 PageCache에 있는 데이터를 유저 영역의 애플리케이션 버퍼로 복사합니다. * 문제점: 이 과정에서 **컨텍스트 스위칭(Context Switching)**이 발생하여 CPU 자원을 소모합니다.

3

3. User to Kernel (CPU Copy)

애플리케이션이 send()를 호출하면, 유저 버퍼의 데이터를 다시 **커널 영역의 소켓 버퍼(Socket Buffer)**로 복사합니다.

4

4. DMA to NIC

소켓 버퍼의 데이터가 네트워크 인터페이스 카드(NIC)로 전달되어 전송됩니다.

성능의 혁신: Zero-copy (sendfile)

만약 읽어온 파일 내용을 애플리케이션이 가공(압축, 암호화 등)할 필요 없이 그대로 보낸다면? 유저 영역을 갔다 오는 것은 낭비입니다.

Zero-copy의 원리

리눅스의 sendfile() 같은 시스템 콜을 사용하면, 데이터가 PageCache에서 곧바로 소켓 버퍼로 이동(혹은 참조)합니다. * 효과: 불필요한 데이터 복사와 컨텍스트 스위칭이 사라져, CPU 부하가 줄고 전송 속도가 획기적으로 향상됩니다. Nginx, Kafka 같은 고성능 서버들이 이 방식을 채택합니다.


2. 데이터의 변신: 세그먼트와 패킷

소켓 버퍼에 안착한 데이터는 네트워크 선로를 타기 위해 잘게 쪼개지고 포장됩니다.

  1. 세그먼트(Segment): TCP 계층에서 데이터를 MSS(Maximum Segment Size) 단위로 자릅니다. 이때 각 조각에는 순서를 보장하기 위한 **순서 번호(Sequence Number)**가 붙습니다.
  2. 패킷(Packet): IP 계층에서 세그먼트에 출발지/목적지 IP 주소를 붙여 포장합니다.
  3. 프레임(Frame): 링크 계층에서 물리적 주소(MAC)를 붙여 최종적으로 전송합니다.

3. TCP의 핵심: 흐름 제어 (Flow Control)

TCP가 UDP보다 느린 이유는 **"확실하게 보내기 위해 기다리기 때문"**입니다. 이 기다림의 미학을 **슬라이딩 윈도우(Sliding Window)**라고 합니다.

ACK와 윈도우 사이즈 (Window Size)

서버는 데이터를 무작정 보내지 않습니다. **수신측(Client)이 받아들일 수 있는 양(Window Size)**만큼만 보냅니다.

  1. 전송: 서버는 현재 윈도우 사이즈가 허용하는 만큼 세그먼트들을 연속해서 쏩니다. 2. ACK 수신: 클라이언트는 데이터를 잘 받았다는 신호(ACK)와 함께, **"나 버퍼에 이만큼 공간 남았어(Window Size)"**라는 정보를 보냅니다.
  2. 윈도우 슬라이딩: 서버는 ACK를 받은 만큼 윈도우를 옆으로 밀어(Slide), 다음 데이터를 전송할 자격을 얻습니다.

4. 병목의 진실: 왜 네트워크가 느릴까?

네트워크 회선은 1Gbps인데 실제 속도는 10Mbps도 안 나오는 경우가 있습니다. 범인은 의외로 **수신측(Client)**일 수 있습니다.

Receive Window가 0이 되는 순간

TCP는 OS가 관리하지만, 최종적으로 그 데이터를 가져가는 건 **애플리케이션(Process)**입니다.

  1. 수신: 클라이언트의 OS는 데이터를 받아 TCP 수신 버퍼에 쌓아둡니다.
  2. 병목: 만약 클라이언트 애플리케이션이 바빠서(혹은 I/O가 느려서) 버퍼에서 데이터를 가져가는 속도가 느리다면?
  3. 포화: 수신 버퍼가 꽉 찹니다.
  4. 통보: 클라이언트는 서버에게 **"Window Size = 0"**이 담긴 패킷을 보냅니다.
  5. 중단: 서버는 윈도우가 다시 열릴 때까지 전송을 강제로 중단합니다.
성능 튜닝의 Insight

네트워크 지연이 발생할 때, 무조건 서버나 회선 탓을 하기 전에 **수신측 시스템의 부하(CPU, Disk I/O)**를 점검해야 합니다. 수신측이 데이터를 빨리빨리 소화해주지 못하면, 송신측은 아무리 성능이 좋아도 기다릴 수밖에 없는 것이 TCP의 운명입니다.


요약

  • I/O 최적화: 서버 내부에서는 Zero-copy 기술을 통해 불필요한 메모리 복사와 컨텍스트 스위칭을 줄여야 합니다.
  • TCP 지연: TCP의 속도는 Window Size와 **RTT(왕복 시간)**의 관계에서 결정됩니다.
  • 병목 지점: 수신측의 애플리케이션 처리 속도가 전체 통신 속도의 한계점이 될 수 있습니다.