15강. JSON 기반 메시지 타입 설계

0. 학습 목표
→ 이번 글에서 무엇을 이해하고, 무엇을 설계하고, 무엇을 확인할지 먼저 정리합니다.
0.1 이번 글에서 다룰 내용
이번 글은 개념 중심 강의입니다.
14강에서 단순 문자열만으로는 메시지의 의미를 안정적으로 구분하기 어렵다는 것을 이해했습니다.
메시지에 종류·보낸 사람·받는 사람·내용을 담는 규칙이 필요하다는 것도 정리했습니다.
이번 강의에서는 그 규칙을 JSON 구조로 구체화합니다.
일반 채팅, 입장 알림, 퇴장 알림, 귓속말, 오류 메시지를 모두 다음처럼 종류와 내용이 분리된 구조로 표현합니다.
{
"type": "chat",
"sender": "user1",
"content": "안녕하세요"
}
이번 강의에서는 아직 코드를 실행하지 않습니다. 16강에서 json.dumps()와 json.loads()로 이 구조를 소켓 송수신 코드에 연결합니다. 이번 강의의 목표는 어떤 메시지가 어떤 구조를 가져야 하는지 설계하는 것입니다. 이 설계를 이해하고 있어야 16강의 코드가 "왜 이렇게 생겼지?" 없이 바로 읽힙니다.

| 구분 | 내용 |
| 이해할 것 | JSON 구조가 채팅 메시지에 적합한 이유 |
| 설계할 것 | type, sender, target, content 네 항목으로 메시지 종류별 구조 정의 |
| 확인할 것 | 메시지 종류를 type으로 구분할 수 있는지, 종류마다 필요한 항목이 다름을 판단할 수 있는지 |
0.2 앞으로 만들 프로젝트 구조 미리보기
이번 강의는 개념과 설계 중심이므로 기존 파일을 직접 수정하지 않습니다.
chat_server/
└── server.py ← 이번 강의에서 수정하지 않음
chat_client/
└── client.py ← 이번 강의에서 수정하지 않음
이번 강의에서 설계할 메시지 구조가 이후 모든 기능의 공통 기반이 됩니다.
현재 (13강까지):
클라이언트 → "안녕하세요" → 서버
16강 이후:
클라이언트 → {"type": "chat", "sender": "user1", "content": "안녕하세요"} → 서버
1. JSON과 Python 딕셔너리 이해하기
→ JSON이 무엇인지, 왜 채팅 메시지 구조에 적합한지 이해합니다.
1.1 JSON과 Python 딕셔너리의 관계
JSON(JavaScript Object Notation)은 데이터를 구조적으로 표현하는 형식입니다. Python 딕셔너리와 모양이 매우 비슷합니다.
# Python 딕셔너리
message = {
"type": "chat",
"sender": "user1",
"content": "안녕하세요"
}
JSON 형식 (네트워크로 주고받는 형태)
{
"type": "chat",
"sender": "user1",
"content": "안녕하세요"
}
두 형태는 생김새가 거의 같습니다. Python에서는 딕셔너리를 JSON 문자열로 변환해 소켓으로 보내고, 받은 JSON 문자열을 다시 딕셔너리로 되돌려 처리합니다. 이 변환은 16강에서 json.dumps()와 json.loads()로 구현합니다.
| 상황 | 형태 | 함수 |
| 보낼 때 | Python 딕셔너리 → JSON 문자열 | json.dumps() (16강) |
| 받을 때 | JSON 문자열 → Python 딕셔너리 | json.loads() (16강) |
1.2 서버 입장에서 JSON이 편한 이유
문자열 방식과 JSON 방식의 서버 처리 코드를 비교하면 차이가 명확합니다. 아래는 개념 설명용 의사코드입니다.
# 문자열 방식 — 문자열을 직접 잘라서 분석
if message.startswith("/w"):
parts = message.split(" ", 2)
target = parts[1] # 분리 규칙이 맞아야 동작
content = parts[2]
# JSON 방식 — 항목 이름으로 바로 꺼내서 사용
if message["type"] == "whisper":
target = message["target"] # 항목 이름으로 직접 접근
content = message["content"]
JSON 방식은 메시지 종류가 늘어나도 처리 방식이 일정합니다. 새로운 메시지 타입이 생겨도 type 값 하나만 추가하면 됩니다. JSON은 Python에서만 쓰이는 형식이 아닙니다. 언어에 무관한 범용 데이터 형식이고, Python 딕셔너리와 비슷할 뿐 같은 것은 아닙니다.
✔ 확인 기준: "JSON은 Python 딕셔너리와 모양이 비슷하며, type 항목으로 메시지 종류를 바로 구분할 수 있다"라고 설명할 수 있으면 완료.
2. 기본 메시지 구조 설계하기
→ 레시피 카드 비유로 메시지 구조의 역할을 이해하고, 네 항목으로 기본 구조를 설계합니다.
2.1 레시피 카드로 비유하기
메시지 구조를 레시피 카드로 비유하면 이해하기 쉽습니다. 레시피 카드에는 "요리 종류", "재료", "분량", "만드는 방법"처럼 항목이 나뉘어 있습니다. 요리사는 카드를 보고 어떤 요리를 어떻게 만들지 바로 알 수 있습니다. "재료 목록"에 "볶음밥"이라고 적혀 있다고 해서 그게 요리 종류가 되는 것이 아닙니다. 항목이 분리되어 있기 때문에 혼동이 없습니다.
메시지 구조도 같습니다. type에 "chat"이라고 적혀 있으면 서버는 "이것은 전체 채팅이다"라는 것을 바로 알 수 있습니다. content에 "exit"이 들어 있어도 type이 "chat"이면 종료 명령으로 처리하지 않습니다.
| 레시피 카드 항목 | 메시지 구조 항목 | 역할 |
| 요리 종류 | type |
서버가 처리 방법 결정 |
| 주 재료 (누가) | sender |
보낸 사람 식별 |
| 대상 (누구를 위해) | target |
받는 사람 지정 (필요할 때만) |
| 만드는 방법 (내용) | content |
실제 전달할 데이터 |
2.2 기본 메시지 구조 설계하기
앞으로 사용할 기본 메시지 구조입니다.
# 기본 메시지 구조 — 모든 메시지가 이 형태를 기반으로 한다
{
"type": "", # 메시지 종류 — 서버가 이 값을 보고 처리 방식 결정
"sender": "", # 보낸 사람
"target": "", # 받는 사람 (귓속말 등 필요한 경우에만)
"content": "" # 실제 내용
}
| 항목 | 의미 | 설명 |
type |
메시지 종류 | 서버가 이 값을 보고 처리 방식을 결정한다. 항상 포함 |
sender |
보낸 사람 | 누가 보냈는지 표시할 때 사용한다 |
target |
받는 사람 | 귓속말처럼 특정 사용자에게 보낼 때만 포함한다 |
content |
실제 내용 | 채팅 메시지, 오류 설명 등 실제 데이터 |
모든 메시지가 항상 네 항목을 모두 가져야 하는 것은 아닙니다. type은 항상 포함하고, 나머지는 메시지 종류에 따라 필요한 것만 포함합니다.
2.3 서버 처리 흐름
이 구조가 완성되면 서버는 다음처럼 동작합니다.
메시지 수신
↓
JSON 파싱 (json.loads — 16강에서 구현)
↓
type 확인
↓
"chat" → 전체 채팅으로 브로드캐스팅
"whisper" → 특정 사용자에게만 전달
"join" → 입장 알림 브로드캐스팅
"leave" → 퇴장 처리 + 목록에서 제거
"error" → 오류 메시지 전송
✔ 확인 기준: 기본 구조의 네 항목(type, sender, target, content)이 각각 어떤 역할을 하는지 설명할 수 있으면 완료. target이 모든 메시지에 항상 있어야 한다고 생각하지 않는지 확인하세요. 귓속말처럼 특정 사람에게 보낼 때만 포함합니다.
3. 메시지 타입별 구조 설계하기
→ 메시지 타입별 JSON 구조를 설계하고, 종류마다 어떤 항목이 필요한지 정리합니다.
3.1 메시지 타입별 JSON 구조
아래는 각 메시지 타입의 설계 예시입니다. 이번 강의에서는 실행하지 않습니다.
일반 채팅 (chat) — 전체 채팅방에 전달됩니다.
{"type": "chat", "sender": "user1", "content": "안녕하세요"}
입장 알림 (join) — 서버가 생성해서 전체 사용자에게 보냅니다.
{"type": "join", "sender": "user1"}
퇴장 알림 (leave) — 사용자가 나갈 때 사용합니다.
{"type": "leave", "sender": "user1"}
귓속말 (whisper) — 특정 사용자에게만 전달합니다. target이 필요한 유일한 타입입니다.
{"type": "whisper", "sender": "user1", "target": "user2", "content": "비밀 메시지"}
오류 메시지 (error) — 서버가 클라이언트에게 보냅니다. sender가 서버이므로 생략합니다.
{"type": "error", "content": "존재하지 않는 사용자입니다."}
파일 전송 (file) — 이번 강의에서는 구현하지 않습니다. 나중에 기능을 추가할 때 확장합니다.
{"type": "file", "sender": "user1", "filename": "report.pdf", "size": 102400}
타입별로 항목이 다른 것이 처음에는 복잡하게 느껴질 수 있습니다.
그런데 이것이 오히려 장점입니다. 귓속말에는 target이 필요하고, 입장 알림에는 필요 없다는 것이 구조에서 바로 드러납니다. 문자열 방식에서는 이 차이가 코드 안에 숨어 있어 나중에 버그를 찾기 어렵습니다. 타입별 구조를 명시적으로 설계해 두면, 새로운 팀원이 코드를 볼 때도 "이 메시지에는 어떤 항목이 있는지"를 코드 없이도 파악할 수 있습니다.
3.2 타입별 필요한 항목 한눈에 보기
type |
sender |
target |
content |
설명 |
chat |
필요 | — | 필요 | 전체 채팅 메시지 |
join |
필요 | — | — | 입장 알림 |
leave |
필요 | — | — | 퇴장 알림 |
whisper |
필요 | 필요 | 필요 | 특정 사용자에게만 전달 |
error |
— | — | 필요 | 오류 메시지 |
✔ 확인 기준: whisper와 chat에 필요한 항목이 왜 다른지 설명할 수 있고, 어떤 타입에 target이 필요한지 판단할 수 있으면 완료. error 타입에 sender가 없는 이유도 설명할 수 있는지 확인하세요. 오류 메시지는 서버가 생성하기 때문에 sender를 별도로 표시하지 않습니다.
4. 두 방식 코드 비교하기
→ 문자열 방식과 JSON 방식을 귓속말 예시로 직접 비교해 차이를 확인합니다.
4.1 문자열 방식
문자열을 직접 잘라서 분석해야 합니다. 아래는 개념 설명용 의사코드입니다.
message = "/w user2 안녕하세요"
if message.startswith("/w"):
parts = message.split(" ", 2)
target = parts[1]
content = parts[2]
print(f"{target}에게 귓속말: {content}")
4.2 JSON 방식
항목 이름으로 바로 꺼내서 사용합니다. 아래는 개념 설명용 의사코드입니다.
message = {
"type": "whisper",
"sender": "user1",
"target": "user2",
"content": "안녕하세요"
}
if message["type"] == "whisper":
target = message["target"]
content = message["content"]
print(f"{target}에게 귓속말: {content}")
user2에게 귓속말: 안녕하세요
4.3 두 방식 비교 정리
| 항목 | 문자열 방식 | JSON 방식 |
| 메시지 구분 | 문자열 내용을 직접 분석 | type 값 확인 |
| 기능 추가 | if문과 분석 규칙 추가 |
type 값 하나 추가 |
| 오류 가능성 | 띄어쓰기·순서 실수에 취약 | 항목 이름으로 접근하므로 안정적 |
| 유지보수 | 어려움 | 쉬움 |
| 최종 프로젝트 적합성 | 낮음 | 높음 |
✔ 확인 기준: JSON 방식에서 type 값만 바꾸면 다른 메시지 종류를 처리할 수 있다는 점을 설명할 수 있으면 완료. JSON 방식이 처음에는 길어 보이지만 기능이 늘어날수록 오히려 코드가 정리된다는 것을 이해하고 있는지 확인하세요.
5. 다음 강의로 이어지는 부분
→ 이번 강의에서 설계한 메시지 구조를 정리하고 16강에서 구현할 내용을 예고합니다.
JSON 메시지 구조 설계에서 자주 하는 오해를 먼저 정리합니다.
| 오해 | 올바른 정리 |
| JSON은 Python 딕셔너리와 같다 | 모양이 비슷하지만 다른 형식이다. Python 딕셔너리는 코드 안에서, JSON은 네트워크로 주고받을 때 사용한다. 변환은 json.dumps()와 json.loads()로 한다 |
| 모든 메시지에 항상 같은 항목이 있어야 한다 | type은 항상 포함하지만, target처럼 필요할 때만 포함하는 항목이 있다 |
type이 있으면 다른 항목은 필요 없다 |
type은 처리 방식을 결정하는 항목이고, 실제 내용(content)·보낸 사람(sender)은 별도로 필요하다 |
| JSON 방식이 처음부터 더 좋다 | 기능이 하나뿐인 초기 단계에서는 문자열 방식이 이해하기 쉬웠다. 기능이 늘어나는 단계에서 JSON 방식이 더 적합해진다 |
| 이번 강의에서 바로 코드를 실행할 수 있다 | 이번 강의는 설계 단계다. json.dumps()와 json.loads()를 사용해 실제로 소켓으로 주고받는 코드는 16강에서 구현한다 |
이번 강의에서 설계한 메시지 구조를 한곳에 정리합니다.
# 공통 기본 구조
{
"type": "", # 항상 포함 — 서버가 처리 방식 결정
"sender": "", # 보낸 사람
"target": "", # 받는 사람 (whisper 등 필요한 경우만)
"content": "" # 실제 내용
}
# 타입별 설계 정리
{"type": "chat", "sender": "user1", "content": "안녕하세요"}
{"type": "join", "sender": "user1"}
{"type": "leave", "sender": "user1"}
{"type": "whisper", "sender": "user1", "target": "user2", "content": "비밀 메시지"}
{"type": "error", "content": "존재하지 않는 사용자입니다."}
16강에서는 Python의 json 모듈을 사용해 이 구조를 실제 네트워크 송수신 코드로 구현합니다.
Python 딕셔너리
↓ json.dumps()
JSON 문자열
↓ 소켓으로 전송
JSON 문자열
↓ json.loads()
Python 딕셔너리
→ 다음 강의 (16강): import json으로 json.dumps()와 json.loads()를 배우고, 이번 강의에서 설계한 메시지 구조를 실제 chat_server/server.py와 chat_client/client.py에 적용합니다.