ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCP 데이터 보장 원리에 대해 파헤쳐보기 1
    개발 2025. 8. 5. 16:56

    TCP는 데이터의 전송을 보장하는 신뢰성 있는 프로토콜이다.
    호스트 간에 갑작스럽게 연결 종료가 되었다거나 전송 지연 등으로 인해 데이터의 유실 소지가 있는 네트워크 상황에서 TCP는 내부적으로 어떤 원리로 데이터를 보장해주는지 관련 테스트를 통해 알아보기로 했다.

    TCP 상태

    테스트를 진행하기에 앞서 TCP 상태에 대해 알아보겠다.

    각 상태들은 다음을 의미한다.

    • LISTEN : 서버가 소켓을 bind 하고 클라이언트의 접속 요청을 기다린다.
    • SYN_SENT : 클라이언트가 서버로 접속 요청을 한다.
    • SYN_RCVD : 서버가 클라이언트로부터 접속 요청을 받고 클라이언트에게 접속 요청 응답한다.
    • ESTABLISHED : 클라이언트와 서버가 서로 세션 확립이 이루어졌다.
    • FIN_WAIT_1 : 접속을 끊기 위해 소켓을 close 하고 close 요청을 한다. close 발생 주체는 서버가 될 수도 있고 클라이언트가 될 수도 있다.
    • CLOSE_WAIT : close 요청을 받고 응답을 보낸다.
    • FIN_WAIT_2 : close 요청에 대한 응답을 받고 최종 응답을 기다린다.
    • LAST_ACK : 소켓을 close를 하고 최종 close 응답을 한다.
    • TIME_WAIT : 최종 close 응답을 받고 미처 처리되지 못한 데이터를 처리하기 위해 일정 시간 동안 유지된다.

    테스트

    테스트 환경

    • OS: Linux CentOS 7
    • 클라이언트/서버 애플리케이션 언어 : C

    리눅스에서 TCP는 통신 시 데이터를 효율적으로 전송하기 위해 버퍼라고 하는 임시 데이터 공간을 사용한다.
    이것으로 인해 응용프로그램으로부터 생성된 소켓은 소켓마다 시스템으로부터 일정 크기의 송신 버퍼와 수신 버퍼를 할당받게 된다.

    리눅스에서 TCP는 통신 시 데이터를 효율적으로 전송하기 위해 버퍼라고 하는 임시 데이터 공간을 사용한다.
    이것으로 인해 응용프로그램으로부터 생성된 소켓은 소켓마다 시스템으로부터 일정 크기의 송신 버퍼와 수신 버퍼를 할당받게 된다.

    다음 그림은 클라이언트 서버 모델 에서의 소켓 버퍼 동작 구조를 표현한 것이다.
    이는 클라이언트에서 서버로 데이터를 보내고 응답받는 과정에 해당한다.
    클라이언트, 서버 각각 자신의 소켓 버퍼를 가지고 있는 것을 알 수 있다.
    응용 프로그램에서는 데이터를 read/write 하게 되고 해당 데이터는 버퍼를 통해 전송된다.

    1. 클라이언트에서 서버에 보낼 데이터를 write 한다.
    2. 데이터가 클라이언트의 송신 버퍼(Send Buffer)에 적재된다.
    3. 적재된 데이터가 서버의 수신 버퍼(Receive Buffer)에 전송된다.
    4. 서버가 수신 버퍼에 적재된 데이터를 read 한다.
    5. 서버에서 클라이언트에게 응답할 데이터를 write 한다.
    6. 데이터가 서버의 송신 버퍼에 적재된다.
    7. 적재된 데이터가 클라이언트의 수신 버퍼에 전송된다.
    8. 클라이언트가 수신 버퍼에 적재된 데이터를 read 한다.

    사용자가 직접 버퍼 사이즈를 조정할 수도 있다.
    C에서는 소켓 버퍼 사이즈를 설정할 수 있는 소켓 옵션(SO_SNDBUF, SO_RCVBUF)이 제공된다.
    여기서 유의해야 할 점은설정 값이 그대로 적용되는 게 아니라 커널상에서 사용자가 설정한 값을 기반으로 임의로 적용된다는 것이다.
    그래서 설정한 값에 대한 재확인이 필요할 수 있다.

    버퍼를 이용한 통신 구조를 보면 서버 입장에서 수신 버퍼에 쌓여있던 데이터는 “수신하려 했던 데이터”에 해당하고 클라이언트 입장에서 송신 버퍼에 쌓여있던 데이터는 “송신하려 했던 데이터” 에 해당하는 것으로 여길 수 있다.
    만약 응용프로그램이 갑작스럽게 종료되어 세션이 끊긴다면 버퍼에 남아있는 데이터들은 어떻게 되는 것일까?
    이에 대한 테스트를 위해 다음과 같은 시나리오를 구상하게 되었다.

    다음 과정은 서버 클라이언트 통신 중 세션이 끊겼을 시 버퍼에 남아있는 데이터가 어떻게 처리되는 지를 확인해보기 위한 시나리오이다.

    1. 클라이언트에서 서버에 데이터를 write 한다.
    2. 서버에서 클라이언트의 데이터를 read 하기 전에 임의대로 행을 걸어놓고 read를 하지 못하게 막는다.
    3. 1번 과정을 반복해서 서버의 수신 버퍼와 클라이언트의 송신 버퍼에 패킷이 쌓이도록 한다.
    4. 클라이언트 응용프로그램을 강제로 종료해서 연결을 끊는다.
    5. 서버의 행을 풀고 쌓여있던 모든 패킷을 read 한다.

    테스트 결과

    다음은 서버 측과 클라이언트 측의 테스트 로그 출력 결과이다.
    로그 출력을 위해 netstat를 활용해서 실시간으로 로컬의 네트워크 상태와 소켓 버퍼의 상태를 체크하는 방식으로 테스트를 진행했다.
    로그 상에서는 클라이언트가 언제 close를 했는지 알 수 없는데 이유는 클라이언트 버퍼로부터 패킷을 받는 시점에 세션 상태가 CLOSE_WAIT으로 바뀌었기 때문이다.
    이를 통해 시스템에서 연동 호스트의 close 여부는 버퍼에 패킷이 채워지는 시점이라는 것을 알 수 있었다.
    클라이언트의 송신 버퍼가 꽉 찼을 경우엔 클라이언트 소켓이 블록 상태가 되고 서버가 read를 해주기 전까지는 더 이상 write를 할 수 없다는 것도 알 수 있었다.
    결과적으로 버퍼가 모두 비워지기 전까진 세션 상태가 CLOSE_WAIT 인 상태에 머무르고 데이터 또한 모두 read 하는 것을 볼 수 있는데 이것으로 세션이 끊긴 상황에서 시스템 측에서는 해당 세션에 대해 CLOSE_WAIT 상태에 머무르며 버퍼에 남아있는 데이터들이 보장된다는 것을 알 수 있었다.

    서버 netstat 로그

         (수신 버퍼)                                                      상태
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     1024      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     3072      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     6144      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     9216      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    10240      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    12288      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    14336      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    15360      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    *******************************************************************************
    수신 버퍼가 꽉 찬 시점
    *******************************************************************************
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    *******************************************************************************
    read 시작
    *******************************************************************************
    tcp    15360      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    13312      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    10240      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     8192      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     3072      0 192.168.0.195:8002      192.168.0.192:57686     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    20697      0 192.168.0.195:8002      192.168.0.192:57686     CLOSE_WAIT
    *******************************************************************************
    클라이언트 송신 버퍼로부터 패킷을 받아 다시 버퍼가 차는 시점
    세션 상태 CLOSE_WAIT로 진입
    *******************************************************************************
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    15577      0 192.168.0.195:8002      192.168.0.192:57686     CLOSE_WAIT
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    10457      0 192.168.0.195:8002      192.168.0.192:57686     CLOSE_WAIT
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     6361      0 192.168.0.195:8002      192.168.0.192:57686     CLOSE_WAIT
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     1241      0 192.168.0.195:8002      192.168.0.192:57686     CLOSE_WAIT
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    *******************************************************************************
    모든 버퍼를 다 받은 후 클라이언트 세션 close
    *******************************************************************************
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN

    서버 read결과

    Server receive buffer size: 16384
    
    read: 1
    read: 2
    read: 3
    read: 4
    read: 5
    read: 6
    read: 7
    read: 8
    read: 9
    read: 10
    read: 11
    read: 12
    read: 13
    read: 14
    read: 15
    read: 16
    read: 17
    read: 18
    read: 19
    read: 20
    read: 21
    read: 22
    read: 23
    read: 24
    read: 25
    read: 26
    read: 27
    read: 28
    read: 29
    read: 30
    read: 31
    read: 32
    read: 33
    read: 34
    read: 35
    read: 36
    read: 37
    read: 38

    클라이언트 netstat 로그

                (송신 버퍼)                                                상태
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0   2048 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0   4096 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0   6144 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0   9216 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  10240 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  12288 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  14336 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  17408 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  18432 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  19456 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21504 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    *******************************************************************************
    송신 버퍼가 꽉찬 시점
    *******************************************************************************
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    tcp        0  21720 192.168.0.192:57686     192.168.0.195:8002      ESTABLISHED
    *******************************************************************************
    클라이언트 close
    서버로부터 close ACK를 받을 때까지 FIN_WAIT1 상태 진입
    *******************************************************************************
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0  21721 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT1
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT2
    *******************************************************************************
    서버의로부터 close ACK를 받고 수신 버퍼가 비면 송신 버퍼에 남아있는 패킷 전송
    서버로부터 FIN 응답을 받을때까지 FIN_WAIT2 상태 진입
    *******************************************************************************
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT2
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT2
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      FIN_WAIT2
    *******************************************************************************
    서버로부터 FIN 응답을 받고 TIME_WAIT 상태 진입
    *******************************************************************************
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT
    tcp        0      0 192.168.0.192:57686     192.168.0.195:8002      TIME_WAIT

    이것으로 TCP의 데이터 보장과 관련된 한 가지 예를 알아보았다.
    이번 과정을 통해 원리를 이해했다면 TCP 네트워크 환경에서의 이슈 대처에 도움이 될 것으로 본다.

    댓글

Designed by Tistory.