14강 개념 중심 ⏱ 약 25분

 

0. 학습 목표

→ 이번 글에서 무엇을 이해하고, 무엇을 정리하고, 무엇을 확인할지 먼저 정리합니다.

더보기

0.1 이번 글에서 다룰 내용

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

 

13강까지 만든 채팅 서버는 문자열 메시지를 주고받을 수 있습니다. 그런데 채팅 프로그램에 기능이 하나씩 더해지면 서버는 메시지의 의미를 구분해야 합니다.

"안녕하세요"              ← 일반 채팅인가?
"exit"                   ← 종료 명령인가? 아니면 'exit'이라는 단어를 채팅한 건가?
"/w 민수 안녕"            ← 귓속말인가? 서버가 이 규칙을 어떻게 아는가?
"[ERROR] 없는 사용자"     ← 오류 메시지인가?

 

사람은 문장을 보고 의미를 추측할 수 있지만, 프로그램은 명확한 규칙이 없으면 이 메시지가 일반 채팅인지, 명령어인지, 귓속말인지 안정적으로 판단하기 어렵습니다. 그래서 메시지 규칙이 필요합니다. 네트워크 프로그램에서 이런 규칙을 프로토콜이라고 부릅니다.

 

이번 강의에서는 코드를 새로 작성하지 않습니다. 대신 "왜 규칙이 필요한가"를 충분히 이해하는 데 집중합니다. 규칙을 먼저 이해해야 15강에서 JSON 코드로 구현할 때 "이게 왜 이렇게 생겼지?" 하는 의문 없이 따라올 수 있습니다.

구분 내용
이해할 것 단순 문자열만으로는 메시지의 의미를 안정적으로 구분하기 어렵다는 것
정리할 것 메시지 타입, 보낸 사람, 받는 사람, 본문 같은 기본 규칙
확인할 것 어떤 메시지에 어떤 정보가 필요한지 판단할 수 있는지

 

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

이번 강의는 개념 중심이므로 기존 파일을 직접 수정하지 않습니다.

chat_server/
└── server.py      ← 이번 강의에서 수정하지 않음

chat_client/
└── client.py      ← 이번 강의에서 수정하지 않음

 

이번 강의에서 이해할 메시지 구조의 변화 방향입니다. 이번 강의에서는 JSON 코드를 완성하지 않습니다.

현재 방식 (13강까지):
"안녕하세요"

15강에서 발전할 방식:
{
    "type": "chat",
    "sender": "user1",
    "content": "안녕하세요"
}

 

1. 단순 문자열의 한계 이해하기

→ 지금까지 사용한 문자열 방식이 왜 한계에 부딪히는지 확인합니다.

더보기

1.1 지금까지의 메시지는 단순 문자열이었다

13강의 서버는 클라이언트가 보낸 메시지를 문자열로 받아 다른 클라이언트에게 전달했습니다.

data = client_socket.recv(1024)
message = data.decode("utf-8")
broadcast_message = f"{client_address}: {message}"
broadcast(broadcast_message, client_socket)

 

이 구조에서 한 가지 문제를 생각해 봅니다. 사용자가 채팅으로 "exit"이라는 단어를 보내고 싶다면 어떻게 될까요?

if message == "exit":
    break   # 종료 명령으로 처리되어 버림

 

서버는 문자열 내용을 직접 비교해 종료를 처리합니다. 사용자가 채팅 내용으로 "exit"을 보냈더라도 서버는 그것을 명령으로 받아들입니다. 메시지의 의미내용이 분리되어 있지 않기 때문입니다. 규칙이 없어서 종료되는 것이지, exit을 보내면 무조건 종료되어야 하는 것이 아닙니다.

 

기능이 늘어날수록 이런 충돌이 잦아집니다. 귓속말을 위해 "/w 이름 내용" 형식을 쓰거나, 시스템 메시지를 "[SYSTEM]"으로 시작하게 만들어도 사용자가 그 문자열을 그냥 채팅으로 보내면 구분할 방법이 없습니다.

 

1.2 메시지 규칙이란 무엇인가

메시지 규칙은 서버와 클라이언트가 서로 약속한 데이터 형식입니다. 예를 들어 다음처럼 약속할 수 있습니다.

모든 메시지는 type을 가진다
일반 채팅은 type이 "chat"이다
입장 알림은 type이 "join"이다
퇴장 알림은 type이 "leave"이다
귓속말은 type이 "whisper"이다
오류 메시지는 type이 "error"이다

 

이렇게 규칙을 정하면 서버는 문자열 내용을 추측하지 않고 type을 보고 처리할 수 있습니다.

type이 "chat"    → 전체 채팅으로 전달
type이 "whisper" → 특정 사용자에게만 전달
type이 "error"   → 오류 메시지로 표시
type이 "leave"   → 연결 종료 처리

 

그리고 사용자가 채팅으로 "exit"이라는 단어를 보내고 싶다면 다음처럼 표현할 수 있습니다. type"chat"이므로 서버는 content 안에 "exit"이 있어도 종료 명령으로 처리하지 않습니다.

{"type": "chat", "content": "exit"}

 

1.3 프로토콜이라는 말 이해하기

프로토콜은 통신할 때 지키는 규칙입니다. HTTP(웹), SMTP(이메일) 같은 것도 모두 프로토콜입니다. 지금 우리 프로젝트에서는 다음 정도로 이해하면 충분합니다.

프로토콜 = 서버와 클라이언트가 메시지를 어떤 모양으로 주고받을지 정한 약속

 

채팅 프로젝트에서 프로토콜은 이런 질문에 답합니다.

질문 항목 예시
이 메시지는 어떤 종류인가? type chat, join, leave, whisper, error
누가 보냈는가? sender user1
누구에게 보내는가? target user2, all
실제 내용은 무엇인가? content 안녕하세요

✔ 확인 기준: "메시지 규칙은 서버와 클라이언트가 메시지 모양을 미리 약속하는 것이며, type 항목으로 메시지 종류를 구분한다"라고 설명할 수 있으면 완료. 프로토콜이 HTTP나 TCP처럼 거창한 것만을 의미한다고 생각하고 있지 않은지 확인하세요. 우리 프로젝트에서는 "메시지 모양 약속" 수준으로 이해하면 됩니다.

 

2. 비유와 비교로 차이 확인하기

→ 편지 봉투 비유로 두 방식의 차이를 이해하고, 실제 서비스에서의 예를 확인합니다.

더보기

2.1 편지 봉투 비유로 이해하기

단순 문자열 방식은 봉투 없이 종이쪽지만 던지는 것과 같습니다.

내용이 일반 메시지인지, 명령인지, 오류인지 종이 내용을 읽어봐야만 알 수 있습니다.

내용이 복잡해질수록 받는 쪽이 해석하기 어려워집니다.

 

규칙 있는 메시지 방식은 봉투에 "수신자", "종류", "발신자"를 적어 보내는 것과 같습니다.

봉투를 열기 전에 이미 "이건 귓속말이고, 민수에게 보내는 것"을 알 수 있습니다.

비유 방식 서버 처리
봉투 없는 쪽지 단순 문자열 내용을 읽어 의미를 추측해야 함
봉투 있는 편지 규칙 있는 메시지 봉투(type)만 보고 처리 방법 결정

 

2.2 단순 문자열 방식과 규칙 있는 방식 비교

구분 단순 문자열 방식 규칙 있는 메시지 방식
예시 "안녕하세요" {"type": "chat", "content": "안녕하세요"}
서버 처리 문자열 내용을 직접 비교 type 항목을 먼저 확인
기능 추가 if문이 계속 늘어남 type 하나 추가로 처리
"exit" 채팅 종료 명령으로 처리됨 type"chat"이면 채팅으로 처리
최종 프로젝트 적합성 낮음 높음

 

2.3 실제 서비스에서의 예

우리가 매일 쓰는 서비스에서도 같은 방식을 사용합니다. 규모는 다르지만 "메시지에 종류 정보를 담아 처리 방법을 구분한다"는 원리는 동일합니다.

서비스 메시지 규칙 예시
카카오톡 일반 메시지, 이모티콘, 파일, 읽음 확인이 모두 다른 type으로 구분됨
유튜브 라이브 채팅 일반 채팅, 슈퍼챗, 멤버 메시지가 type으로 구분되어 화면에 다르게 표시
온라인 게임 이동, 공격, 채팅, 아이템 사용이 각각 다른 패킷 구조로 전달됨

✔ 확인 기준: 두 방식의 차이를 "type으로 구분할 수 있느냐 없느냐"로 설명할 수 있으면 완료. 단순 문자열 방식이 틀린 방법이라고 생각하지 않는지 확인하세요. 기초 단계에서는 좋은 출발점이었고, 이제 확장하는 것입니다.

 

3. 메시지 구조 판단 기준 정리하기

→ 메시지에 어떤 정보가 필요한지, 종류별로 어떻게 다른지 정리합니다.

더보기

3.1 메시지에 들어가야 할 기본 정보

채팅 메시지를 안정적으로 처리하려면 최소한 다음 정보가 필요합니다.

항목 의미 예시
type 메시지 종류 chat, join, leave, error, whisper
sender 보낸 사람 user1
target 받는 사람 user2, all
content 실제 메시지 내용 안녕하세요

모든 메시지가 항상 모든 항목을 가져야 하는 것은 아닙니다. 전체 채팅은 target이 없어도 되지만, 귓속말은 누구에게 보낼지 알아야 하므로 target이 필요합니다.

 

3.2 메시지 종류별 필요한 정보

메시지 종류 필요한 항목 설명
일반 채팅 type, sender, content 모두에게 전달할 메시지
입장 알림 type, sender 누가 들어왔는지 알림
퇴장 알림 type, sender 누가 나갔는지 알림
귓속말 type, sender, target, content 특정 사용자에게만 전달
오류 메시지 type, content 잘못된 요청이나 실패 이유 전달
파일 전송 type, sender, filename, size 파일 정보 전달

✔ 확인 기준: 귓속말과 일반 채팅에 필요한 항목이 왜 다른지 설명할 수 있고, 어떤 메시지에 target이 필요한지 판단할 수 있으면 완료. 모든 메시지에 항상 같은 항목이 있어야 한다고 생각하지 않는지 확인하세요. 종류마다 필요한 항목이 다릅니다.

 

4. 귓속말 예시로 차이 확인하기

→ 문자열 방식과 규칙 있는 방식으로 귓속말을 처리하는 예시를 나란히 비교합니다.

더보기

4.1 단순 문자열만 사용할 때의 문제

문자열만으로 귓속말을 표현하면 서버가 문자열을 직접 분석해야 합니다. 아래는 의사코드입니다.

message = "/w user2 안녕하세요"

if message.startswith("/w"):
    parts = message.split(" ", 2)
    target = parts[1]     # "user2"
    content = parts[2]    # "안녕하세요"
    print(f"{target}에게 귓속말: {content}")

이 방식의 문제입니다.

띄어쓰기가 잘못되면 오류가 난다
   /w user2안녕하세요  → parts[2] 오류

명령어가 많아질수록 if문이 늘어난다
   /w → 귓속말
   /exit → 종료
   /name → 이름 변경
   ...

메시지 내용에 /w가 포함되면 명령어로 처리된다
   "/w를 입력하면 귓속말이 됩니다" → 귓속말로 처리됨

실제로 많은 초급자가 이 방식으로 명령어를 처리하다가 예외 상황이 생길 때마다 if문을 하나씩 추가하게 됩니다. 코드가 30줄, 50줄이 됩니다. 나중에는 어떤 if가 어떤 경우를 처리하는지 본인도 헷갈립니다. 프로토콜을 정의하는 이유가 이것입니다. 규칙을 먼저 정하면 예외 상황을 미리 막을 수 있습니다.

 

4.2 규칙 있는 구조로 바꿔 보기

같은 귓속말을 규칙 있는 구조로 표현하면 다음과 같습니다. 아래는 의사코드입니다.

message = {
    "type": "whisper",
    "sender": "user1",
    "target": "user2",
    "content": "안녕하세요"
}

if message["type"] == "whisper":
    target = message["target"]
    content = message["content"]
    print(f"{target}에게 귓속말: {content}")
user2에게 귓속말: 안녕하세요

type, target, content를 각각 꺼내면 되므로 메시지 종류가 늘어나도 처리 방식이 일정합니다. 서버는 문자열을 억지로 분리하지 않아도 됩니다.

비교 단순 문자열 규칙 있는 구조
귓속말 표현 "/w user2 안녕하세요" {"type": "whisper", "target": "user2", "content": "안녕하세요"}
서버 처리 message.startswith("/w") message["type"] == "whisper"
띄어쓰기 오류 오류 발생 가능 영향 없음
명령어 추가 if문 추가 type 항목 하나 추가

✔ 확인 기준: type 값을 보면 메시지 종류를 바로 알 수 있고, 문자열 내용을 직접 추측하지 않고 정해진 항목으로 처리할 수 있다는 것을 설명할 수 있으면 완료. 규칙 있는 구조가 처음에는 길어 보여도 기능이 늘어날수록 오히려 정리된다는 것을 이해하고 있는지 확인하세요.

 

5. 다음 강의로 이어지는 부분

→ 이번 강의에서 이해한 내용을 정리하고 15강에서 구현할 JSON 메시지 구조를 예고합니다.

더보기

메시지 규칙에 대한 자주 하는 오해를 먼저 정리합니다.

오해 올바른 정리
문자열만으로도 충분하다 일반 채팅 이상으로 확장하려면 규칙이 필요하다. 기능이 늘어나면 문자열 분석 코드가 점점 복잡해진다
프로토콜은 어렵고 거창한 것이다 이번 프로젝트에서는 "메시지 모양 약속" 정도로 이해하면 된다
exit을 보내면 무조건 종료된다 규칙이 없어서 종료되는 것이다. type으로 명령과 채팅을 구분하면 contentexit이 있어도 채팅으로 처리할 수 있다
모든 메시지에 같은 항목이 있어야 한다 메시지 종류마다 필요한 정보가 다르다. 공통 항목(type)은 있되, 선택 항목(target 등)은 필요할 때만 포함하면 된다
규칙을 정하면 코드가 무조건 복잡해진다 처음에는 길어 보이지만 기능이 늘어날수록 처리 기준이 명확해져 코드가 오히려 정리된다

 

이번 강의에서 이해한 핵심을 한 문장으로 정리하면 다음과 같습니다.

메시지에 종류(type)·보낸 사람(sender)·받는 사람(target)·내용(content) 같은
규칙을 만들면, 서버는 문자열 내용을 추측하지 않고 type만 보고 처리할 수 있다.

 

이 개념을 코드로 표현하는 가장 대표적인 방법이 JSON입니다.

Python에서 딕셔너리로 메시지를 만들고, JSON 형식으로 변환해 소켓으로 주고받는 구조를 15강에서 구현합니다.

 

이번 강의에서 server.pyclient.py를 바로 수정하지 않는 이유도 여기에 있습니다. "왜 규칙이 필요한가"와 "JSON을 어떻게 만드는가"를 동시에 배우면 한꺼번에 너무 많은 내용이 등장합니다. 이번 강의에서 첫 번째 질문에 집중했으니, 다음 강의에서 두 번째 질문을 코드로 구현합니다.

# 15강에서 만들 메시지 구조 예시
{
    "type": "chat",
    "sender": "user1",
    "content": "안녕하세요"
}

→ 다음 강의 (15강): Python의 json 모듈로 딕셔너리를 JSON 문자열로 변환하고, 소켓으로 주고받는 방법을 배웁니다. 이번 강의에서 이해한 type, sender, content 구조가 실제 코드로 구현됩니다.