주소가 이미 사용 중 — SO_REUSEADDR
에코 서버를 한 번 실행하고 클라이언트로 테스트한 다음, 서버를 종료하고 곧바로 다시 시작해 보세요. 아마 이런 오류를 만날 것입니다.
OSError: [Errno 48] Address already in use
리눅스라면 Errno 98로 나타납니다. 분명 서버를 종료했는데 왜 주소가 아직 사용 중이라고 할까요.
TIME_WAIT와 연결
PART 05의 4-way 종료 절을 기억하십니까. TCP 연결은 끊어진 후에도 먼저 종료를 시작한 쪽이 TIME_WAIT 상태로 약 2MSL, 최대 수 분 동안 머뭅니다. 그 기간에는 같은 IP와 포트 조합이 그 소켓에 묶여 있습니다.
서버를 Ctrl+C로 종료하면, 서버가 다루던 연결 소켓이 이 TIME_WAIT를 비롯한 정리 단계에 잠시 남아 그 포트를 붙잡고 있을 수 있습니다. 이 상태가 풀리기 전에 같은 포트로 서버를 다시 열려 하면, 기본 설정의 운영체제는 bind를 거부합니다. "아직 그 포트는 쓰는 중이야"라고 말하는 것입니다.
개발 중에 서버를 자주 재시작하는 환경에서는 이 오류가 반복해서 나타나 매우 불편합니다.
SO_REUSEADDR 옵션
해결책은 setsockopt로 SO_REUSEADDR 옵션을 켜는 것입니다. 이 옵션은 "TIME_WAIT 상태인 포트라도 재사용을 허용해 달라"는 요청입니다. 거의 모든 서버 프레임워크가 기본으로 설정하는 옵션입니다.
bind 전에, 즉 소켓을 만들자마자 설정해야 효과가 있습니다.
# 수정: echo_server.pyimport socketHOST = "127.0.0.1"PORT = 50007with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((HOST, PORT)) server.listen() print(f"서버가 {HOST}:{PORT}에서 대기 중입니다.") conn, addr = server.accept() with conn: print(f"{addr}에서 연결되었습니다.") while True: data = conn.recv(1024) if not data: break conn.sendall(data)
추가된 것은 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 한 줄뿐입니다. SOL_SOCKET은 소켓 레벨 옵션임을 나타냅니다. SO_REUSEADDR는 옵션 이름입니다. 1은 켜다는 뜻입니다.
옵션 적용 후 테스트
수정된 서버를 실행하고 클라이언트로 테스트한 뒤, 서버를 종료하고 즉시 다시 실행해 봅니다.
# 파일: 터미널 1python echo_server.py# (클라이언트 테스트 후 Ctrl+C로 종료)python echo_server.py # 오류 없이 바로 시작됨
TIME_WAIT 오류 없이 즉시 재시작됩니다.
한 걸음 물러서서
이 한 줄의 변화는 작아 보이지만, 그 배경에는 PART 05에서 배운 TCP 4-way 종료, TIME_WAIT의 존재 이유, 그리고 운영체제가 포트를 관리하는 방식이 모두 연결되어 있습니다. 코드를 외우는 것과 왜 그 코드가 필요한지 이해하는 것은 전혀 다른 이야기입니다.
다음 장에서는 IP 주소를 자동으로 받는 DHCP와, 네트워크가 켜질 때 무슨 일이 일어나는지를 살펴봅니다.