Computer Science/네트워크 (Network)

네트워크 모델 | (6) TCP/IP 데이터 흐름 더 깊게 이해하기 (feat. Buffered I/O)

hyuga_ 2023. 11. 21. 17:13

앞에서(네트워크 모델 | (5) 데이터 단위와 흐름 이해하기) TCP 통신에서의 데이터 흐름에 대한 큰 그림을 그려보고, 각 단계별 데이터를 뭐라고 부르는지 알아보는 시간을 가졌었다. 

이번에는 이 큰 그림의 세부내용을 구체화해보는 시간을 가져보자.

데이터가 계층을 넘나드는 과정을 디테일하게 그리려면, 우선 Buffered I/O가 무엇인지 알아야 한다. 

 

Buffered I/O

Buffered I/O는 데이터를 메모리의 버퍼에 임시로 저장하고, 버퍼가 가득 차거나 빌 때까지 기다린 후 데이터를 전송하는 방식이다. 

애플리케이션 계층에서 TCP가 있는 전송 계층으로, 그리고 다시 네트워크 계층으로.. 이렇게 데이터를 내려줄 때 버퍼를 사용하여 데이터를 전송하고 수신한다. 잠시 버퍼에 담아두고, 살짝 마사지 해준 후 다음 계층으로 넘겨주는 방식은 네트워크 지연이나 데이터의 부분적인 도착을 관리하는 데 유용하다. 

 

네트워크 통신은 본질적으로 file의 입출력이다. 그리고 이는 각 계층별 버퍼에 데이터를 임시 저장하고, 버퍼끼리 데이터를 read(= receive), write(= send)하는 과정으로 이루어진다. 

 

RIO(Robust I/O) API를 통해 소켓 통신이 프로그래밍되어 있다면, 애플리케이션 계층에서 네트워크를 담당하는 버퍼는 RIO 버퍼라고 부른다. RIO 말고도 현대 프로그래밍 언어, 프레임워크는 자체적인 고수준 네트워크 라이브러리를 제공한다. 

예를 들어 파이썬은 socket 모듈, 자바는 java.net 패키지를 제공하는데, 이들은 RIO와는 다른 접근법을 사용한다고 한다. 

  • 파이썬의 socket 모듈은 BSD 소켓 API를 기반으로 한다. (여기선 send, recv 메서드를 쓴다)

일단, 이번 프로젝트에서는 RIO를 활용하므로, 애플리케이션 계층에서 활용되는 네트워크 버퍼를 RIO 버퍼라고 전제한 후에 내용 정리를 진행하도록 하자. 

 

애플리케이션 버퍼

애플리케이션 계층에 위치하며, 네트워크 읽기/쓰기 작업을 더 효율적이고 안정적으로 만들기 위한 추가적인 버퍼링 계층을 제공한다.

애플리케이션 프로그램이 TCP 네트워크 통신을 수행할 때 데이터를 임시로 저장하고 관리한다.

 

이를 통해 어플리케이션은 데이터를 일관되고 효율적으로 읽고 쓸 수 있다. 애플리케이션 버퍼는 부분적인 읽기나 쓰기 문제를 처리하고, 네트워크 지연에 따른 데이터 전송의 복잡성을 추상화한다.

 

애플리케이션 계층의 버퍼는 프로세스의 가상 메모리 내에 동적으로 할당된다. 즉, 힙 영역에 위치한다. 

다음은 C 프로그래밍으로 소켓 버퍼를 동적 할당하는 예시이다. 

char *buffer = (char *)malloc(BUFFER_SIZE);

 


TCP 소켓 버퍼

TCP 버퍼는 운영 체제의 커널 공간에서 관리되며, 소켓을 통해 전송되는 데이터를 버퍼링한다. 소켓을 통해 들어오거나 나가는 데이터를 임시로 저장한다. 이를 통해 데이터가 네트워크 인터페이스를 통해 순차적이고 안정적으로 전송될 수 있도록 한다.

애플리케이션 버퍼는 애플리케이션 계층에서의 데이터 처리를 간소화하는 역할을 한다면, TCP 버퍼는 네트워크 통신의 신뢰성과 순차성을 보장하는 역할을 한다. 

 

위에서 적었듯 이들은 서로 데이터를 read, write 하면서 교환하고, 그 아래 Network, Network Access 계층에서도 유사한 과정으로 데이터가 이동한다. 

 

 

서버-클라이언트의 TCP 통신 과정

이제 TCP 연결이 된 상태에서,

  1. 서버가 클라이언트에 데이터를 전송하고
  2. 데이터가 네트워크를 통해 이동하고
  3. 클라이언트가 데이터를 받고, ACK 패킷을 송신하고
  4. 서버가 ACK 패킷을 받고 처리하는 과정을 살펴보자. 

지금껏 살펴봤던 데이터 단위, 흐름을 다 알아야 이해할 수 있는 내용이다. 

 

 

1. 서버 (데이터 전송)

packet은 택배 박스로 포장된 거고, frame은 택배 트럭에 탄거라고 생각하면 됨.

왜냐하면 frame은 여러번 갈아타거든. 실제로 헤더에 담기는 데이터도 그런 역할과 비슷하게 담긴다. 

 

1. 데이터 생성 및 버퍼링 (L7)

  • 서버의 애플리케이션 계층에서 생성된 데이터(예: HTML 페이지, API 응답 등)는 애플리케이션 버퍼에 임시 저장된다. 
  • 이 단계에서의 데이터는 일반적으로 스트림(stream) 형태의 원시 데이터이다. 

2. TCP 소켓을 통한 데이터 전송 (L4)

  1. 서버는 소켓 파일 디스크립터를 통해 TCP 소켓에 접근하고, 소켓을 파일처럼 다룬다. 
  2. write() 또는 send() 시스템 콜을 사용하여 애플리케이션 버퍼의 데이터를 TCP 소켓 버퍼로 전송한다. 
  3. 소켓 버퍼에 저장된 데이터는 TCP 세그먼트로 변환되고, TCP 헤더 정보가 추가된다. 이 단계에서의 데이터 단위는 세그먼트(segment)이다.

3. IP 패킷 캡슐화 및 전송 (L3)

  • TCP 세그먼트는 네트워크 버퍼에 저장되며, 여기에서 IP 패킷으로 캡슐화된다. 이 과정에서 IP 헤더가 추가되어 네트워크 계층의 데이터 단위인 패킷(packet)으로 변환된다.
  • 이 패킷은 네트워크 인터페이스를 통해 물리 네트워크로 전송된다. 

4. 데이터 링크 계층 및 물리 계층을 통한 전송 (L1, L2)

  • 네트워크 계층에서 전달된 IP 패킷은 데이터 링크 계층에서 프레임(Frame)으로 캡슐화된다.
  • 이 프레임은 데이터 링크 계층의 네트워크 장치의 버퍼에 저장된다. 
  • 버퍼에 저장된 프레임은 물리 계층으로 전송되기 위해 준비된다. 이 과정에서 네트워크 장치는 버퍼의 내용을 기반으로 물리적 전송에 필요한 조치를 취한다. -> 전기 신호나 광 신호로 변환되어 네트워크를 통해 전송된다.

 

2. 네트워크를 통한 이동

1. 네트워크 간 이동

  • 데이터 패킷은 여러 네트워크 장치(라우터, 스위치 등)를 거치며 전송된다. 이 과정에서 패킷은 여러 네트워크, 서브넷을 거쳐 이동할 수 있다.
  • 라우팅 과정은 네트워크의 상태와 트래픽에 따라 동적으로 변할 수 있다. 라우터는 네트워크 혼잡, 장애, 변경된 경로 등에 반응하여 경로를 조정한다.

2. 일반적인 라우팅 과정

  • 라우터는 네트워크 상에서 데이터 패킷이 목적지까지 도달하는 데 필요한 경로를 결정한다. 라우터는 IP 주소, 라우팅 테이블, 그리고 다양한 라우팅 프로토콜을 사용하여 이를 수행한다.
  • 각 라우터는 프레임을 디캡슐화하여 패킷으로 만들고, 패킷의 헤더를 분석하여 최적의 next hop(다음 단계, 다음 장치)을 결정한다. 그리고 다시 캡슐화하여 프레임을 next hop으로 보낸다. 이는 패킷이 목적지에 도달할 때까지 계속된다. 

 

3. 클라이언트 측 (데이터 수신)

1. 네트워크 접근 계층 (L2)

  • 이 계층에서 수신된 데이터 프레임(Frame)은 NIC의 버퍼에 임시 저장된다.
  • 이 버퍼는 물리적 신호를 디지털 데이터로 변환하는 과정에서 필요한 임시 저장 공간을 제공한다.

2. 네트워크 계층 (L3)

  • IP 패킷이 수신되면, 이들은 OS의 네트워크 스택에 있는 네트워크 계층의 버퍼에 저장된다. 여기서 패킷은 다음 계층으로 전달될 준비가 된다.

3. 전송 계층 (L4)

  • 수신된 TCP 세그먼트는 TCP 버퍼에 저장되며, 여기서 수신된 세그먼트의 순서를 관리, 누락된 부분이나 재정렬이 필요한 부분을 처리한다. (UDP였다면 순서 보장 없이 데이터그램이 처리된다.)
  • ACK 생성 및 전송 
    • 세그먼트가 성공적으로 수신되면, TCP 스택은 ACK 패킷을 생성하고, 송신자에게 전송한다.
    • 여기서 전송된 ACK 패킷의 향방은 (4. 서버 ACK 수신) 으로.. 
  • 스트림 변환
    • 버퍼에 저장된 세그먼트를 연속적인 데이터 스트림(Stream)으로 변환한다.
      (TCP는 연결 지향적이고 스트림 기반의 통신을 제공하는 특성을 갖고있다.)
  • 변환한 스트림 데이터를 애플리케이션 계층으로 전달한다. 

4. 애플리케이션 계층 (L7, Application Layer)

  • 수신된 스트림 데이터는 애플리케이션의 내부 버퍼에 저장되며, 여기서 필요한 처리가 이루어진다.
    (예를 들어, 웹 브라우저는 이 버퍼에 있는 HTML 데이터를 렌더링하거나, 이메일 클라이언트는 메시지를 표시한다.)

ACK 패킷에 담기는 정보

  1. ACK 플래그
    • ACK 패킷은 TCP 헤더 내의 플래그 중 하나인 ACK 플래그를 설정한다.
    • 이 플래그가 설정되면, 패킷이 데이터의 수신을 확인하는 용도임을 나타낸다. 
  2. 확인 응답 번호 (ACK number, Acknowledgement number)
    • 이 필드는 클라이언트가 다음에 기대하는 바이트의 순서 번호를 포함한다.
    • 예를 들어, 확인 응답 번호가 n이라면, 수신자는 순서 번호 n 이전의 모든 데이터를 성공적으로 수신했음을 나타낸다. 
      (즉, n번부터 데이터를 달라는 신호가 된다.)
  3. Window Size
    • 클라이언트 측의 TCP 스택은 TCP 버퍼의 크기, 트래픽 양 등을 고려하여 window size를 정한다. 이는 한번에 처리할 수 있는 데이터의 양을 나타낸다.
    • 버퍼 오버플로우(버퍼에 들어온 데이터를 처리하는 속도보다 버퍼로 들어오는 속도가 빠른 경우 발생)를 방지하고, 효율적인 데이터 전송을 보장하기 위해 이런 요소가 도입되었다. 
  4. 체크섬(checksum)
    1. 모든 TCP 패킷에는 데이터의 무결성을 확인하기 위한 체크섬이 포함된다.
    2. UDP 패킷에도 체크섬은 포함된다. 
  5. 그외 옵션 필드, 헤더 길이 등

4. 서버 측 (ACK 패킷 수신)

  1. 서버는 아까 데이터 패킷을 보낸 이후로 ACK이 올 때까지 기다리고 있는다. (wait)
    (이 기다림이 UDP 대비 속도지연이 있는 가장 큰 이유라고 한다.)
  2. ACK 패킷을 수신하면, 디캡슐화를 한 다음에 헤더에 담겨있는 내용을 확인한다. 
  3. ACK 패킷이 오면 확인 응답 번호를 체크한다.
  4. 만약, 보낼 데이터의 용량이 window size보다 크다면? 보내지 않고 대기한다. 이러한 현상이 반복되거나 길어질 경우 체감되는 통신 지연으로 이어진다. 
  5. 그렇지 않다면 (window size > segment size) 다음 순번의 데이터를 보낸다.

 

위 과정에서 TCP의 전송 보장, 전송 제어 등 특성이 무엇을 의미하는지 확인할 수 있다. 

 

 

더보기

이게 중요한 이유가, 아니 왜이렇게 데이터 느려?? 라고 투정부리는게 주로 클라이언트인데, 알고 보면 클라이언트측 문제일 수 있다는 거.

 

TCP 버퍼에서 최대한 빠르게 File I/O 버퍼로 올려주는게 중요하겠지? 즉 Process 단에서 빠르게 Read(Receive)를 해주는게 중요하다.

그래야 남은 window size가 계속 확보될테니까.. 서버 측에서도 기다리지 않고 전송하겠지. 

 

이게 네트워크 관련 문제를 파악하고 해결하는데 많은 도움이 될 거임. 이런 경우에는 네트워크에서 장애 원인을 찾지 말고 프로그램 로직에서, 특히 클라이언트 쪽에서 찾아봐야겠지?

 


아래 유튜브 강의 영상이 많은 도움이 되었다. 진짜 명강인 것 같으니 다들 한번씩 보시길 ..👍

 

이해하면 인생이 바뀌는 TCP 송/수신 원리