[Socket] setsockopt(2)에 대해 알아보자

윤동환·2023년 4월 13일
0

Network

목록 보기
5/9
post-thumbnail

setsockopt()?

파일 설명자가 참조하는 소켓에 대한 옵션을 조작합니다.

 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

매개변수

  • sockfd
  • level
    소켓 API 수준에서 옵션을 조작하기 위해 수준은 SOL_SOCKET으로 지정합니다.
  • optname
    설정을 위한 소켓 옵션의 번호입니다.
    해석을 위해 적절한 프코토콜 모듈에 해석되지않은 상태로 전달됩니다.
  • optval, optlen
    setsockopt의 옵션값에 액세스하는데 사용됩니다.
    getsockopt의 경우 옵션값이 반환될 버퍼를 식별합니다.
    optlen은 초기에 optval이 가리키는 버퍼의 크기를 포함하고 반환된 값의 실제 크기를 나타내도록 반환시 수정되는 값(인수) 입니다.
    옵션값을 제공하거나 반환하지 않으면 NULL이 될 수 있습니다.
    부울 옵션 활성화 하려면 optval을 0이 아닌수, 비활성화는 0으로 설정한다.

반환 값

성공 시 : 0 반환
실패 시 : -1 반환
Netfilter를 사용하면 관련 처리기와 함께 사용자 지정 소켓옵션 정의 할 수 있습니다.
이 때 반환 값은 처리기의 반환 값입니다.

error 값

  • EBADF : 유효한 FD가 아닐 때
  • EFAULT : optval이 가리키는 주소나 optlen이 프로세스의 주소공간의 유효하지 않은 부분일 때
  • EINVAL : optlen이 유효하지 않을 때
  • ENOPROTOOPT : 표시된 수준에서 알 수 없은 옵션입니다.
  • ENOTSOCK : sockfd가 소켓을 참조하지 않을 때

SOL_SOCKET레벨에서 사용할 수 있는 옵션과 데이터 형

옵션값데이터형설명
SO_BROADCASTBOOL브로드캐스트 메시지 전달이 가능하도록 한다.
SO_DEBUGBOOL디버깅 정보를 레코딩 한다.
SO_DONTLINGERBOOL소켓을 닫을때 보내지 않은 데이터를 보내기 위해서 블럭되지 않도록 한다.
SO_DONTROUTEBOOL라우팅 하지 않고 직접 인터페이스로 보낸다.
SO_OOBINLINEBOOLOOB 데이터 전송을 설정할때, 일반 입력 큐에서 데이터를 읽을 수 있게 한다. 이 플래그를 켜면 recv(:12)나 send(:12)에서 MSG_OOB 플래그를 사용할 필요 없이 OOB 데이터를 읽을 수 있다.
SO_GROUP_PRIORITYint사용하지 않음
SO_KEEPALIVEBOOLKeepalives를 전달한다.
SO_LINGERstruct LINGER소켓을 닫을 때 전송되지 않은 데이터의 처리 규칙
SO_RCVBUFint데이터를 수신하기 위한 버퍼공간의 명시
SO_REUSEADDRBOOL이미 사용된 주소를 재사용 (bind) 하도록 한다.
SO_SNDBUFint데이터 전송을 위한 버퍼공간 명시

SO_REUSEADDR 사용 사례

소켓을 이용한 서버프로그램을 운용하다보면 강제종료, 비정상 종료 되는 경우가 발생합니다. 테스트 목적으로 할 경우, 특히 강제 종료 시켜야 하는 경우가 자주 발생하는데,
bind error : Address already in use
이러한 에러메시지를 종종 보게된다. 이는 비정상 종료된 상태로 커널이 bind정보를 유지하고 있는 상태이며 1-2분뒤에 커널이 알아서 정리하지만 테스트 할 때 기다리는 것은 번거롭습니다.

int sock = socket(...);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&bf, (int)sizeof(bf));

위와 같은 설정으로 (기존에 bind된 소켓 자원을 프로세스가 재 사용할 수 있도록 허락하는 코드) 이러한 부분을 해결 할 수 있습니다.

SO_SNDBUF, SO_RCVBUF 사용 사례

SO_SNDBUF는 전송 소켓에서 사용할 최대 버퍼의 크기를 설정합니다.
커널은 setsockopt로 설정한 값의 2배를 버퍼 크기로 잡습니다.

전송소켓 버퍼의 기본 크기는 /proc/sys/net/core/wmem_default에서,
설정 가능한 최대 크기는 /proc/sys/net/core/wmem_max에서 확인할 수 있습니다.

SO_RCVBUF는 수신 소켓에서 사용할 최대 버퍼 크기를 설정합니다.
마찬가지로 커널은 2배 크기로 잡습니다.

전송소켓 버퍼의 기본 크기는 /proc/sys/net/core/rmem_default에서, 설정 가능한 최대 크기는 /proc/sys/net/core/rmem_max에서 확인할 수 있습니다.

사용 예시 코드
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **artv)
{
    int s;
    int bsize = 0;
    int rn;
    rn = sizeof(int);
    s = socket(AF_INET, SOCK_STREAM, 0);

    // 현재 전송 소켓 버퍼의 크기를 가져온다.
    getsockopt(s, SOL_SOCKET, SO_SNDBUF, &bsize, (socklen_t *)&rn);
    printf("Send buf size : %d\n", bsize);

    // 현재 버퍼 크기에 * 2를한다.
    bsize = bsize * 2;
    setsockopt(s, SOL_SOCKET, SO_SNDBUF, &bsize, (socklen_t)rn);

    // 현재 버퍼크기를 가져온다. 
    // 커널은 * 2만큼 버퍼크기를 설정하므로, 최초 크기의 4배만큼 설정됐을 것이다.
    getsockopt(s, SOL_SOCKET, SO_SNDBUF, &bsize, (socklen_t *)&rn);
    printf("Send buf size : %d\n", bsize);
}

SO_LINGER 사용 예제

SO_LINGER는 소켓이 close되었을 때 소켓 버퍼에 남아있는 데이터를 어떻게 할 것인지 결정하기 위해 사용합니다.

SO_LINGER를 제외한 모든 소켓 수준 옵션은 optval이 정수를 가리키고 optlen이 정수 크기로 설정될 것으로 예상합니다.

SO_LINGER 옵션 데이터 구조체
struct linger
{
	int l_onoff; // linger 옵션을 끌것인지 킬 것인지 결정
	int l_linger; // 기다리는 시간의 결정
}

두 멤버 변수로 close 방식을 정할 수 있습니다.

  • l_onoff == 0 일 때 (우아한 연결 종료 보장)
    l_linger의 영향을 받지 않으며, 소켓의 기본 설정으로 버퍼에 남아있는 모든 데이터를 보냅니다. 이때 close는 바로 리턴하며 작업은 백그라운드에서 이루어집니다.

  • l_onoff > 0 이고 l_linger > 0 일 때
    버퍼에 남아있는 데이터를 모두 보냅니다(우아한 연결 종료). 이 때 close는 l_linger에 지정된 시간만큼 블럭상태에서 대기합니다.
    만약 지정된 시간 내에 남아있는 데이터를 모두 보냈다면 리턴이 됩니다.
    지정된 시간 내에 보내지 못했다면 에러와 함께 리턴됩니다.

  • l_onoff > 0 이고 l_linger == 0 일 때 (hard, abortive 종료)
    close는 바로 리턴하며 소켓 버퍼에 남아있는 데이터는 버린다.
    TCP 연결 상태일 경우 상대편 호스트에 리셋을 위핸 RST 패킷을 보냅니다.
    hard, abortive 종료라고 부릅니다.

Reference

https://www.joinc.co.kr/w/Site/Network_Programing/AdvancedComm/SocketOption
https://www.ibm.com/docs/en/zos/2.1.0?topic=calls-setsockopt

profile
모르면 공부하고 알게되면 공유하는 개발자

0개의 댓글