사전 리슨
인터페이스

리슨

gabury1고친 사람 github-actions[bot]

리슨은 서버 프로그램이 이제부터 연결을 받겠다고 운영체제에 알리는 일입니다. 리슨한 포트로 연결 요청이 오면 운영체제가 대신 연결을 맺어 두고 프로그램이 가져가기를 기다립니다. 포트가 열려 있다는 말은 그 포트에서 누군가 리슨하고 있다는 뜻입니다.

쉽고 빠른 이해

리슨은 서버 프로그램이 「이 포트로 오는 연결은 받겠다」고 운영체제에 알리는 일입니다. 웹 서버가 8080번 포트에서 손님을 기다리기 시작하는 순간이 리슨입니다.

연결을 받는 서버 쪽만 리슨합니다. 연결을 거는 클라이언트나, 연결 없이 주고받는 UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)는 리슨하지 않습니다.

리슨하지 않은 포트로 연결 요청이 오면 운영체제는 바로 거절합니다. 클라이언트는 연결 거부 오류를 받습니다. 받을 준비가 된 포트와 아닌 포트를 가르는 것이 리슨입니다.

어떻게 도나:

  1. 서버가 통신 창구를 만들고 포트 번호를 붙입니다
  2. 리슨을 부릅니다. 이때부터 운영체제가 연결 요청을 받습니다
  3. 운영체제가 연결을 맺어 줄에 세우면 서버가 하나씩 꺼내 씁니다

대가는 줄의 길이에 상한이 있다는 것입니다. 서버가 꺼내는 속도보다 요청이 빨리 쌓이면 줄이 찹니다. 그 뒤로 온 요청은 무시되거나 거절됩니다.

상세

이 절은 리슨이 소켓을 어떻게 바꾸는지에서 시작합니다. 파이썬 코드 두 벌로 리슨한 포트와 안 한 포트가 어떻게 다르게 답하는지 봅니다. 이어서 리슨이 받는 인자인 백로그와 리슨이 실패하는 경우를 봅니다.

소켓은 프로그램이 네트워크로 데이터를 주고받으려고 운영체제에서 받아 쓰는 통신 창구입니다. 프로그램은 소켓에 쓰고 소켓에서 읽습니다.

TCP(Transmission Control Protocol, 전송 제어 프로토콜)는 데이터가 빠짐없이 순서대로 닿게 챙기는 통신 방식입니다. 데이터를 보내기 전에 양쪽이 연결부터 맺습니다. 그래서 TCP 에는 연결을 거는 쪽과 받는 쪽이 따로 있습니다.

포트는 한 컴퓨터 안에서 데이터를 받을 프로그램을 가리키는 번호입니다. 한 컴퓨터에 서버 프로그램이 여럿 떠 있어도 포트 번호로 어느 프로그램 몫인지 가립니다.

리슨은 소켓을 연결을 받는 쪽으로 바꾸는 호출입니다. 리슨한 소켓은 데이터를 주고받지 않습니다. 들어오는 연결 요청만 받습니다. 요청이 연결로 맺어지면 그 연결 몫의 소켓이 새로 생깁니다.

리슨은 프로그램이 운영체제에 직접 부탁하는 호출입니다. 이런 호출을 시스템 콜이라고 부릅니다. 언어가 달라도 이름은 대개 listen 입니다. 언어마다 있는 소켓 함수가 운영체제의 이 호출을 감싸기 때문입니다.

서버가 연결을 받기까지

이 소절은 서버 프로그램이 연결 하나를 받기까지 부르는 호출 넷을 봅니다. 차례로 socket · bind · listen · accept 입니다. 리슨은 그중 셋째입니다.

첫째 호출 socket 은 소켓을 하나 만듭니다. 막 만든 소켓에는 아직 주소도 포트 번호도 없습니다.

둘째 호출은 바인드입니다. 소켓에 주소와 포트 번호를 붙이는 일입니다. 바인드로 포트 번호를 정해 둬야 클라이언트가 어디로 연결을 걸지 압니다.

셋째 호출 listen 부터 운영체제가 이 포트로 온 연결 요청을 받습니다. 연결을 맺는 일은 운영체제가 서버 프로그램 없이 끝냅니다. 맺은 연결은 줄에 세워 둡니다.

넷째 호출 accept 는 줄 선 연결 하나를 꺼내 옵니다. 줄이 비어 있으면 연결이 올 때까지 기다립니다. 꺼낸 연결마다 새 소켓이 하나씩 생깁니다. 리슨한 소켓은 남아서 다음 요청을 받습니다.

sequenceDiagram
    participant 클라이언트
    participant 운영체제 as 서버의 운영체제
    participant 서버 as 서버 프로그램
    서버->>운영체제: listen · 연결을 받겠다
    클라이언트->>운영체제: 연결 요청
    운영체제-->>클라이언트: 요청을 받았다는 답
    클라이언트->>운영체제: 답을 받았다는 확인
    Note over 운영체제: 맺은 연결을 줄에 세운다
    서버->>운영체제: accept · 하나 꺼내 달라
    운영체제-->>서버: 연결 하나와 새 소켓

그림 가운데의 세 메시지가 연결을 맺는 과정입니다. 클라이언트가 요청합니다. 운영체제가 답합니다. 클라이언트가 그 답을 받았다고 확인합니다. 이 세 번의 주고받음을 3방향 핸드셰이크라고 부릅니다.

서버 프로그램은 이 세 메시지에 끼지 않습니다. 서버가 accept 를 부르는 것은 연결이 다 맺어진 뒤입니다. accept 는 연결을 맺는 호출이 아니라 맺어 둔 연결을 꺼내는 호출입니다.

리슨한 포트와 안 한 포트

이 소절은 파이썬 코드 두 벌을 견줍니다. 한 벌은 리슨한 포트에 연결을 겁니다. 다른 한 벌은 바인드만 하고 리슨은 안 한 포트에 겁니다.

첫째 코드에서 서버는 리슨까지만 합니다. accept 는 부르지 않습니다. 클라이언트는 connect 로 그 포트에 연결을 겁니다. connect 는 연결을 거는 쪽이 부르는 호출입니다.

Python
import socket

addr = ("127.0.0.1", 8080)
srv = socket.socket()
srv.bind(addr)
srv.listen(5)

cli = socket.socket()
cli.connect(addr)
print("연결됨")  # 연결됨

connect 가 성공합니다. 마지막 줄이 찍힙니다. 서버가 accept 를 한 번도 부르지 않았는데도 그렇습니다.

연결은 운영체제가 맺었습니다. 그 연결이 지금 줄에서 기다립니다. listen 에 넘긴 5 는 아래 「줄의 길이를 정하는 백로그」에서 봅니다.

둘째 코드는 리슨하는 줄 하나만 뺐습니다. 포트만 8081 로 바꿨습니다. 첫째 코드에 이어 돌리면 8080 은 첫째 서버가 아직 잡고 있기 때문입니다.

Python
import socket

addr = ("127.0.0.1", 8081)
srv = socket.socket()
srv.bind(addr)

cli = socket.socket()
cli.connect(addr)  # ConnectionRefusedError

이번에는 connect 가 예외를 던집니다. 포트에 소켓이 바인드되어 있어도 리슨하지 않았으면 운영체제는 요청을 거절합니다. 클라이언트가 받는 이 오류가 흔히 보는 Connection refused 입니다.

그래서 연결 거부는 대개 그 주소와 포트에서 아무도 리슨하지 않는다는 신호입니다. 서버가 안 떴거나 다른 포트에서 리슨하고 있을 때 이 오류를 봅니다.

줄의 길이를 정하는 백로그

이 소절은 listen 에 넘기는 인자 하나를 봅니다. 운영체제가 맺어 둔 연결을 몇 개까지 줄에 세울지 정하는 값입니다. 이 값을 백로그라고 부릅니다.

줄이 필요한 까닭은 서버가 연결을 꺼내는 속도에 한계가 있기 때문입니다. 서버가 다른 일을 하는 동안에도 요청은 들어옵니다. 그 사이 맺은 연결은 어딘가에서 기다려야 합니다. 줄이 그 대기 공간입니다.

상한을 두는 까닭은 메모리입니다. 맺어 둔 연결 하나하나가 운영체제의 메모리를 씁니다. 서버가 꺼내지 않는 연결을 한없이 받아 두면 메모리도 그만큼 늘어납니다.

줄이 찬 뒤에 온 요청은 무시되거나 거절됩니다. 어느 쪽인지는 운영체제가 정합니다. 무시되면 클라이언트는 조금 뒤 요청을 다시 보냅니다. 그래서 연결이 늦게 맺어집니다.

flowchart TD
    A["연결 요청이 도착한다"] --> B{"이 포트에서 리슨 중인가"}
    B -->|아니다| C["거절한다"]
    B -->|그렇다| D{"줄에 빈 칸이 있나"}
    D -->|없다| E["무시하거나 거절한다"]
    D -->|있다| F["연결을 맺고 줄에 세운다"]

그림은 요청 하나가 도착했을 때 운영체제가 묻는 두 가지를 보입니다. 먼저 리슨 중인지 묻습니다. 그다음 줄에 빈 칸이 있는지 묻습니다. 둘 다 통과한 요청만 연결이 됩니다.

백로그는 서버가 동시에 들고 있는 연결 수가 아닙니다. accept 로 꺼낸 연결은 줄을 떠납니다. 그래서 백로그가 작아도 서버는 꺼낸 연결을 그보다 훨씬 많이 들고 있을 수 있습니다. 백로그가 세는 것은 아직 안 꺼낸 연결뿐입니다.

운영체제는 넘긴 백로그를 줄여서 쓰기도 합니다. 운영체제마다 백로그의 상한이 따로 있습니다. 넘긴 값이 그 상한보다 크면 상한으로 깎습니다. 백로그를 키웠는데 줄이 안 길어지면 운영체제 쪽 상한을 봐야 합니다.

리슨이 실패할 때

이 소절은 리슨이 실패를 알리는 방법과 흔한 원인 둘을 봅니다. 포트가 겹치는 문제는 대개 리슨보다 앞선 바인드에서 걸립니다. 그래서 리슨 자체가 실패하는 경우는 많지 않습니다.

파이썬에서는 리슨이 실패하면 listen 을 부른 줄에서 OSError 예외가 납니다.

그 예외 안에는 운영체제가 알려 준 오류 번호가 들어 있습니다. 번호마다 이름이 붙어 있어서 이름을 보면 원인을 가릴 수 있습니다. 리슨에서 흔히 보는 이름은 둘입니다.

이름 흔한 원인
EOPNOTSUPP 연결을 맺지 않는 방식의 소켓에 리슨을 불렀습니다
EADDRINUSE 같은 주소와 포트에서 다른 소켓이 이미 리슨하고 있습니다

EOPNOTSUPP 를 내는 대표가 UDP 소켓입니다. UDP 는 연결을 맺지 않고 데이터 묶음을 하나씩 보내는 방식입니다. 맺을 연결이 없으니 줄 세울 것도 없습니다. UDP 소켓은 리슨 없이 바인드만 하면 바로 데이터를 받습니다.

EADDRINUSE 는 원래 바인드에서 걸리는 오류입니다. 바인드는 보통 다른 소켓이 이미 쓰는 포트 번호를 받아 주지 않습니다.

소켓 옵션은 소켓의 동작을 바꾸는 설정값입니다. 그중 SO_REUSEADDR 는 바인드의 이 겹침 검사를 느슨하게 풉니다. 이 옵션을 켜 두면 운영체제에 따라 바인드가 겹침을 다 못 막습니다. 그러면 겹침은 리슨하는 순간에 드러납니다.

C 언어에는 예외가 없어서 리슨이 실패하면 -1 을 돌려줍니다. 오류 번호는 errno 라는 변수에 남습니다.

리슨은 됐는데 응답이 없을 때

이 소절은 리슨이 성공했는데도 서버가 제 일을 못 하는 경우를 봅니다. 앞의 첫째 코드가 그 원인을 이미 보였습니다. 연결을 맺는 것은 서버 프로그램이 아니라 운영체제입니다.

서버 프로그램이 멈춰 accept 를 못 불러도 운영체제는 연결을 계속 맺습니다. 줄이 찰 때까지 그렇습니다.

클라이언트는 연결이 됐다고 보고 요청을 보냅니다. 요청은 운영체제에 쌓이기만 합니다. 답은 오지 않습니다. 클라이언트는 시간 초과로 끝납니다.

헬스 체크는 서버가 살아 있는지 주기적으로 확인하는 일입니다. 연결이 맺어지는지만 보는 헬스 체크는 이 상태를 못 알아챕니다. 연결은 운영체제가 맺어 주기 때문입니다. 서버 프로그램이 답하는지 보려면 요청을 보내 응답까지 받아 봐야 합니다.

리슨 중인 포트를 확인하는 명령

이 소절은 지금 어느 포트에서 누가 리슨하는지 보는 명령 둘을 봅니다. 서버가 떴는데 연결이 안 될 때 먼저 보는 것이 이 목록입니다.

터미널
ss -ltn        # 리슨 중인 TCP 소켓
netstat -an    # 모든 소켓과 그 상태

ss는 리눅스에서 소켓 목록을 보여 주는 명령입니다. -l 은 리슨 중인 소켓만, -t 는 TCP 만 고릅니다. -n 은 포트를 번호 그대로 찍게 합니다. 이 옵션이 없으면 80 대신 http 처럼 잘 알려진 포트에 붙은 이름으로 찍습니다.

netstat는 여러 운영체제에 있는 같은 목적의 명령입니다. 소켓마다 상태를 적는 칸이 있습니다. 리슨 중인 소켓은 그 칸에 LISTEN 이 찍힙니다.

목록에서 주소 칸도 함께 봅니다. 127.0.0.1 에서 리슨하는 서버는 같은 컴퓨터 안에서 온 연결만 받습니다. 이 주소를 루프백이라고 부릅니다. 목록에 포트가 보이는데 밖에서 안 붙으면 이 주소부터 확인합니다.

이름이 겹치는 다른 리슨

영어 listen 은 「귀를 기울인다」는 뜻이라 네트워크 밖에서도 씁니다. 백엔드 개발자가 자주 만나는 셋을 표로 모았습니다.

부르는 말 분야 뜻
서버 설정의 listen 항목 웹 서버 설정 파일 이 문서의 리슨과 같습니다. 어느 주소와 포트에서 리슨할지 적습니다
LISTEN 명령 PostgreSQL 데이터베이스가 보내는 알림을 받겠다고 등록합니다
이벤트 리스너 프로그래밍 전반 어떤 일이 일어나면 불릴 함수를 등록합니다

PostgreSQL 의 LISTEN 명령과 이벤트 리스너는 소켓이나 포트와 관계없는 기술입니다.

관련 항목

리슨과 함께 서버 소켓을 여는 호출

소켓 · 바인드 · accept · connect · close · 시스템 콜 · 버클리 소켓

리슨한 소켓 앞에 쌓이는 연결 대기열

리슨 소켓 · 백로그 · SYN 큐 · accept 큐 · 3방향 핸드셰이크 · SYN 플러드

리슨이 연결을 받는 프로토콜

TCP · UDP · 전송 계층 · TCP 상태 전이 · TIME_WAIT

리슨이 실패하거나 연결이 막힐 때 보는 오류

ECONNREFUSED · EADDRINUSE · EOPNOTSUPP · 연결 타임아웃 · errno

리슨 중인 포트를 들여다보는 도구

netstat · ss · lsof · nmap · 포트 스캔

리슨한 포트에 연결이 닿는지를 정하는 설정

포트 · 루프백 · 와일드카드 주소 · 네트워크 인터페이스 · 방화벽 · 포트 포워딩

리슨한 소켓에서 연결을 꺼내 처리하는 방식

SO_REUSEPORT · epoll · 이벤트 루프 · 스레드 풀 · 블로킹

리슨한 서버가 살아 있는지 살피는 수단

헬스 체크 · 로드 밸런서 · TCP 헬스 체크 · 응답 시간

리슨과 이름이 겹치는 다른 기술

이벤트 리스너 · 리스너 · 옵저버 패턴 · LISTEN과 NOTIFY

다른 이름: listen · 소켓 리슨 · 리스닝