네트워크 용어 사전
소켓bind · listen · accept · C10K

소켓

네트워크 통신의 종단점. 서버는 bind·listen·accept로 연결을 받고, 블로킹 모델의 한계가 C10K다.

네트워크 통신의 종단점(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 까지 반복해서 읽어야 한다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 소켓 — bind/listen/accept·blocking·epoll