TCP 소켓 옵션 - SO_LINGER

김정용·2020년 1월 1일
1

SO_LINGER는 C에서 제공 되는 TCP 소켓 옵션입니다.
SO_LINGER를 사용하면 소켓을 close 했을 때 전송되지 않은 데이터를 어떻게 처리할 것인지 조정할 수 있습니다.
이전 포스팅에서 TCP 데이터 보장에 대한 분석을 통해 TCP는 연결이 끊긴 이후에도 일정시간동안 데이터를 보장해주도록 동작하는것을 알 수 있었습니다.
여기서 SO_LINGER를 활용하면 보장에 대한 시간을 조정할 수 있고 즉시 데이터를 버릴 수 도있습니다.

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

struct linger
{
 int l_onoff;
 int l_linger;
}

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

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

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

여기서는 이전 포스팅의 TCP 데이터 보장에서 테스트했던 케이스를 활용해서 위 세 가지 close 방식 중 2번에 해당하는 l_onoff > 0 이고 l_linger == 0 일경우에 실제로 데이터가 버려지고 비정상종료가 이루어지는지 테스트를 통해 확인해보도록 하겠습니다.

Client


클라이언트 소스의 Linger의 옵션 설정 값을 살펴보겠습니다.

(클라이언트 소스)

#include <stdio .h>
#include <sys socket.h>
#include <arpa inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

#define BUFF_SIZE 1024

int main(int argc, char **argv) {
        struct sockaddr_in serveraddr;
        int server_socket;
        int client_len;
        char write_buf[BUFF_SIZE];

        if(argc != 3)
        {
                printf("usage : %s ip_Address port\n", argv[0]);
                exit(0);
        }

        memset(write_buf, 0x00, sizeof(write_buf));

        if((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
                perror("Error : ");
                exit(1);
        }

        int snd_buf = 8192;
        int option = 1;
        int len = sizeof(snd_buf);

        // Linger structure
        struct linger {
                int l_onoff;
                int l_linger;
        };
        struct linger _linger;

        _linger.l_onoff = 1;
        _linger.l_linger = 0;

        setsockopt(server_socket, SOL_SOCKET, SO_LINGER, &_linger, sizeof(_linger));    // Linger option
        setsockopt(server_socket, SOL_SOCKET, SO_SNDBUF, &snd_buf, sizeof(snd_buf));    // Set sender buffer size
        getsockopt(server_socket, SOL_SOCKET, SO_SNDBUF, &snd_buf, &len);
        printf("Client send buffer size: %d\n", snd_buf);

        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
        serveraddr.sin_port = htons(atoi(argv[2]));

        client_len = sizeof(serveraddr);

        if(connect(server_socket, (struct sockaddr*)&serveraddr, client_len) == -1) {
                perror("Error connect : ");
                exit(1);
        }
  
        while(1)
        {
                printf("write: ");
                fgets(write_buf, sizeof(write_buf), stdin);
                write_buf[strlen(write_buf)-1] = '\0';

                if(write(server_socket, write_buf, BUFF_SIZE) <= 0) {
                        close(server_socket);
                        break;
                }
        }
        return 0;
}

(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

위 netstat 기록에 의하면 버퍼가 꽉찬 상태에서 close를 한 시점에 즉시 상태가 종료되어 더이상 출력되지 않았습니다.
또한 출력 결과를 보면 총 38개의 패킷을 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

Server


(서버 소스)

#include <stdio .h="">
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa inet.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUFF_SIZE 1024

int main(int argc, char *argv[]){
   int server_socket;
   int client_socket;
   int client_addr_size;

   struct sockaddr_in server_addr;
   struct sockaddr_in client_addr;

   char read_buf[BUFF_SIZE];
   char std_in[BUFF_SIZE];
   int rcv_buf = 16384;

   if(argc != 2)
   {
           printf("usage : %s [port]\n", argv[0]);
           exit(0);
   }

   memset(read_buf, 0x00, sizeof(read_buf));
   memset(std_in, 0x00, sizeof(std_in));

   server_socket = socket(PF_INET, SOCK_STREAM, 0);
   if(-1 == server_socket){
      printf( "Fail created socket\n");
      exit( 1);
   }

   int option = 1;
   int len = sizeof(rcv_buf);

   setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
   setsockopt(server_socket, SOL_SOCKET, SO_RCVBUF, &rcv_buf, sizeof(rcv_buf)); // Set receiver buffer size
   getsockopt(server_socket, SOL_SOCKET, SO_SNDBUF, &rcv_buf, &len);
   printf("Server receive buffer size: %d\n", rcv_buf);

   memset(&server_addr, 0, sizeof(server_addr));
   server_addr.sin_family     = AF_INET;
   server_addr.sin_port       = htons(atoi(argv[1]));
   server_addr.sin_addr.s_addr= htonl(INADDR_ANY);

   if(-1 == bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr))){
      printf( "Fail bind\n");
      exit(1);
   }

   if(-1 == listen(server_socket, 5)){
           printf( "Fail listen\n");
           exit(1);
   }

   client_addr_size = sizeof( client_addr);

   client_socket = accept( server_socket, (struct sockaddr*)&client_addr, &client_addr_size);
   if (-1 == client_socket){
           printf( "Fail client accept\n");
           exit(1);
   }

   while(1){

           fgets(std_in, sizeof(std_in), stdin);
           if(read (client_socket, read_buf, BUFF_SIZE) == 0) {
                   close(client_socket);
           }
           printf("read: %s", read_buf);
   }
   
   return 0;
}

(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

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

(결과 출력)

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

위 결과를 보면 클라이언트에서는 38개의 패킷을 보냈지만 서버는 16개의 패킷만 받은 결과를 알 수 있습니다.

TCP 에서의 데이터 보장은 곧 정상종료를 의미합니다.
Linger는 데이터의 보장에 대한 여부를 조정하는 옵션으로 필요에 따라 정상 종료와 비정상종료를 가능하게 합니다.
Linger가 활용되는 한가지 예로 클라이언트 커넥션이 잦은 서버에 TIME_WAIT 상태를 소멸시키기 위한 기법으로 쓰이기도 합니다.
복잡하고 다양한 네트워크 환경에서 필요에 따라 TCP 연결 종료 방식을 조정할 수 있으면 Linger 옵션이 유용하게 활용 될 수 있을것입니다.

profile
Network Programming, Cloud, IOT, Web

0개의 댓글