사전 바인드
인터페이스

바인드

gabury1고친 사람 github-actions[bot]

바인드는 프로그램이 어느 주소와 포트로 오는 데이터를 받을지 운영체제에 알리는 일입니다. 이 일을 해 둬야 찾아온 데이터가 그 프로그램에게 닿습니다. 도커나 데이터베이스에서도 바인드라는 말을 다른 뜻으로 씁니다. 이 글은 네트워크 통신의 바인드를 다룹니다.

쉽고 빠른 이해

바인드는 서버 프로그램이 「이 주소, 이 번호로 오는 것은 내가 받는다」고 운영체제에 등록하는 일입니다. 웹 서버가 8080번 포트를 맡는 것이 바인드입니다.

등록이 없으면 운영체제는 도착한 데이터를 어느 프로그램에게 줄지 모릅니다. 한 컴퓨터에서 프로그램 여럿이 통신합니다. 그래서 누가 무엇을 받을지 미리 정해 둬야 합니다.

어떻게 도나:

  1. 프로그램이 통신 창구를 하나 만듭니다
  2. 그 창구에 주소와 번호를 붙입니다. 이것이 바인드입니다
  3. 연결을 받겠다고 알린 뒤 붙는 연결을 하나씩 받습니다

대가는 번호 하나에 주인이 하나뿐이라는 것입니다. 이미 누가 맡은 번호는 못 맡습니다. 어느 주소에 붙이느냐에 따라 다른 컴퓨터에서 아예 못 들어오기도 합니다.

연결을 거는 쪽은 보통 바인드하지 않습니다. 운영체제가 남는 번호를 골라 붙여 줍니다.

상세

이 절은 바인드가 무엇을 정하는지에서 시작합니다. 파이썬 코드 몇 줄로 서버가 연결을 받는 순서를 따라갑니다. 끝은 바인드가 실패하는 흔한 경우입니다.

소켓은 프로그램이 네트워크로 데이터를 주고받으려고 운영체제에서 받아 쓰는 통신 창구입니다. 프로그램은 소켓에 쓰고 소켓에서 읽습니다. 처음 만든 소켓에는 주소가 붙어 있지 않습니다.

바인드는 이 소켓에 주소와 포트 번호를 붙이는 일입니다. 주소는 이 컴퓨터가 가진 IP 주소(Internet Protocol address, 인터넷 프로토콜 주소) 가운데 하나입니다. 한 컴퓨터가 주소를 여럿 가질 수 있는 까닭은 아래 「붙이는 주소와 받아 주는 연결」에서 봅니다.

포트는 한 컴퓨터 안에서 데이터를 받을 프로그램을 가리키는 번호입니다. 주소만으로는 같은 컴퓨터에서 도는 프로그램 여럿을 가를 수 없습니다. 그래서 번호를 하나 더 씁니다.

운영체제는 바인드한 주소·포트와 소켓을 짝지어 적어 둡니다. 데이터가 도착하면 데이터에 적힌 도착 주소와 포트를 읽습니다. 적어 둔 짝에서 맞는 소켓을 찾아 데이터를 넘깁니다. 바인드가 없으면 찾을 짝이 없습니다.

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

서버가 연결을 받기까지

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

TCP(Transmission Control Protocol, 전송 제어 프로토콜)는 데이터가 빠짐없이 순서대로 닿게 챙기는 통신 방식입니다. 보내기 전에 양쪽이 연결을 먼저 맺습니다. TCP 서버는 첫째 호출 socket 으로 소켓을 만들고 바인드한 뒤 연결을 기다립니다.

셋째 호출 listen 은 이 소켓으로 연결을 받겠다고 운영체제에 알립니다. 이 일을 리슨이라고 부릅니다. 리슨한 뒤부터 운영체제는 이 포트로 온 연결 요청을 받아 줄을 세워 둡니다.

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

sequenceDiagram
    participant 서버 as 서버 프로그램
    participant 운영체제
    participant 클라이언트
    서버->>운영체제: socket · 소켓을 만든다
    서버->>운영체제: bind · 8080 번을 붙인다
    Note over 운영체제: 8080 번과 이 소켓을 짝지어 적어 둔다
    서버->>운영체제: listen · 연결을 받겠다
    클라이언트->>운영체제: 8080 번으로 연결 요청
    Note over 운영체제: 적어 둔 짝으로 소켓을 찾아 줄을 세운다
    서버->>운영체제: accept · 하나 꺼내 달라
    운영체제-->>서버: 연결 하나와 새 소켓

그림에서 운영체제는 바인드 때 적어 둔 짝을 보고 연결 요청을 서버의 소켓으로 보냅니다. 서버는 accept 로 그 연결을 꺼냅니다.

아래는 파이썬으로 같은 순서를 적은 코드입니다.

Python
import socket

s = socket.socket()
s.bind(("0.0.0.0", 8080))
s.listen()
conn, addr = s.accept()
print(addr)  # ('10.0.0.5', 51413)

bind 에는 주소와 포트를 한 쌍으로 넘깁니다. 0.0.0.0 은 이 컴퓨터의 어느 주소로 오든 받겠다는 뜻입니다. 이 값은 아래 「붙이는 주소와 받아 주는 연결」에서 다시 봅니다.

accept 는 값 둘을 돌려줍니다. conn 은 accept 가 이 연결에 쓰라고 만든 새 소켓입니다. addr 는 걸어온 쪽의 주소와 포트입니다.

마지막 줄은 10.0.0.5 에서 붙은 클라이언트를 받았을 때 찍히는 값입니다. 앞은 걸어온 쪽의 주소입니다. 뒤는 걸어온 쪽의 포트입니다.

클라이언트는 바인드를 안 했는데도 51413번을 갖고 있습니다. 이 번호의 출처는 아래 「바인드를 건너뛰는 클라이언트」에서 봅니다.

UDP(User Datagram Protocol, 사용자 데이터그램 프로토콜)는 연결을 맺지 않고 데이터 묶음을 하나씩 보내는 방식입니다. UDP 소켓은 리슨과 accept 없이 바인드만 하면 바로 데이터를 받습니다. 맺은 연결이 없으니 줄 세울 것도 없기 때문입니다.

붙이는 주소와 받아 주는 연결

이 소절은 바인드에 넘기는 주소가 누가 들어올 수 있는지를 어떻게 가르는지 봅니다. 흔히 고르는 주소 셋을 표로 견줍니다.

컴퓨터가 망에 닿는 출입구 하나하나를 네트워크 인터페이스라고 부릅니다. 유선 랜과 와이파이가 각각 인터페이스 하나입니다. 인터페이스마다 IP 주소가 따로 붙습니다. 그래서 주소를 고르는 것은 곧 어느 출입구로 온 연결을 받을지 고르는 것입니다.

루프백은 컴퓨터가 자기 자신에게 보낼 때 쓰는 가상의 인터페이스입니다. 주소는 127.0.0.1 입니다. 같은 컴퓨터 안의 프로그램끼리 통신할 때 씁니다.

0.0.0.0 은 한 주소가 아니라 모든 인터페이스를 뜻하는 값입니다. 무엇이든 맞는다는 표시라서 이런 값을 와일드카드 주소라고 부릅니다.

붙이는 주소 받아 주는 연결
127.0.0.1 같은 컴퓨터 안에서 온 연결만
한 인터페이스의 주소 그 주소로 찾아온 연결만
0.0.0.0 모든 인터페이스로 들어온 연결

IPv6(Internet Protocol version 6, 인터넷 프로토콜 6판) 주소에서는 ::1 이 루프백이고 :: 가 와일드카드입니다. 뜻은 표와 같습니다.

「서버는 떴는데 밖에서 안 붙는다」는 흔한 증상이 이 표에서 나옵니다. 개발용 설정은 흔히 127.0.0.1 에 붙입니다. 이 값을 바꾸지 않고 서버에 올리면 다른 컴퓨터에서는 닿지 않습니다. 서버 설정에서 이 값은 대개 bind 나 host 라는 이름의 항목에 들어갑니다.

컨테이너는 프로그램을 격리된 환경에 담아 돌리는 방식입니다. 컨테이너는 보통 자기 몫의 네트워크 인터페이스를 따로 받습니다.

그래서 컨테이너의 127.0.0.1 은 컨테이너 자신만 가리킵니다. 표의 「같은 컴퓨터 안」이 컨테이너에서는 컨테이너 안쪽뿐입니다. 컨테이너 안에서 127.0.0.1 에 붙인 서버는 컨테이너 밖에서 닿지 않습니다.

좁은 주소에 붙이는 쪽이 쓸모 있을 때도 있습니다. 데이터베이스나 캐시 서버는 바깥에 드러나면 안 됩니다. 이런 서버를 루프백이나 안쪽 망의 주소에만 붙이면 바깥에서 온 연결은 닿지 못합니다. 지나가는 데이터를 규칙으로 걸러 내는 방화벽과 별개로 서버 스스로 거는 잠금입니다.

번호 하나에 주인 하나

이 소절은 바인드가 실패하는 흔한 경우 셋을 봅니다. 실패하면 파이썬은 OSError 예외를 던집니다. 운영체제는 실패 이유를 오류 코드로 알려 줍니다. 코드마다 EADDRINUSE 같은 이름이 붙어 있습니다.

이름 찍히는 메시지 흔한 원인
EADDRINUSE Address already in use 다른 소켓이 이미 그 포트를 맡았다
EACCES Permission denied 관리자 권한 없이 1024보다 작은 포트를 맡으려 했다
EADDRNOTAVAIL Cannot assign requested address 이 컴퓨터에 없는 주소를 적었다

첫째 줄을 제일 자주 만납니다. 도착한 데이터를 누구에게 줄지 정하려면 번호 하나가 소켓 하나를 가리켜야 합니다. 그래서 운영체제는 이미 쓰이는 포트를 다른 소켓이 또 맡으려 하면 거절합니다. 서버를 두 번 띄웠을 때 이 오류가 납니다.

둘째 줄의 1024보다 작은 번호를 특권 포트라고 부릅니다. 웹의 443번처럼 널리 약속된 서비스가 쓰는 번호입니다. 아무 프로그램이나 이 번호를 맡으면 그 서비스를 흉내 낼 수 있습니다. 그래서 유닉스 계열 운영체제는 이 번호를 바인드할 때 관리자 권한을 요구합니다.

셋째 줄은 주소를 잘못 적었을 때 납니다. 다른 컴퓨터의 주소를 적으면 운영체제가 받아 주지 않습니다. 이 컴퓨터가 가진 주소에만 바인드할 수 있습니다.

막 끈 서버를 다시 띄울 때

서버를 끄고 곧바로 다시 띄우면 EADDRINUSE 가 나기도 합니다. 그 포트를 쓰는 프로그램이 없는데도 그렇습니다. 원인은 방금 끊긴 연결이 남긴 기록입니다.

TCP 연결을 먼저 끊은 쪽은 그 연결의 기록을 한동안 남겨 둡니다. 늦게 도착한 옛 데이터가 같은 번호로 맺은 새 연결에 섞이지 않게 하려는 것입니다. 이 상태를 TIME_WAIT라고 부릅니다. 서버가 연결을 먼저 끊었다면 이 기록이 서버의 포트를 붙든 채 남습니다.

소켓 옵션은 소켓의 동작을 바꾸는 설정값입니다. 프로그램이 소켓을 만든 뒤 켜고 끕니다.

SO_REUSEADDR는 그런 옵션 가운데 하나입니다. 이 옵션을 켜면 TIME_WAIT 기록이 남아 있어도 운영체제가 바인드를 허락합니다. 서버 프로그램은 흔히 바인드하기 전에 이 옵션을 켭니다.

Python
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET,
             socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", 8080))

setsockopt 는 소켓 옵션을 켜고 끄는 호출입니다. 두 줄에 걸쳐 적었지만 한 호출입니다.

SOL_SOCKET 은 TCP 같은 프로토콜의 옵션이 아니라 모든 소켓에 공통인 옵션이라는 표시입니다. 끝의 1 은 켠다는 뜻입니다.

옵션은 바인드보다 먼저 켭니다. 운영체제가 바인드를 받을 때 이 옵션을 보고 허락할지 정하기 때문입니다.

바인드를 건너뛰는 클라이언트

이 소절은 연결을 거는 쪽을 봅니다. 앞의 코드에서 클라이언트가 가진 51413번의 출처가 이 소절에서 풀립니다.

연결을 거는 쪽은 보통 바인드를 부르지 않습니다. 곧장 connect 로 상대에게 연결을 겁니다. 그러면 운영체제가 남는 포트 하나를 골라 소켓에 붙여 줍니다.

이렇게 운영체제가 빌려 주는 번호를 임시 포트라고 부릅니다. 서버의 답장이 돌아오면 운영체제는 이 번호로 클라이언트의 소켓을 찾습니다. 연결이 끝나면 번호를 돌려받아 다음 연결에 빌려 줍니다.

서버도 번호를 운영체제에 맡길 수 있습니다. 포트를 0으로 바인드하면 운영체제가 비어 있는 번호를 골라 붙입니다. 고른 번호는 바인드한 뒤에 물어봐서 알아냅니다.

Python
s = socket.socket()
s.bind(("127.0.0.1", 0))
print(s.getsockname()[1])  # 54321

getsockname 은 소켓에 붙은 주소와 포트를 돌려줍니다. 찍히는 번호는 실행할 때마다 다릅니다. 테스트 코드가 서버를 여럿 띄울 때 이 방법을 씁니다. 번호를 미리 정하지 않으니 테스트끼리 포트가 부딪히지 않습니다.

이름이 겹치는 다른 바인드

영어 bind 는 「묶는다」는 뜻이라 여러 분야가 저마다의 뜻으로 씁니다. 백엔드 개발자가 자주 만나는 셋을 표로 모았습니다.

부르는 말 분야 무엇을 묶나
바인드 마운트 컨테이너 바깥 컴퓨터의 디렉토리를 컨테이너 안 경로에 이어 붙입니다
바인드 변수 데이터베이스 쿼리 쿼리의 빈칸에 들어갈 값을 쿼리와 따로 실어 보냅니다
함수의 bind 자바스크립트 함수 안의 this 가 가리킬 객체를 고정합니다

셋 다 소켓이나 포트와 관계없는 기술입니다.

관련 항목

바인드와 함께 서버 소켓을 여는 호출

소켓 · 리슨 · accept · connect · close · getsockname · setsockopt · 버클리 소켓 · 시스템 콜

바인드가 소켓에 붙이는 주소

IP 주소 · 포트 · 소켓 주소 · 와일드카드 주소 · 루프백 · 네트워크 인터페이스 · IPv6

바인드할 포트 번호의 구간

잘 알려진 포트 · 특권 포트 · 등록 포트 · 임시 포트

바인드가 실패할 때 받는 오류와 그 해법

EADDRINUSE · EACCES · EADDRNOTAVAIL · TIME_WAIT · SO_REUSEADDR · SO_REUSEPORT

바인드한 소켓을 들여다보는 도구

netstat · ss · lsof · nmap

바인드 주소와 함께 연결이 닿는지를 정하는 설정

방화벽 · 포트 포워딩 · NAT · 포트 매핑

바인드 위에서 데이터를 실어 나르는 프로토콜

TCP · UDP · 전송 계층 · 역다중화

이름에 바인드가 들어가는 다른 용어

바인드 마운트 · 바인드 변수 · 바인드 그룹 · 바인드 포즈 · 바인딩

다른 이름: bind · 소켓 바인드 · 주소 바인드