19강 개념 중심 ⏱ 약 25분

 

0. 학습 목표

→ 클래스가 왜 필요한지 이해하고, 기존 전역 변수·함수가 어느 클래스로 이동하는지 정리합니다.

더보기

0.1 이번 글에서 다룰 내용

이번 글은 개념 중심 강의입니다.

 

18.5강까지 터미널 채팅 서버와 클라이언트를 완성했습니다.

기능은 잘 동작하지만, 코드가 전역 변수와 함수로 흩어진 채 서로 얽혀 있습니다.

이 상태에서 GUI를 붙이면 화면 코드와 네트워크 코드가 한 파일에 뒤섞여 오류 원인을 찾기 어려워집니다.

 

이번 강의에서는 지금 코드를 바로 리팩토링하지 않습니다. 먼저 클래스가 왜 필요한지, 어떤 역할을 어떻게 나누면 좋은지 이해하는 것이 목표입니다. 여기서 정리하는 역할 분리 기준은 이후 Part 5~7 전체에서 그대로 유지됩니다.

구분 내용
이해할 것 함수·전역 변수 중심 코드의 한계와 클래스 구조가 필요한 이유
정리할 것 ChatServer / ChatClient / GUI 클래스의 역할 구분 기준
확인할 것 기존 전역 변수·함수가 어느 클래스의 속성·메서드로 이동하는지 대응 관계

 

0.2 앞으로 만들 프로젝트 구조 미리보기

이번 강의는 개념 강의이므로 기존 파일을 수정하지 않습니다. 원하면 이번 강의 예시 코드를 class_preview.py에 작성해 실행해 볼 수 있습니다.

chat_server/
├── protocol.py          ← 17강에서 만든 메시지 규칙 (그대로 유지)
├── server.py            ← 이번 강의에서는 수정하지 않음
└── class_preview.py     ← (선택) 이번 강의 예시 실습 파일

chat_client/
├── protocol.py          ← 17강 protocol.py 복사본 (그대로 유지)
└── client.py            ← 이번 강의에서는 수정하지 않음

GUI 파트(20강~)에서는 구조가 다음처럼 발전합니다. 이번 강의에서는 이 구조를 완성하지 않고, 역할을 나누는 기준만 먼저 잡습니다.

chat_server/
├── protocol.py          ← 메시지 규칙
└── server.py            ← ChatServer 클래스 포함 (이후 강의에서 수정)

chat_client/
├── protocol.py          ← 메시지 규칙
├── client.py            ← ChatClient 클래스 포함 (네트워크 담당)
└── main.py              ← GUI 진입점·화면 (21강~ 생성 예정)

 

1. 핵심 개념 이해하기

→ 지금 코드에서 무엇이 불편해지는지, 클래스가 그것을 어떻게 해결하는지 이해합니다.

더보기

1.1 함수·전역 변수 중심 코드의 한계

18.5강까지 작성한 서버 코드는 함수와 전역 변수 중심입니다. 처음 배우기에는 간결하지만, 기능이 늘어날수록 다음 문제가 생깁니다.

문제 설명
전역 변수가 많아진다 clients, HOST, PORT 같은 값을 여러 함수가 함께 사용한다
함수 사이 관계가 복잡해진다 어떤 함수가 어떤 값을 바꾸는지 계속 추적해야 한다
GUI와 섞이기 쉽다 버튼 클릭 처리 코드 안에 네트워크 코드가 들어갈 수 있다
재사용이 어렵다 터미널 클라이언트 코드를 GUI 클라이언트에서 그대로 가져다 쓰기 어렵다

특히 GUI에서는 화면 코드(버튼 클릭, 텍스트 표시)와 네트워크 코드(send(), recv())가 섞이면 화면이 멈추거나 충돌이 발생합니다. 이 문제를 풀려면 역할을 나누어야 합니다.

 

1.2 클래스는 관련된 데이터와 기능을 묶는 방법이다

클래스는 관련된 값과 함수를 하나의 역할 단위로 묶는 도구입니다. 서버에는 다음 정보와 기능이 함께 필요합니다.

서버 IP / 서버 포트 / 서버 소켓 / 접속자 목록

        ↓ 함께 묶으면

클라이언트 처리 / 브로드캐스팅 / 서버 시작

이것들을 하나로 묶으면 ChatServer 클래스를 만들 수 있습니다.

class ChatServer:
    def __init__(self, host, port):
        self.host = host        # 이 서버 객체가 기억하는 IP
        self.port = port        # 이 서버 객체가 기억하는 포트
        self.clients = []       # 이 서버 객체가 관리하는 접속자 목록

    def start(self):
        print("서버를 시작합니다.")            # 실제 socket/bind/listen은 이후 강의에서 채운다

    def broadcast(self, message):
        print("모든 클라이언트에게 메시지를 보냅니다.")   # 13강 broadcast() 로직이 이 안으로 들어온다

self.clients는 이 서버 객체가 관리하는 접속자 목록입니다. 여러 함수가 공유하던 전역 변수 clients보다 "누구의 목록인지"가 분명해집니다.

 

1.3 클래스 용어 정리

처음 보면 낯선 용어들입니다. 이번 강의에서는 다음 수준으로 이해하면 충분합니다.

구분 예시 한 줄 의미
클래스 ChatServer 서버 역할을 하는 설계도
객체 server = ChatServer(...) 설계도대로 실제로 만들어진 서버
속성 self.clients 이 객체가 기억하는 값
메서드 start(), broadcast() 이 객체가 할 수 있는 동작

self는 "이 객체 자신"을 의미합니다. self.clients = []는 "이 서버 객체가 자기 접속자 목록을 가진다"는 뜻입니다. self가 붙은 값은 이 객체가 기억하는 것이고, 클래스 안에서 self.를 빠뜨리면 NameError가 나거나 엉뚱한 값이 바뀌는 버그가 생깁니다.

✔ 확인 기준: "클래스는 관련된 값과 기능을 역할별로 묶는 도구이며, self.clients는 이 서버 객체가 관리하는 접속자 목록이다"라고 설명할 수 있으면 완료. 클래스·객체·속성·메서드를 ChatServer 예시에서 하나씩 가리키며 설명할 수 있으면 충분합니다.

 

2. 구조나 흐름으로 확인하기

→ 함수 중심 구조와 클래스 중심 구조를 나란히 비교하고, GUI를 위해 클라이언트도 분리해야 하는 이유를 확인합니다.

더보기

2.1 사물함 비유로 이해하기

학교 사물함을 생각해 보겠습니다. 학생 50명의 물건을 보관하는 방법은 두 가지입니다. 첫 번째는 창고 하나에 모두 넣는 것입니다. 표시가 없으면 꺼낼 때마다 전부 뒤져야 합니다. 두 번째는 학생마다 이름표가 붙은 사물함을 하나씩 배정하는 것입니다.

지금 서버 코드는 첫 번째 방식입니다. clients, HOST, PORT가 창고에 뒤섞여 있고 모든 함수가 그 창고에 직접 손을 뻗습니다. 클래스는 두 번째 방식처럼 역할별로 "이름표가 붙은 사물함"을 만드는 것입니다.

비유 코드에서의 대응
이름 없는 공용 창고 전역 변수 clients, HOST, PORT
서버 사물함 ChatServer 클래스
클라이언트 사물함 ChatClient 클래스
GUI 사물함 ChatWindow 클래스
사물함 안 물건 속성 (self.clients, self.host)
사물함 자물쇠 여닫기 메서드 (start(), broadcast())

 

2.2 함수 중심 vs 클래스 중심 구조 비교

함수 중심 구조에서는 clients를 여러 함수가 공유합니다.

server.py (함수 중심)
├── clients             ← 전역 변수, 모든 함수가 사용
├── HOST / PORT         ← 전역 변수
├── print_clients()     ← clients 사용
├── broadcast()         ← clients 사용
├── handle_client()     ← clients 사용
└── 서버 실행 코드      ← clients 사용

클래스로 바꾸면 서버와 관련된 값과 기능이 ChatServer 안에 모입니다.

ChatServer (클래스 중심)
├── 속성: host / port / server_socket / clients
└── 메서드: start() / handle_client() / broadcast() / send_error() / print_clients()

실제로 사용할 때는 이렇게 됩니다.

server = ChatServer("127.0.0.1", 5000)   # 서버 객체를 하나 만든다
server.start()                            # 그 객체에게 "시작해"라고 시킨다

 

2.3 GUI를 위해 클라이언트도 분리해야 하는 이유

GUI 채팅에서는 사용자가 버튼을 누르면 메시지를 보내고, 서버에서 메시지가 오면 화면에 표시해야 합니다.

 

만약 GUI 코드 안에 recv()를 직접 넣으면 화면이 응답하지 못하고 멈춥니다.

GUI가 멈추는 가장 흔한 원인이 바로 이 분리가 안 되어 있을 때입니다.

그래서 네트워크 처리는 ChatClient 클래스가 따로 담당해야 합니다.

ChatClient
├── 속성: host / port / socket / receive_thread
└── 메서드: connect() / send_chat() / send_exit() / receive_loop() / close()

나중에 GUI 클래스는 네트워크 세부 코드를 몰라도 이렇게 호출하기만 하면 됩니다.

client.send_chat("안녕하세요")   # GUI는 "이 메시지 보내줘"라고만 부탁한다

GUI는 화면만 담당하고, ChatClient는 네트워크만 담당합니다. 둘은 필요한 순간에만 값을 주고받습니다.

✔ 확인 기준: 함수 중심 구조에서 여러 함수가 clients를 공유하던 문제가 클래스 구조에서 어떻게 정리되는지, 그리고 GUI에서 recv()를 화면 코드와 분리해야 하는 이유를 설명할 수 있으면 완료입니다.

 

3. 판단 기준 정리하기

→ 어떤 코드를 클래스로 묶을지 판단하는 기준과, 기존 코드가 새 구조의 어디로 이동하는지 정리합니다.

더보기

3.1 클래스로 묶기 좋은 코드 기준

기준 예시
같은 데이터를 여러 함수가 함께 사용한다 clientsbroadcast(), handle_client(), print_clients()가 함께 사용
역할이 분명하다 서버 역할 / 클라이언트 역할 / GUI 역할
나중에 재사용하고 싶다 터미널 클라이언트와 GUI 클라이언트가 같은 네트워크 기능 사용
기능이 계속 늘어날 예정이다 귓속말 / 파일 전송 / 사용자 목록 등 Part 6~7에서 추가될 기능들

 

3.2 기존 코드와 새 구조의 대응표

지금까지 만든 전역 변수와 함수가 클래스 구조에서 어디로 이동하는지 정리하면 다음과 같습니다.

기존 함수 또는 변수 이동할 위치 이유
clients (전역) ChatServer.clients 서버가 관리하는 접속자 목록
HOST, PORT (전역) ChatServer.__init__ 인자 서버 생성 시 전달
server_socket (전역) ChatServer.server_socket 서버 객체가 자기 소켓을 기억
print_clients() ChatServer.print_clients() 서버가 접속자 수를 출력
broadcast() ChatServer.broadcast() 서버가 전체 전달을 담당
send_error() ChatServer.send_error() 서버가 오류 메시지를 보냄
handle_client() ChatServer.handle_client() 서버가 각 클라이언트를 처리
client_socket (client.py) ChatClient.socket 클라이언트 객체가 자기 연결을 기억
receive_loop() ChatClient.receive_loop() 클라이언트가 서버 메시지를 계속 받음
input() 루프 (터미널) 그대로 / (GUI) 버튼·입력창 GUI에서는 이벤트로 바뀜

 

3.3 자주 하는 오해

오해 올바른 이해
클래스로 바꾸면 기능이 자동으로 좋아진다 구조만 바뀔 뿐 동작 로직은 직접 구현해야 합니다. 클래스는 기능 추가가 아니라 관리 방식의 개선입니다
self 없이 clients만 써도 된다 전역 변수와 객체 속성이 섞여 혼란이 생깁니다. 클래스 안에서는 반드시 self.clients처럼 속성으로 관리합니다
모든 것을 하나의 큰 클래스에 넣는다 클래스가 너무 커지면 다시 복잡해집니다. 서버 / 클라이언트 / GUI처럼 역할별로 나눕니다

✔ 확인 기준: 전역 변수 clients와 함수 broadcast()·handle_client()가 클래스 구조에서 어디로 이동하는지 대응표를 보지 않고 말할 수 있으면 완료입니다.

 

4. 작은 예시로 검토하기

→ 함수 중심 코드와 클래스 중심 코드가 같은 결과를 낸다는 것을 직접 실행해서 확인합니다.

더보기

4.1 함수 중심 코드 예시

접속자 추가·삭제를 함수와 전역 변수로만 작성하면 다음과 같습니다.

clients = []            # 전역 변수로 관리

def add_client(name):
    clients.append(name)                   # 전역 목록에 추가
    print(f"{name} 접속")
    print(f"현재 접속자 수: {len(clients)}")

def remove_client(name):
    clients.remove(name)                   # 전역 목록에서 제거
    print(f"{name} 퇴장")
    print(f"현재 접속자 수: {len(clients)}")

add_client("user1")
add_client("user2")
remove_client("user1")
user1 접속
현재 접속자 수: 1
user2 접속
현재 접속자 수: 2
user1 퇴장
현재 접속자 수: 1

 

4.2 클래스 중심 코드 예시

같은 기능을 클래스로 묶으면 다음과 같습니다. chat_server/class_preview.py에 작성해 실행해 볼 수 있습니다.

class ChatServerPreview:
    def __init__(self):
        self.clients = []       # 이 서버 객체가 관리하는 접속자 목록

    def add_client(self, name):
        self.clients.append(name)          # 이 객체의 목록에 추가한다
        print(f"{name} 접속")
        self.print_clients()               # 같은 객체 안의 메서드를 self로 호출한다

    def remove_client(self, name):
        if name in self.clients:
            self.clients.remove(name)      # 이 객체의 목록에서 제거한다
        print(f"{name} 퇴장")
        self.print_clients()

    def print_clients(self):
        print(f"현재 접속자 수: {len(self.clients)}")


server = ChatServerPreview()    # 서버 객체를 하나 만든다

server.add_client("user1")
server.add_client("user2")
server.remove_client("user1")
python chat_server/class_preview.py
user1 접속
현재 접속자 수: 1
user2 접속
현재 접속자 수: 2
user1 퇴장
현재 접속자 수: 1

기능 결과는 같지만 구조가 달라졌습니다. clients가 전역 변수 대신 server 객체 안의 self.clients로 관리됩니다.

self를 처음 보면 "왜 매번 써야 하나" 싶은데, 이것이 없으면 clients가 전역 변수인지 이 객체의 속성인지 Python이 구분하지 못합니다. self.clients는 "이 서버 객체의 접속자 목록"이고 clients는 "어딘가의 전역 변수"로, 이름이 같아도 완전히 다른 것입니다.

✔ 확인 기준: python chat_server/class_preview.py 실행 후 접속자 수가 1 → 2 → 1 흐름으로 출력되면 완료. 실패하면 self.clients처럼 self.를 빠뜨리지 않았는지, server.add_client("user1")처럼 객체를 통해 호출했는지 확인하세요.

- 함수와 전역 변수만으로는 코드가 커질수록 관리가 어렵다
- 클래스는 관련된 값과 기능을 역할별로 묶는 도구다
- 서버 → ChatServer / 클라이언트 → ChatClient / 화면 → ChatWindow
- GUI에서는 화면 코드와 네트워크 코드를 반드시 분리해야 한다
- self가 없으면 Python이 전역 변수와 객체 속성을 구분하지 못한다

→ 다음 강의 (20강): 터미널 채팅의 input()·print()가 GUI 이벤트와 어떻게 연결되는지 코드 수준으로 정리합니다. 이번 강의에서 정한 ChatServer / ChatClient 역할 분리가 실제 연결 코드 안에서 어떻게 살아 있는지 확인합니다.