4강. 첫 번째 서버 만들기

0. 학습 목표
→ 이번 글에서 무엇을 이해하고, 무엇을 만들고, 무엇을 확인할지 먼저 정리합니다.
0.1 이번 글에서 다룰 내용
이번 글은 구현 중심 강의입니다.
3강에서 소켓의 역할과 서버-클라이언트 연결 흐름을 이해했습니다. 이번 강의에서는 그중 서버 쪽 흐름을 실제 Python 코드로 작성합니다. 아직 완성형 채팅 서버를 만들지는 않습니다. 이번 강의의 목표는 더 작고 분명합니다.
1. 서버가 실행된다
2. 서버 소켓을 만든다
3. IP와 포트에 서버 소켓을 묶는다
4. 클라이언트 접속을 기다리는 상태가 된다
이번 강의에서 만드는 chat_server/server.py는 최종 채팅 프로젝트 서버의 출발점으로, 이후 강의에서 메시지 처리·다중 접속·GUI 연결 등 기능을 하나씩 추가해 완성합니다.
가장 중요한 성공 기준은 서버 프로그램이 바로 종료되지 않고 클라이언트 접속을 기다리는 상태가 되는 것입니다. 처음에는 프로그램이 멈춘 것처럼 보일 수 있지만, accept()에서 기다리는 상태라면 정상입니다.
| 구분 | 내용 |
| 이해할 것 | 서버 소켓 생성, 주소 연결, 접속 대기 흐름(socket / bind / listen / accept) |
| 만들 것 | chat_server/server.py |
| 확인할 것 | 서버가 실행 후 오류 없이 클라이언트 접속을 기다리는 상태가 되는지 |
0.2 이번 강의에서 직접 다루는 구조
이번 강의에서는 chat_server/ 폴더를 만들고 그 안에 server.py를 생성합니다. 클라이언트는 다음 강의에서 만듭니다.
chat_server/
└── server.py ← 이번 강의에서 생성
chat_client/
└── client.py ← 5강에서 생성 예정
server.py 안에서 이번 강의에서 다루는 구조입니다.
chat_server/server.py
├── socket 모듈 가져오기
├── HOST와 PORT 정하기
├── 서버 소켓 만들기 → socket()
├── bind()로 주소 묶기
├── listen()으로 접속 대기
├── accept()로 접속 받기
└── 소켓 닫기 → try/except/finally
1. 실습 준비하기
→ 작업 폴더와 Python 실행 환경을 확인하고 server.py 파일을 만듭니다.
터미널을 열고 프로젝트 루트 폴더로 이동합니다.
mkdir network-chat-project
cd network-chat-project
이번 강의는 Python 기본 라이브러리인 socket을 사용합니다. 별도 설치는 필요하지 않습니다. Python이 실행되는지 먼저 확인합니다.
python --version
Python 3.12.0
Python 3.x.x 버전이면 이번 실습을 진행할 수 있습니다. 위 명령어가 동작하지 않으면 python3 --version을 시도합니다.
프로젝트 루트 폴더에서 서버 코드를 담을 폴더와 파일을 만듭니다.
mkdir chat_server
touch chat_server/server.py
파일 이름은 반드시 server.py로 맞춥니다. 이번 강의의 실행 명령어가 다음과 같기 때문입니다.
python chat_server/server.py
✔ 확인 기준: 터미널이 프로젝트 루트 폴더에 있고, 에디터에서 chat_server/server.py 파일을 열 수 있으면 완료. 현재 위치가 프로젝트 루트 폴더인지, 폴더 이름을 다르게 만들지 않았는지 확인하세요.
2. 서버 - 소켓 만들기
→ socket 모듈을 가져오고 HOST, PORT를 설정한 뒤 서버 소켓 객체를 만듭니다.
2.1 socket 모듈과 HOST, PORT 작성하기
chat_server/server.py 파일을 열고 다음 코드를 작성합니다.
import socket
HOST = "127.0.0.1"
PORT = 5000
| 이름 | 값 | 의미 |
| HOST | "127.0.0.1" | 현재 내 컴퓨터를 의미하는 주소. 같은 컴퓨터에서 서버와 클라이언트를 함께 실행할 때 사용 |
| PORT | 5000 | 서버 프로그램이 기다릴 입구 번호 |
2.2 서버 소켓 만들기
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.socket()은 새로운 소켓 객체를 만드는 코드입니다.
| 코드 | 의미 |
socket.AF_INET |
IPv4 주소 체계를 사용한다 |
socket.SOCK_STREAM |
TCP 방식으로 통신한다 |
2.3 파일 객체와 비교해 보기

소켓은 처음 보면 낯설지만, Python에서 파일을 사용할 때와 흐름이 비슷합니다.
# 파일 객체 흐름
file = open("memo.txt", "w", encoding="utf-8") # 객체 만들기
file.write("안녕하세요") # 객체로 작업하기
file.close() # 객체 닫기
소켓도 같은 흐름을 따릅니다.
# 소켓 객체 흐름
my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 객체 만들기
my_socket.connect(("127.0.0.1", 5000)) # 객체로 작업하기
my_socket.close() # 객체 닫기
| 구분 | 파일 객체 | 소켓 객체 |
| 만드는 방법 | open() |
socket.socket() |
| 작업 대상 | 파일 | 네트워크 연결 |
| 주요 작업 | read(), write() |
bind(), listen(), accept(), send(), recv() |
| 마무리 | close() |
close() |
✔ 확인 기준: server_socket = socket.socket(...) 코드가 작성되어 있으면 완료. 소켓 객체도 파일 객체처럼 만들고, 사용하고, 닫는 흐름을 가진다는 점을 설명할 수 있는지 확인하세요.
3. 서버 - 소켓 대기 상태 만들기
→ bind()로 서버 주소를 묶고 listen()으로 접속 대기 상태를 만듭니다.
3.1 bind()로 서버 주소 묶기
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
bind()는 서버 소켓을 특정 주소에 묶는 함수입니다. 괄호 구조에 주의해야 합니다. (HOST, PORT)를 하나의 튜플로 묶어 전달해야 하기 때문에 괄호가 두 겹처럼 보입니다.
# 올바른 형태
server_socket.bind((HOST, PORT))
# 잘못된 형태 — TypeError 발생
server_socket.bind(HOST, PORT)
💡 강사 팁 — 반복 실습 시 주소 충돌 방지
서버를 Ctrl+C로 강제 종료한 뒤 바로 재실행하면 다음 오류가 날 수 있습니다.
OSError: [Errno 98] Address already in use
운영체제가 방금 쓴 포트를 아직 정리 중이기 때문입니다. bind() 바로 전에 한 줄을 추가하면 이 오류 없이 바로 재실행할 수 있습니다.
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 포트 즉시 재사용 허용
server_socket.bind((HOST, PORT))
실습 중 서버를 여러 번 껐다 켜야 하는 상황이 많으므로, 지금 추가해 두면 이후 강의에서도 계속 편하게 쓸 수 있습니다.
3.2 listen()으로 접속 대기 상태 만들기
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"서버가 시작되었습니다. {HOST}:{PORT}")
print("클라이언트 접속을 기다리는 중입니다...")
listen()은 서버 소켓을 클라이언트 접속 대기 상태로 만드는 함수입니다. 여기까지 작성하면 서버는 다음 상태가 됩니다.
서버 소켓을 만들었다 → 127.0.0.1:5000 주소에 묶었다 → 클라이언트 접속을 기다릴 준비가 되었다
아직 클라이언트가 접속한 것은 아닙니다. 서버가 접속을 받을 준비를 마친 상태입니다.
✔ 확인 기준: server.py 안에 socket.socket(), bind((HOST, PORT)), listen() 코드가 들어 있으면 완료. bind(HOST, PORT)처럼 괄호를 잘못 쓰지 않았는지 확인하세요.
4. 서버 - 클라이언트 요청 수락 만들기
→ accept()로 클라이언트 접속 대기 코드를 추가하고, try/except/finally로 종료 처리 구조를 연결합니다.
4.1 accept()로 클라이언트 접속 기다리기
client_socket, client_address = server_socket.accept()
accept()는 클라이언트가 접속할 때까지 기다리는 함수입니다. 이 줄에서 프로그램이 멈춘 것처럼 보일 수 있지만 오류가 아닙니다. 서버가 클라이언트 접속을 기다리는 정상 상태입니다. 클라이언트 접속이 들어오면 두 값을 돌려줍니다.
| 반환값 | 의미 |
client_socket |
접속한 클라이언트와 실제로 통신할 소켓 |
client_address |
접속한 클라이언트의 주소 정보 |
server_socket은 접속을 기다리는 소켓이고, client_socket은 접속한 클라이언트와 실제로 대화하는 소켓입니다.
4.2 try/except/finally로 종료 처리 연결하기
실습을 끝내려면 Ctrl + C를 눌러 직접 종료해야 합니다. 이때 서버 소켓을 닫기 위해 try/except/finally 구조를 사용합니다.
| 코드 | 이번 강의에서의 역할 |
try |
서버 실행 코드를 시도하는 부분 |
except KeyboardInterrupt |
Ctrl + C로 종료했을 때 처리하는 부분 |
finally |
중간에 종료되더라도 서버 소켓을 닫는 부분 |
try/except/finally를 적용한 전체 구조입니다.
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"서버가 시작되었습니다. {HOST}:{PORT}")
print("클라이언트 접속을 기다리는 중입니다...")
client_socket, client_address = server_socket.accept()
print(f"클라이언트가 접속했습니다: {client_address}")
client_socket.close()
print("클라이언트 연결을 닫았습니다.")
except KeyboardInterrupt:
print("\n서버를 직접 종료했습니다.")
finally:
server_socket.close()
print("서버 소켓을 닫았습니다.")
서버 실행 흐름은 다음과 같습니다.
python chat_server/server.py 실행
↓
socket()으로 서버 소켓 생성
↓
bind()로 127.0.0.1:5000에 연결
↓
listen()으로 접속 대기 상태 진입
↓
accept()에서 클라이언트 접속 대기 ← 이번 강의의 성공 지점
✔ 확인 기준: accept()가 서버의 접속 대기 함수임을 설명할 수 있고, try/except/finally 구조가 들어 있으면 완료. 서버가 바로 종료되지 않고 기다리는 상태가 정상이라는 것을 알고 있는지 확인하세요.
5. 실행 결과 확인하기
→ 서버를 실행하고 접속 대기 상태가 되는지 확인합니다.
5.1 정상 실행 확인
python chat_server/server.py
서버가 시작되었습니다. 127.0.0.1:5000
클라이언트 접속을 기다리는 중입니다...
여기서 프로그램이 멈춘 것처럼 보이는 것이 정상입니다. 서버가 accept()에서 클라이언트를 기다리는 중입니다. 이번 강의에서는 아직 클라이언트 파일을 만들지 않았으므로 이 상태가 확인되면 성공입니다.
실습을 멈추고 싶다면 서버가 실행 중인 터미널에서 Ctrl + C를 누릅니다.
서버를 직접 종료했습니다.
서버 소켓을 닫았습니다.
5.2 흔한 오류와 해결 방법
오류가 나오면 어느 단계에서 실패했는지 먼저 생각합니다.
| 오류 상황 | 원인 및 해결 방법 |
Address already in use |
5000번 포트를 이미 다른 프로그램이 사용 중. 기존 서버를 종료하거나 PORT = 5001처럼 번호를 바꾼다. 단, 다음 강의 클라이언트도 같은 포트를 사용해야 한다 |
can't open file 'chat_server/server.py' |
현재 위치가 프로젝트 루트가 아니거나 파일 경로가 다름. 프로젝트 루트에서 실행한다 |
NameError: name 'socket' is not defined |
import socket이 없거나 오타가 있음. 파일 맨 위에 import socket이 있는지 확인한다 |
TypeError: bind() takes 2 arguments |
bind(HOST, PORT)처럼 인자를 잘못 전달. bind((HOST, PORT))처럼 튜플로 묶어 전달한다 |
PermissionError |
사용 권한이 제한된 포트를 사용. 5000, 8000처럼 일반 실습용 포트를 사용한다 |
| 서버가 멈춘 것처럼 보임 | accept()가 클라이언트 접속을 기다리는 중. 정상 동작이다. 이번 강의에서는 이 상태가 성공이다 |
Address already in use는 가장 자주 만나는 오류 중 하나입니다. PORT 값을 바꿀 때는 서버와 클라이언트의 포트 번호가 반드시 일치해야 한다는 점을 기억하세요.
✔ 확인 기준: python chat_server/server.py 실행 후 "서버가 시작되었습니다. 127.0.0.1:5000" 출력과 함께 대기 상태가 되면 완료. 오류 메시지를 보고 파일 경로, 포트, bind() 괄호, 서버 실행 상태를 차례대로 점검할 수 있으면 충분합니다.
6. 최종 코드 정리하기
→ 이번 강의에서 완성한 chat_server/server.py 전체 코드를 한곳에 정리합니다.
6.1 chat_server/server.py
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"서버가 시작되었습니다. {HOST}:{PORT}")
print("클라이언트 접속을 기다리는 중입니다...")
client_socket, client_address = server_socket.accept()
print(f"클라이언트가 접속했습니다: {client_address}")
client_socket.close()
print("클라이언트 연결을 닫았습니다.")
except KeyboardInterrupt:
print("\n서버를 직접 종료했습니다.")
finally:
server_socket.close()
print("서버 소켓을 닫았습니다.")
6.2 최종 확인 표
| 확인할 코드 | 의미 |
import socket |
소켓 모듈 불러오기 |
HOST = "127.0.0.1" |
현재 컴퓨터에서 실습 |
PORT = 5000 |
서버가 사용할 포트 |
socket.socket(...) |
서버 소켓 생성 |
bind((HOST, PORT)) |
서버 주소 연결 |
listen() |
접속 대기 상태 진입 |
accept() |
클라이언트 접속 받기 — 이번 강의의 성공 지점 |
client_socket.close() |
접속한 클라이언트와의 연결 닫기 |
server_socket.close() |
서버 소켓 닫기 |
이번 강의에서는 아직 클라이언트가 없으므로 accept()에서 기다리는 상태까지만 확인되면 성공입니다. 다음 강의에서 클라이언트를 만들면 "클라이언트가 접속했습니다" 메시지까지 확인할 수 있습니다.
→ 다음 강의 (5강): chat_client/client.py를 만들고, 이번 강의에서 완성한 서버의 127.0.0.1:5000으로 실제 접속해 "클라이언트가 접속했습니다" 메시지를 서버 터미널에서 확인합니다.