네트워크 통신의 종단점(endpoint) 이자, 운영체제가 제공하는 통신 API.
서버가 연결을 받는 절차
fd = socket(AF_INET, SOCK_STREAM, 0); // 소켓을 만든다
bind(fd, addr); // 주소·포트를 붙인다
listen(fd, backlog); // 연결 대기 큐를 만든다
while (1) {
conn = accept(fd, ...); // 큐에서 하나를 꺼낸다 (없으면 블로킹)
read(conn, buf, size);
write(conn, resp, len);
close(conn);
}
-
연결 하나는 네 값으로 식별된다
- (출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트)
-
서버 포트가 하나여도 클라이언트가 다르면 다른 연결이다
-
"포트 하나로 어떻게 수만 명을 받나" 의 답
backlog가 만드는 문제
listen(fd, backlog) 의 backlog 는 '완료된 연결이 accept 를 기다리는 큐' 크기다
애플리케이션이 accept 를 늦게 하면 큐가 찬다
- 새 연결이 거부되거나 무시된다
- 클라이언트는 타임아웃으로만 알게 된다
ss -ltn # Recv-Q 가 현재 대기 수, Send-Q 가 backlog 상한
sysctl net.core.somaxconn # 시스템 상한 (앱이 더 크게 요청해도 여기서 잘린다)
C10K — 블로킹 모델의 한계
연결마다 스레드/프로세스를 하나씩 붙이면
- 스레드 1개 = 스택 수 MB + 컨텍스트 스위치 비용
- 동시 접속 1만이면 메모리도 스케줄링도 감당이 안 된다
대부분의 스레드가 '기다리기만' 하는데 자원을 다 쓴다는 게 문제였다
I/O 멀티플렉싱 — 한 스레드가 여럿을
- select — fd 집합을 통째로 넘기고 커널이 훑는다 — O(n) · fd 1024 제한
- poll — 제한은 풀렸지만 여전히 O(n)
- epoll — 관심 fd 를 커널에 '등록' 해 두고, 준비된 것만 돌려받는다 — O(1)
- (리눅스. BSD 는 kqueue, 윈도우는 IOCP)
int ep = epoll_create1(0);
epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev); // 한 번만 등록한다
int n = epoll_wait(ep, events, MAX, -1); // 준비된 것만 받는다
"매번 전부 확인"에서 "준비된 것만 통지"로 바뀐 것이 핵심이고, 이 위에 이벤트 루프 기반 서버들이 섰다.
또 다른 길 — 경량 스레드
I/O 멀티플렉싱은 빠르지만 코드가 콜백·이벤트 중심이 되어 읽기 어렵다
대안: 런타임이 관리하는 경량 스레드를 두고,
- 블로킹처럼 보이는 코드를 쓰되 실제로는 내부에서 멀티플렉싱한다
- 코드는 순차적으로 읽히고 성능은 이벤트 루프에 가깝다
면접 함정
- ❌ "포트 하나에 연결 하나" → 4-튜플로 구분하므로 포트 하나로 수만 연결을 받는다.
- ❌ "epoll이 select보다 항상 빠르다" → fd가 적으면 차이가 없거나 select가 낫다. 많을 때 격차가 벌어진다.
파일 디스크립터 한계
ulimit -n # 프로세스당 열 수 있는 fd 수 (기본 1024가 흔하다)
sysctl fs.file-max # 시스템 전체
cat /proc/<pid>/limits | grep files
ls /proc/<pid>/fd | wc -l # 현재 사용 중
"Too many open files" 는 소켓 서버의 대표 장애다
- 동시 접속이 늘면 fd 가 그만큼 필요하다
- 소켓을 안 닫는 누수여도 같은 증상이 난다 (CLOSE_WAIT 이 쌓인다)
한계를 올리기 전에 '누수인지 부족인지' 부터 가른다
커널 파라미터
sysctl net.core.somaxconn # accept 대기 큐 상한 (기본이 작다)
sysctl net.ipv4.tcp_max_syn_backlog # SYN 큐
sysctl net.ipv4.ip_local_port_range # 클라이언트가 쓸 임시 포트 범위
클라이언트 역할을 하는 서버(프록시·API 게이트웨이)는 임시 포트 고갈에 걸린다 — 기본 범위가 약 28,000개뿐이라 연결을 재사용하지 않으면 금방 소진된다.
레벨 트리거와 엣지 트리거
-
epoll 은 두 모드가 있다
-
Level-triggered — (기본) 읽을 게 남아 있으면 계속 알려 준다 — 안전하다
-
Edge-triggered — 상태가 '바뀔 때만' 알려 준다 — 빠르지만
- 한 번에 다 읽지 않으면 나머지를 영영 못 받는다
- 논블로킹 + EAGAIN 까지 반복해서 읽어야 한다