ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • TCP 데이터 보장 원리에 대해 파헤쳐보기 2 - SO_LINGER
    개발 2025. 8. 7. 18:51

    SO_LINGER는 C에서 제공되는 소켓 옵션이다. SO_LINGER는 소켓을 close 했을 때 전송되지 않은 데이터를 어떻게 처리할 것인지에 대한 처리 규칙이다.

    이전 포스팅 분석에 의하면 TCP는 연결이 끊긴 이후에도 일정 시간 동안 데이터를 보장해주도록 동작하는 것을 알 수 있다.
    여기에 SO_LINGER를 활용하면 보장에 대한 시간을 조정할 수 있고 즉시 데이터를 버릴 수 도있다.

    다음은 SO_LINGER 옵션에 사용되는 데이터 구조체이다.

    struct linger
    {
       int l_onoff;
       int l_linger;
    }
    • l_onoff : Linger 옵션을 활성화할 것인지 비 활성화할 것인지에 대한 플래그
    • l_linger : Linger 옵션이 활성화되었을 때 기다리는 시간

    위 두 개의 멤버 변수의 값에 따라 다음 세 가지 close 방식이 결정된다.

    • l_onoff == 0 : Linger를 비활성화하겠다는 것으로 소켓의 default 값이다. 소켓 버퍼에 남아있는 모든 데이터를 전송하는 일반적인 소켓의 정상 종료가 이루어진다.
    • l_onoff > 0이고 l_linger == 0 : close가 즉시 리턴을 해서 상태가 종료 되고 소켓 버퍼에 남아있는 데이터를 버리는 비정상 종료가 이루어진다. 만약 TCP 연결 상태일 경우에는 상대편 호스트에 리셋을 위한 RST 패킷을 보낸다.
    • l_onoff > 0이고 l_linger > 0 : 지정한 시간동안 대기하고 버퍼에 남아있는 데이터를 모두 보낸다. 지정된 시간 내에 데이터를 모두 보냈다면 정상적으로 리턴이 되고 시간이 초과되었다면 에러와 함께 리턴이 되는 방식으로 특정 조건에 따라 비정상 종료 혹은 정상 종료가 이루어진다.

    이전 포스팅에서 테스트했던 케이스를 활용해서 위 세 가지 close 방식 중 2번에 해당하는 l_onoff > 0 이고 l_linger == 0 일 경우에 데이터가 버려지고 비정상 종료가 이루어지는 지에 대한 테스트를 진행하였다.

    클라이언트

    다음 테스트 케이스는 클라이언트의 송신 버퍼가 꽉 찬 상태에서 close를 한 것이다.

    netstat 로그

                (송신 버퍼)                                                상태
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0      0 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0   1024 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0   3072 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0   5120 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0   7168 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0   9216 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  10240 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  12288 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  13312 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  15360 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  17408 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  18432 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  20480 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED
    tcp        0  21720 192.168.0.195:33064     192.168.0.192:8002      ESTABLISHED

    write 로그

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

    서버

    서버 netstat 로그에 의하면 버퍼를 비우는 시점에 즉시 클라이언트 상태가 사라진 것을 알 수 있다. 이것은 이미 close 했던 클라이언트의 상태를 이 시점에 인지한 것으로 볼 수 있다. 그런데 다음 출력 결과에서 보이는 것처럼 상태가 사라졌음에도 불구하고 수신 버퍼에 남아있던 데이터는 모두 읽어 들였다. 그렇다면 상태가 사라져도 수신 버퍼는 소멸되지 않고 데이터를 읽을 때까지 남아 있는 것인가? (이 부분에 대해서는 좀 더 검토해야 될 부분으로 남겨두었다.)

    결론은 예상했던 것처럼 클라이언트의 송신 버퍼에 남아있던 데이터들은 읽지 못했다는 것이다.

    netstat 로그

         (수신 버퍼)                                                      상태
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp        0      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     1024      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     4096      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     7168      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp     9216      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    10240      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    12288      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    14336      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    tcp        0      0 0.0.0.0:8002            0.0.0.0:*               LISTEN
    tcp    16384      0 192.168.0.192:8002      192.168.0.195:33064     ESTABLISHED
    *******************************************************************************
    버퍼를 비우는 시점
    *******************************************************************************
    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
    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: 16
    read: 16

    TCP에서의 데이터 보장은 곧 정상종료를 의미한다.

    SO_LINGER는 데이터의 보장에 대한 여부를 조정하는 옵션으로 필요에 따라 정상 종료와 비정상 종료를 가능하게 한다. SO_LINGER가 활용되는 한 가지 예로 클라이언트 커넥션이 잦은 서버에 TIME_WAIT 상태를 소멸시키기 위한 기법으로 쓰이기도 한다.

    복잡하고 다양한 네트워크 환경에서 필요에 따라 TCP 연결 종료 방식을 조정할 수 있으면 SO_LINGER 옵션이 유용하게 활용될 수 있을 것이다.

    '개발' 카테고리의 다른 글

    알아두면 유용한 정렬 알고리즘  (2) 2025.08.07
    Go 언어(Golang)  (2) 2025.08.07
    TCP 소켓 Blocking / Non-blocking  (3) 2025.08.07
    TCP 데이터 보장 원리에 대해 파헤쳐보기 1  (2) 2025.08.05
    Apache JMeter 활용 해보기  (0) 2022.03.13

    댓글

Designed by Tistory.