setsockopt / getsockopt 기본 규칙
기본 형태
int value = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&value),
sizeof(value)) == SOCKET_ERROR) {
int err = WSAGetLastError();
}
int out = 0;
int len = sizeof(out);
if (getsockopt(sock, SOL_SOCKET, SO_TYPE,
reinterpret_cast<char*>(&out), &len) == SOCKET_ERROR) {
int err = WSAGetLastError();
}
level 의미
| Level | 용도 |
|---|
SOL_SOCKET | 소켓 공통 옵션 |
IPPROTO_IP | IPv4 레벨 옵션 |
IPPROTO_TCP | TCP 전용 옵션 |
적용 타이밍
- 옵션마다 "bind 전에 설정해야 의미 있는지"가 다릅니다.
- 서버 코드에서는 보통
socket() 직후, bind() 전에 필요한 옵션을 먼저 설정합니다.
주요 옵션 한눈에 보기
| 옵션 | 목적 | 기본 권장 |
|---|
SO_REUSEADDR | 포트 재바인딩 유연화 | 개발/테스트 환경에서 자주 사용 |
SO_EXCLUSIVEADDRUSE (Windows) | 포트 독점 바인딩 | 운영 서버에서 고려 |
TCP_NODELAY | Nagle 비활성화(지연 감소) | 실시간 게임 트래픽에 대체로 ON |
SO_KEEPALIVE | 장기 유휴 연결 생존 확인 | 보조 수단(앱 heartbeat 우선) |
SO_SNDBUF/SO_RCVBUF | 송수신 버퍼 크기 조정 | 지표 기반 튜닝 |
SO_LINGER | close 동작 제어 | 명확한 의도 없으면 기본값 유지 |
SO_REUSEADDR와 Windows 주의점
문제 상황
- 서버 재시작 직후
bind에서 WSAEADDRINUSE가 발생할 수 있습니다.
- 원인 중 하나는 이전 연결의
TIME_WAIT 상태입니다.
기본 설정 예시
int opt = 1;
setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR,
reinterpret_cast<const char*>(&opt), sizeof(opt));
Windows 실무 주의
- Windows에서
SO_REUSEADDR 의미는 Unix 계열과 완전히 같지 않습니다.
- 운영 서버에서는 의도치 않은 중복 바인딩/포트 하이재킹 위험을 줄이기 위해
SO_EXCLUSIVEADDRUSE를 고려하는 경우가 많습니다.
int opt = 1;
setsockopt(listenSock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
reinterpret_cast<const char*>(&opt), sizeof(opt));
- 팀 규약으로 "개발 환경은 REUSEADDR, 운영은 EXCLUSIVEADDRUSE 우선"처럼 명문화하면 혼선을 줄일 수 있습니다.
TCP_NODELAY와 Nagle 트레이드오프
Nagle 알고리즘 요약
- 작은 패킷을 모아 전송해 패킷 오버헤드를 줄입니다.
- 대신 즉시 보내지 않아 지연이 증가할 수 있습니다.
게임에서 왜 자주 끄나
- 실시간 입력/위치 업데이트는 "작고 자주" 발생합니다.
- Nagle + Delayed ACK 조합에서 체감 지연이 튈 수 있습니다.
- 그래서 실시간 게임 채널은
TCP_NODELAY ON이 흔합니다.
설정 예시
int opt = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<const char*>(&opt), sizeof(opt));
오해 금지
TCP_NODELAY를 켠다고 무조건 성능이 좋아지진 않습니다.
- 대량 전송/배치 전송 중심이라면 패킷 수 증가로 오히려 불리할 수 있습니다.
SO_KEEPALIVE와 앱 레벨 heartbeat
역할
- 장시간 유휴 연결이 끊겼는지 OS 레벨에서 감지하는 보조 기능입니다.
한계
- 기본 keepalive 간격은 게임 실시간 요구에 비해 매우 길 수 있습니다.
- 따라서 게임 서버는 보통 앱 레벨 핑/퐁(heartbeat)을 별도로 둡니다.
권장 사용
SO_KEEPALIVE는 안전망으로 사용
- 실제 세션 타임아웃 판정은 앱 heartbeat 기준으로 운영
송수신 버퍼 크기 튜닝
조정 예시
int snd = 256 * 1024;
int rcv = 256 * 1024;
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<const char*>(&snd), sizeof(snd));
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&rcv), sizeof(rcv));
트레이드오프
| 크게 설정 | 작게 설정 |
|---|
| 버스트 흡수 유리, drop 감소 가능 | 메모리 절약, 지연 관측이 더 민감 |
| 세션 수가 많으면 메모리 급증 가능 | 피크 트래픽에서 막힘 증가 가능 |
튜닝 원칙
- 감으로 바꾸지 말고 지표(지연,
WOULDBLOCK, 큐 길이)로 판단
- 부하 테스트 전/후 비교로 회귀 검증
SO_LINGER는 신중하게
의미
close/closesocket 시 남은 데이터 처리와 종료 방식을 제어합니다.
주의
- 잘못 설정하면 종료 시 블로킹, 예기치 않은 RST 전송 등 부작용이 생길 수 있습니다.
- 명확한 요구가 없다면 기본 동작을 유지하는 편이 안전합니다.
강의 시 유의사항
강조 포인트
- 소켓 옵션은 "좋은 옵션 모음"이 아니라 트레이드오프 선택 도구입니다.
SO_REUSEADDR는 편하지만 Windows 특성을 모르고 쓰면 운영 리스크가 생길 수 있습니다.
TCP_NODELAY는 지연 개선 수단이지만 패킷 수 증가와 함께 봐야 합니다.
자주 하는 오해
| 오해 | 바로잡기 |
|---|
SO_REUSEADDR은 무조건 필수 | 환경/OS 정책에 따라 SO_EXCLUSIVEADDRUSE가 더 적합할 수 있음 |
TCP_NODELAY는 항상 켜야 정답 | 트래픽 패턴에 따라 비용/효과가 달라짐 |
| keepalive만 켜면 연결 관리 끝 | 게임은 앱 heartbeat가 핵심 |
체크 질문 (스스로 답해보기)
- 개발/운영 환경에서 포트 재사용 정책을 어떻게 다르게 가져갈 것인가?
TCP_NODELAY를 켰을 때 얻는 이점과 잃는 것은 무엇인가?
- 버퍼 크기 튜닝 결과를 어떤 지표로 검증할 것인가?