TCP socket library 만들기 [두번째] : Server Socket 준비 및 접속대기(listen)

이번 시간에는 tcpsvr class 의 open 함수를 구현해 봅시다. (tcp_socket.cpp)
tcpsvr 의 open 함수가 하는 일은 다음과 같아야 합니다.

  • socket 생성
  • 주소 binding
  • socket option 변경
  • listen

사용자는 객체 생성 후 open 하나만 호출하면 자동으로 client 의 접속을 기다리는 상태까지 되도록 만드는 것입니다.
사용하기가 아주 편리하겠죠 ?
다만, 위의 각 과정을 수행하려면 socket option 값 등이 객체의 멤버 변수로 셋팅이 되어 있어야 하므로, 이를 객체 생성 시 인자로 줄 수 있도록 하였는데 이전 시간의 class 설계 코드를 보시면 알 수 있습니다.
제가 구현에 이용한 socket option 들에 대해서는 다음에 하나하나 살펴보도록 하겠습니다.

#include <tcp_socket.h>

int tcpsvr::open( int   port )
{
    int                 ret;
    struct sockaddr_in  in_sock;
    int                 sock;
    int                 optval;

    memset((char *)&in_sock, 0x00, sizeof(struct sockaddr_in));

    // socket 생성
    sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if( sock < 0 ) return -1;

    // 객체 멤버 변수 설정
    m_port    = port;
    m_sock_fd = sock;

    // socket 주소 binding
    in_sock.sin_family = AF_INET;
    in_sock.sin_addr.s_addr = htonl(INADDR_ANY);
    in_sock.sin_port = htons(port);

    ret = bind( sock, (struct sockaddr*)&in_sock, sizeof(in_sock));
    if( ret < 0 ) return -2;

    // SO_REUSEADDR 를 필수 option 으로 설정
    optval = 1;
    ret = setsockopt( sock,
                            SOL_SOCKET,
                            SO_REUSEADDR,
                            (void *)&optval,
                            sizeof(optval) );
    if( ret < 0 ) return -3;

    // 기타 socket option 설정
    ret = set_sock_option();
    if( ret < 0 ) return ret;

    // listen 준비
    ret = listen( sock, 5 );
    if( ret < 0 ) return -4;

    return sock;
}

server 측은 위와 같이 준비해둔 후 위에서 리턴하는 listen 용 socket 을 select 를 이용해 해당 socket 에 connection 요청이 오는지 event 를 감시하면 됩니다. 실제 사용예는 차차 살펴보도록 하겠습니다.

위의 각 단계는 왜 필요한지 잘 아실 것으로 믿고, 저는 socket option 들에 대해서만 설명을 드리겠습니다.
SO_REUSEADDR option 은 아실 것으로 믿고 넘어가도록 하겠습니다 (모르시면 인터넷 찾아보시면 많이 나옵니다).
이 개념들은 앞으로 socket 프로그래밍을 하면서 필수적인 개념들이니 잘 알아두시기 바랍니다.

int socket_base::set_sock_option()

{
    int     ret = 0;
    int     flag;

    // nagle 알고리즘 적용 여부 선택
    if( m_no_delay_f > 0 )
    {
        ret = setsockopt( m_sock_fd,
                                IPPROTO_TCP,
                                TCP_NODELAY,
                                (void *)&m_no_delay_f,
                                sizeof(m_no_delay_f) );
        if( ret < 0 ) return -1;
    }

    //  socket 전송 버퍼 크기 설정
    if( m_send_buf_size > 0 )
    {
        ret = setsockopt( m_sock_fd,
                                SOL_SOCKET,
                                SO_SNDBUF,
                                (void *)&m_send_buf_size,
                                sizeof(m_send_buf_size) );
        if( ret < 0 ) return -2;
    }

    // socket 수신 버퍼 크기 설정
    if( m_recv_buf_size > 0 )
    {
        ret = setsockopt( m_sock_fd,
                                SOL_SOCKET,
                                SO_RCVBUF,
                                (void *)&m_recv_buf_size,
                                sizeof(m_recv_buf_size) );
        if( ret < 0 ) return -3;
    }

    // socket 을 nonblock 모드로 설정
    if( m_non_block_f > 0 )
    {
        flag = fcntl( m_sock_fd, F_GETFL, 0 );
        ret = fcntl(m_sock_fd, F_SETFL, flag | O_NDELAY );
        if( ret < 0 ) return -4;
    }

    return ret;
}

제가 TCP socket 프로그래밍을 할 때 성능 튜닝 등의 이유로 항상 고려하는 socket option 이 다음의 3 가지입니다.

  • TCP_NODELAY : Default 는 Delay
  • SO_SNDBUF, SO_RCVBUF : Default 값은 커널설정에 따라 다름
  • O_NDELAY : Default 는 Blocking 모드

TCP_NODELAY 옵션은 1(TRUE) 또는 2(FALSE)로 선택할 수 있습니다.
option 값으로 1 을 지정하면 nagle 알고리즘이 적용되지 않아서 Packet 을 보낼 때 작게작게 끊어서 보내게 됩니다.
2 로 지정하면 작은 Packet 들을 모아서 한번에 많이 보내는 방식을 사용합니다.
만약 구현하려는 시스템이 Low Latency(빠른 응답속도)를 원한다면 1 로 지정하고 사용하는 것이 맞습니다.
그렇지 않은 경우는 2 (Default)로 설정하면 됩니다.

SO_SNDBUF 와 SO_RCVBUF 는 커널의 TCP 단 Buffer 의 크기를 지정하는 것입니다.
즉, 시스템은 빠른데 네트워크 속도가 느리면 TCP 커널 Send Buffer 에 packet 이 쌓이게 될텐데, 이 때 Buffering 이 가능한 크기를 말합니다. SO_RCVBUF 는 반대가 되겠죠.
시스템이 무엇때문인지 Job 처리가 느려서 네트워크로 전송되어온 Packet 에 대한 처리가 늦어지면 TCP Recv Buffer 가 차게 될 겁니다.
때로 이 값이 너무 작으면 성능이 떨어지는 경우가 있어서 값을 늘린 후 테스트하는 경우도 있습니다.
위와 같이 application 단에서 설정할 수도 있지만, 커널 설정을 바꿀 수도 있습니다. (시스템마다 커널 설정방법은 다른데 한번 찾아보시기 바랍니다.)

O_NDELAY 는 socket 을 non blocking 모드로 변경해주는 옵션입니다.
이 옵션은 setsockopt 가 아니라 fcntl 을 이용해서 file FD 처럼 수정합니다.
어디선가 socket option 변경으로도 했던 것 같은데 생각은 안나네요.
socket 이 blocking 이라는 것은 해당 socket 을 이용하여 데이터를 읽거나 할 때 데이터가 올 때까지 recv 함수에서 리턴하지 않고 대기하는 모드입니다. 당연히 다음 job 으로 진행을 못합니다.
그래서, 보통은 없으면 바로 리턴하여 다른 job 을 수행한 후 다시 recv 를 시도해보는 routine 으로 구현하는 경우가 많습니다. (non blocking)

You may also like...