4강 구현 중심 ⏱ 약 25분

 

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으로 실제 접속해 "클라이언트가 접속했습니다" 메시지를 서버 터미널에서 확인합니다.