소켓 옵션
고친 사람 github-actions[bot]
소켓 옵션은 소켓 하나가 어떻게 움직일지를 프로그램이 직접 정하게 해 줍니다. 운영체제는 모든 소켓에 같은 기본 동작을 줍니다. 프로그램은 이 기본 동작을 자기 소켓에 맞게 켜고 끕니다. 막 끈 서버가 같은 포트를 곧바로 다시 잡게 하는 일도 소켓 옵션 하나로 합니다.
쉽고 빠른 이해
무슨 일을 하나 — 통신 창구 하나의 동작을 프로그램이 바꿔 씁니다. 채팅 서버는 짧은 메시지를 모아 두지 말고 곧장 보내라고 운영체제에 일러 둘 수 있습니다.
왜 필요한가 — 프로그램마다 원하는 것이 다릅니다. 어떤 프로그램은 빨리 닿는 것이 중요합니다. 어떤 프로그램은 한 번에 많이 보내는 것이 중요합니다. 기본 동작 하나로는 둘을 다 맞출 수 없습니다.
어떻게 도나
- 프로그램이 통신 창구를 만듭니다. 처음에는 기본 동작으로 움직입니다
- 바꾸고 싶은 설정의 이름과 값을 운영체제에 넘깁니다
- 그 뒤로 운영체제는 그 창구만 바뀐 대로 다룹니다
대가 — 운영체제마다 같은 이름의 설정이 조금씩 다르게 돕니다. 하나를 얻으면 하나를 잃는 설정도 많습니다. 곧장 보내게 하면 보내는 묶음 수가 늘어납니다. 그만큼 네트워크가 붐빕니다.
상세
사무실 전화기는 회선이 같아도 자리마다 설정이 다릅니다. 어떤 전화기는 벨소리를 줄여 둡니다. 어떤 전화기는 부재중이면 다른 번호로 돌려 둡니다. 설정을 바꿔도 회선은 같습니다. 그 전화기만 다르게 굽니다.
소켓 옵션이 소켓에 하는 일이 이 전화기 설정입니다. 이 절은 먼저 소켓과 그 기본 동작이 무엇인지 봅니다. 이어서 옵션을 켜고 읽는 호출, 옵션이 나뉘는 계층, 자주 만나는 옵션, 옵션을 켜는 때를 봅니다. 끝으로 소켓 옵션과 헷갈리는 설정과 운영체제마다 다른 동작을 봅니다.
소켓은 프로그램이 네트워크로 데이터를 주고받으려고 운영체제에서 받아 쓰는 통신 창구입니다. 서버는 소켓으로 연결을 받습니다. 클라이언트는 소켓으로 연결을 겁니다.
운영체제는 새로 만든 소켓을 미리 정해 둔 방식으로 다룹니다. 데이터를 얼마나 담아 둘지, 조용한 연결을 언제까지 둘지가 다 정해져 있습니다. 이 방식이 소켓의 기본 동작입니다.
기본 동작은 대부분의 프로그램에 무난하도록 잡혀 있습니다. 무난하다는 것은 어느 한쪽에 딱 맞지는 않다는 뜻이기도 합니다. 짧은 요청을 주고받는 서버와 큰 파일을 보내는 서버는 바라는 것이 다릅니다.
소켓 옵션은 이 기본 동작을 소켓 하나에 한해 바꾸는 설정값입니다. 옵션을 바꾼 소켓만 달라집니다. 같은 프로그램의 다른 소켓은 기본 동작 그대로 남습니다. 옵션마다 이름이 있습니다. 값으로 옵션을 켜고 끄거나 크기를 정합니다.
옵션을 켜고 읽는 호출
옵션은 시스템 콜로 다룹니다. 시스템 콜은 프로그램이 운영체제에 직접 부탁하는 호출입니다. 옵션을 바꾸는 호출이 setsockopt 입니다. 지금 값을 읽는 호출은 getsockopt 입니다.
아래는 파이썬으로 옵션 하나를 읽고, 켜고, 다시 읽는 코드입니다. 고른 옵션은 SO_KEEPALIVE 입니다. 오래 조용한 연결이 아직 살아 있는지 운영체제가 가끔 확인하게 하는 옵션입니다.
import socket
from socket import SOL_SOCKET, SO_KEEPALIVE
s = socket.socket()
k = SO_KEEPALIVE
s.getsockopt(SOL_SOCKET, k) # 0
s.setsockopt(SOL_SOCKET, k, 1)
s.getsockopt(SOL_SOCKET, k) # 1
처음 읽은 값이 0 입니다. 이 옵션은 기본으로 꺼져 있다는 뜻입니다. setsockopt 로 1 을 넘겨 켜고 나면 다시 읽은 값이 1 로 바뀝니다. 0 을 넘기면 다시 꺼집니다.
setsockopt 에 넘긴 값은 셋입니다. 첫째 SOL_SOCKET(socket level, 소켓 계층)은 옵션이 어느 계층의 것인지 가리킵니다. 둘째는 옵션 이름입니다. 셋째는 넣을 값입니다. 계층은 바로 아래 소절에서 봅니다.
다른 언어도 같은 호출을 감쌉니다. 자바에서는 Socket 의 setKeepAlive(true) 가 이 옵션을 켭니다. 데이터베이스 드라이버나 HTTP(HyperText Transfer Protocol, 웹 요청을 주고받는 규칙) 클라이언트의 설정 목록에 보이는 keepAlive · tcpNoDelay 같은 항목도 결국 소켓 옵션으로 내려갑니다.
옵션이 나뉘는 계층
네트워크 통신은 여러 계층이 나눠 맡습니다. 계층은 통신의 일을 위아래로 갈라 맡긴 단위입니다. 소켓 옵션도 어느 계층의 동작을 바꾸느냐로 나뉩니다.
맨 위는 소켓 계층입니다. 어떤 프로토콜(통신 규칙)을 쓰든 모든 소켓에 공통인 동작이 여기 속합니다. 앞의 코드에서 넘긴 SOL_SOCKET 이 이 계층을 가리킵니다.
그 아래가 TCP(Transmission Control Protocol, 전송 제어 프로토콜) 계층입니다. TCP 는 데이터가 빠짐없이 순서대로 닿게 챙기는 통신 규칙입니다. TCP 로 맺은 연결에만 뜻이 있는 옵션이 여기 속합니다. 이 계층의 옵션은 계층 값으로 IPPROTO_TCP 를 넘깁니다.
맨 아래는 IP(Internet Protocol, 인터넷 프로토콜) 계층입니다. IP 는 데이터 묶음을 목적지 컴퓨터까지 실어 나르는 통신 규칙입니다. 이 계층의 옵션은 보내는 데이터 묶음 하나하나의 성질을 바꿉니다.
옵션 이름에 붙은 머리말이 계층을 알려 줍니다. SO_ 로 시작하면 소켓 계층입니다. TCP_ 는 TCP 계층, IP_ 는 IP 계층입니다.
옵션 이름은 실제로는 번호입니다. 그 번호는 계층마다 따로 매깁니다. 그래서 계층 값을 틀리게 주면 그 계층에서 같은 번호를 가진 다른 옵션이 잡히거나 호출이 실패합니다. 계층 값은 머리말에 맞춰 넘깁니다.
자주 만나는 옵션
서버를 다루다 보면 같은 옵션 몇 개를 거듭 만납니다. 아래 표는 그 옵션들을 계층과 함께 모은 것입니다. 표의 버퍼는 데이터를 프로그램과 네트워크 사이에서 잠시 담아 두는 메모리입니다.
| 옵션 | 계층 | 켜거나 바꾸면 달라지는 것 |
|---|---|---|
| SO_REUSEADDR | 소켓 | 막 끈 서버가 같은 포트를 곧바로 다시 잡는다 |
| SO_KEEPALIVE | 소켓 | 조용한 연결이 살아 있는지 운영체제가 가끔 확인한다 |
| SO_RCVBUF · SO_SNDBUF | 소켓 | 받는 버퍼와 보내는 버퍼의 크기가 바뀐다 |
| SO_LINGER | 소켓 | 소켓을 닫을 때 못 보낸 데이터를 얼마나 기다릴지 정한다 |
| SO_RCVTIMEO | 소켓 | 데이터를 기다리는 시간에 상한이 생긴다 |
| TCP_NODELAY | TCP | 작은 데이터를 모아 두지 않고 곧장 보낸다 |
| IP_TTL | IP | 데이터 묶음이 거쳐 갈 수 있는 라우터 수의 상한이 바뀐다 |
표에서 TCP_NODELAY 는 옵션이 왜 소켓마다 따로 있어야 하는지를 잘 보여 줍니다. 이 옵션이 끄는 동작부터 봅니다.
TCP 는 기본으로 작은 데이터를 곧장 보내지 않습니다. 앞서 보낸 데이터에 대해 상대가 받았다는 확인을 돌려주기 전까지 작은 데이터를 모아 둡니다. 확인이 오면 모은 것을 한 번에 보냅니다. 이 규칙을 네이글 알고리즘이라고 부릅니다.
모아 보내면 네트워크로 나가는 패킷 수가 줄어듭니다. 패킷은 네트워크로 한 번에 보내는 데이터 묶음입니다. 한 글자씩 치는 원격 터미널이 글자마다 패킷을 하나씩 보내면 네트워크가 쓸데없이 붐빕니다.
대신 짧은 요청 하나가 상대에게 늦게 닿을 수 있습니다. 요청과 응답을 빠르게 주고받는 서버에는 이 기다림이 곧 지연이 됩니다. 그런 프로그램은 TCP_NODELAY 를 켜서 모아 두기를 끕니다.
한 컴퓨터 안에서도 두 프로그램은 바라는 것이 반대입니다. 그래서 이 선택은 운영체제 전체가 아니라 소켓마다 합니다. 소켓 옵션이 따로 있는 까닭이 이것입니다.
옵션을 켜는 때
옵션은 대개 소켓을 만든 직후에 켭니다. 운영체제가 어떤 판정을 내릴 때 옵션을 보기 때문입니다. 포트를 붙일 때 그 포트를 내줄지 정하는 일이 그런 판정입니다. 판정이 끝난 뒤에 켜면 이미 내린 판정은 바뀌지 않습니다.
SO_REUSEADDR 가 그런 옵션입니다. 서버는 연결을 받기 전에 자기 소켓에 포트를 붙입니다. 이 일이 바인드입니다. 운영체제는 바인드를 받을 때 이 옵션을 보고 포트를 내줄지 정합니다.
이 옵션이 왜 필요한지는 서버를 다시 띄울 때 드러납니다. 서버를 꺼도 그 포트로 맺었던 연결은 잠시 정리 단계에 남습니다. 늦게 도착하는 데이터가 새 연결에 섞이지 않게 하려는 것입니다. 그동안 운영체제는 그 포트를 내주지 않아서, 옵션이 없으면 같은 포트로 하는 바인드가 거절됩니다.
아래 그림은 소켓을 만들 때부터 리슨까지의 순서입니다. 리슨은 포트를 붙인 소켓이 연결을 받기 시작하는 일입니다.
sequenceDiagram
participant 프로그램
participant 운영체제
프로그램->>운영체제: 소켓을 만든다
운영체제-->>프로그램: 기본 동작으로 만든 소켓
프로그램->>운영체제: setsockopt · SO_REUSEADDR 를 켠다
프로그램->>운영체제: 바인드 · 포트를 붙인다
Note over 운영체제: 켜 둔 옵션을 보고 포트를 내줄지 정한다
프로그램->>운영체제: 리슨 · 연결을 받기 시작한다
그림에서 옵션은 바인드보다 앞에 켭니다. 순서를 바꾸면 바인드가 기본 동작으로 판정됩니다. 그러면 옵션은 켜져 있어도 효과가 없습니다.
모든 옵션이 이렇지는 않습니다. TCP_NODELAY 처럼 데이터를 보낼 때마다 보는 옵션은 연결 중간에 켜도 그 뒤로 보내는 데이터부터 바로 효과가 납니다.
소켓 하나와 운영체제 전체
소켓 옵션은 소켓 하나만 바꿉니다. 이와 달리 그 컴퓨터의 모든 소켓에 걸리는 운영체제 전체 설정도 있습니다. 둘은 바꾸는 사람과 범위가 다릅니다.
운영체제 전체 설정은 관리자가 바꿉니다. 리눅스에서는 sysctl 이라는 명령으로 바꿉니다. 이 설정은 새로 만드는 모든 소켓의 기본 동작을 정합니다. 소켓 옵션은 프로그램이 자기 소켓에서 그 기본 동작을 다시 덮어씁니다.
앞의 코드에서 켠 SO_KEEPALIVE, 곧 킵얼라이브가 두 설정이 겹치는 예입니다. 킵얼라이브를 켜는 것은 소켓 옵션이 합니다. 조용한 연결에 첫 확인을 보내기 전까지 기다리는 시간은 대개 운영체제 전체 설정이 정합니다. 운영체제에 따라 이 시간도 소켓마다 바꾸는 옵션을 두기도 합니다.
TCP 옵션과 헷갈리지 않기
소켓 옵션과 이름이 비슷한 TCP 옵션이 따로 있습니다. 둘은 값이 가는 곳이 다릅니다. 소켓 옵션은 상대에게 전해지는 값이 아닙니다. 내 컴퓨터의 운영체제가 내 소켓을 어떻게 다룰지 정하는 설정일 뿐입니다.
TCP 는 데이터 앞에 헤더라는 안내 정보를 붙여 보냅니다. 받는 쪽 포트 같은 정보가 여기 담깁니다. TCP 옵션은 이 헤더에 실려 상대에게 가는 값입니다.
운영체제마다 다른 동작
소켓 옵션의 이름은 운영체제끼리 대부분 같습니다. 소켓을 다루는 호출의 모양이 한 뿌리에서 나왔기 때문입니다. 그 뿌리가 버클리 소켓입니다.
이름이 같다고 동작까지 같지는 않습니다. SO_REUSEADDR 는 리눅스에서 재시작한 서버가 포트를 다시 잡게 해 줍니다. 윈도우에서는 같은 이름의 옵션이 남이 쓰고 있는 포트까지 잡게 해 줍니다. 그래서 여러 운영체제에서 도는 프로그램은 옵션마다 그 운영체제에서의 동작을 따로 확인합니다.
값의 단위나 범위가 다른 옵션도 있습니다. 버퍼 크기를 정하는 옵션은 넘긴 값을 운영체제가 그대로 쓰지 않고 고쳐서 쓰기도 합니다. 넘긴 값과 다시 읽은 값이 다르면 운영체제가 손댄 것입니다.
관련 항목
소켓 옵션을 켜고 읽는 호출
setsockopt · getsockopt · 시스템 콜 · 버클리 소켓 · fcntl · ioctl
소켓 옵션이 걸리는 계층 값
SOL_SOCKET · IPPROTO_TCP · IPPROTO_IP · IPPROTO_IPV6 · 전송 계층 · 네트워크 계층
소켓 계층의 대표 옵션
SO_REUSEADDR · SO_REUSEPORT · SO_KEEPALIVE · SO_RCVBUF · SO_SNDBUF · SO_LINGER · SO_RCVTIMEO · SO_SNDTIMEO · SO_BROADCAST
TCP 계층과 IP 계층의 대표 옵션
TCP_NODELAY · TCP_KEEPIDLE · TCP_CORK · TCP_QUICKACK · IP_TTL · IP_MULTICAST_TTL · IPV6_V6ONLY
소켓 옵션이 바꾸는 운영체제 동작
네이글 알고리즘 · 킵얼라이브 · TIME_WAIT · 수신 버퍼 · 송신 버퍼 · 타임아웃 · 지연된 ACK
소켓 옵션이 붙는 대상과 호출
소켓 · 파일 디스크립터 · TCP · UDP · 포트 · 바인드 · 리슨
소켓 옵션과 헷갈리는 설정
TCP 옵션 · sysctl · 커널 파라미터 · 명령줄 옵션 · OPTIONS
소켓 옵션을 감싸 노출하는 언어와 프레임워크
다른 이름: socket option · socket options · 소켓 설정