- 개요, 조사(BGA·한국 인기 보드게임·UI/UX), 아키텍처, 실시간 프로토콜, 안정성·보안, 계정, 로비·방, UI/UX, 데이터 모델, 게임 엔진, 테스트, 배포·운영, 로드맵(M0~M9), 사용자 결정 항목 - 게임별 규칙·엔진 설계·UI·테스트 체크리스트 26종 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8.2 KiB
8.2 KiB
03. 실시간 통신 프로토콜
1. 원칙
- 연결은 사용자당 브라우저 탭 하나에 WebSocket 하나(
wss://<도메인>/ws). - 서버가 유일한 진실. 클라이언트는 행동을 "요청"만 하고, 화면은 서버가 보낸 상태로만 그린다.
- 서버는 매번 그 사람이 볼 수 있는 전체 화면 상태(view) 를 보낸다(차이(diff) 전송 안 함). 보드게임 상태는 작아서(대부분 2~10KB) 전체 전송이 단순하고 안전하다. 메시지를 잃어버려도 다음 메시지 하나로 화면이 완전히 맞춰진다.
- 애니메이션용
events는 덤이다. 놓쳐도 상태는 맞다. - 프로토콜 버전
PROTOCOL = 1. 서버와 버전이 다르면 클라이언트는 새로고침 안내를 띄운다(배포 직후 대비).
2. 연결과 인증
- 클라이언트가
/ws로 업그레이드 요청. 브라우저가 세션 쿠키(sid, httpOnly)를 같이 보낸다. - 서버는 업그레이드 단계에서 세션을 확인하고, 없으면 401로 거절(클라이언트는 닉네임 화면으로).
Origin헤더가 우리 도메인이 아니면 거절(CSWSH 방지).- 연결되면 서버가
welcome을 보낸다. 사용자가 이미 들어가 있는 방이 있으면welcome.activeRoom에 코드를 넣어 준다(→ "진행 중인 게임으로 돌아가기").
3. 메시지 형식
모든 메시지는 JSON 객체이며 t(type) 필드를 가진다. 모든 클라이언트 메시지는 packages/shared의 zod 스키마로 검사한다.
3.1 클라이언트 → 서버
| t | 필드 | 설명 |
|---|---|---|
join |
code, as: "player"|"spectator" |
방 입장. 이미 자리 있는 방이면 재입장 처리 |
leave |
— | 방 나가기(게임 중이면 확인 후, 기권 처리 규칙은 6절) |
seat |
seat?: number |
자리에 앉기/자리 바꾸기(대기실에서만) |
unseat |
— | 관전자로 일어나기(대기실에서만) |
ready |
ready: boolean |
준비 |
config |
gameId?, options?, maxPlayers?, visibility?, allowSpectators?, chatEnabled?, chatFilter? |
방장: 게임/옵션/방 설정 변경(대기실·게임 끝난 뒤에만) |
start |
— | 방장: 시작 |
kick |
userId |
방장: 내보내기 |
host |
userId |
방장 넘기기 |
act |
cs: number, a: Action |
게임 행동. cs는 클라이언트 행동 번호(1부터 증가) |
chat |
text(1~200자) |
채팅 |
emote |
id |
빠른 반응(👍, 😂, "잘했어요!" 등 고정 목록) |
rematch |
— | 게임 끝난 뒤 "한 판 더" 투표 |
sync |
— | 전체 상태 다시 요청 |
ping |
ts |
지연 측정(10초마다) |
3.2 서버 → 클라이언트
| t | 필드 | 설명 |
|---|---|---|
welcome |
me, serverTime, protocol, activeRoom? |
연결 직후 |
room |
seq, room: RoomView |
대기실/자리/옵션/접속 상태 전체 |
state |
seq, view, events, active: userId[], deadline: number|null, cs? |
게임 상태. cs는 이 상태를 만든 행동이 내 것일 때만 포함(= 성공 응답) |
reject |
cs, reason |
내 행동 거절(한국어 이유). 화면은 바꾸지 않고 안내만 표시 |
chat |
from, text, at, channel? |
채팅(마피아 등은 채널 구분) |
emote |
from, id |
반응 |
result |
seq, result |
게임 결과 |
notice |
code, message |
"OO님이 연결이 끊겼어요" 같은 알림 |
error |
code, message |
잘못된 요청, 방 없음 등 |
pong |
ts, serverTime |
지연·시계 보정 |
bye |
reason |
서버 종료 예정(restart), 다른 창에서 접속(replaced), 강퇴(kicked) |
4. 순서 보장 (seq)
- 방마다
seq(정수)가 있고, 방에서 무엇이든 바뀌면 1 증가한다(자리, 옵션, 게임 행동 모두). - 클라이언트는 받은
seq가 가지고 있는 값보다 작거나 같으면 버린다(늦게 도착한 옛 메시지 무시). seq가 건너뛰어도(중간 메시지 누락) 전체 상태이므로 그대로 적용한다. 단events애니메이션은 건너뛴다.
5. 행동 처리와 중복 방지
- 클라이언트는
act를 보내면 해당 버튼을 잠그고 "처리 중" 표시(150ms 이상 걸릴 때만 보이게). - 서버는
(userId, room, cs)를 기억해 같은cs가 다시 오면 무시한다(재연결 직후 재전송 대비). 재연결 시 클라이언트는 응답 못 받은 행동을 다시 보내지 않고sync로 상태를 받은 뒤 사용자가 다시 판단하게 한다(이미 처리됐을 수 있으므로). - 낙관적 업데이트는 하지 않는다(숨겨진 정보 게임에서 오히려 혼란). 예외: 루미큐브 타일 정렬, 그림 맞히기 붓질처럼 "내 화면 안에서만의 조작"은 즉시 그린다.
- 2초 안에 응답이 없으면 "연결 확인 중" 표시 후
ping으로 연결 점검.
6. 연결 끊김과 재접속
6.1 클라이언트
- 끊기면 자동 재연결: 0.5s → 1s → 2s → 4s → 8s → 이후 10s 간격, 각 ±30% 무작위 지연(서버 재시작 시 몰림 방지).
- 화면 위에 작은 띠로 "다시 연결하는 중…" 표시. 게임 화면은 그대로 두되 조작은 잠근다.
- 탭이 다시 보이거나(
visibilitychange) 네트워크가 돌아오면(online) 즉시 재시도. - 다시 연결되면 마지막 방에 자동
join→ 서버가room+state전체를 보냄.
6.2 서버
- 연결이 끊긴 플레이어는 자리를 유지하고 다른 사람에게 "연결 끊김" 표시.
- 턴 타이머는 그대로 흐른다. 끊긴 사람의 차례에 시간이 다 되면 게임의
onTimeout자동 행동(예: 포커 다이/체크, 오목 무작위 착수가 아니라 시간패 등 게임별 문서에 정의). - 끊긴 지 60초(방 옵션 30~300초)가 지나면 "자리 비움" 상태: 자기 차례가 오면 기다리지 않고 바로 자동 행동.
- 자동 행동이 연속 3번이거나 끊긴 지 5분이 지나면 방장에게 "내보내기" 버튼을 보여 준다. 2인 대전 게임은 상대에게 "승리로 끝내기" 선택권을 준다.
- 같은 사용자가 새 탭으로 같은 방에 들어오면 새 연결이 이기고, 옛 연결에는
bye: replaced.
7. 타이머와 시계
- 모든 마감 시각은 서버 시각(epoch ms)으로 보낸다.
- 클라이언트는
pong.serverTime과 왕복 시간으로 시계 차이를 추정(최근 5개 중간값)하고 남은 시간을 그린다. - 시간 판정은 서버만 한다. 서버 타이머는 방마다 하나(
setTimeout)이며, 서버 재시작 후에는 저장된deadline으로 다시 건다.
8. 하트비트·제한
| 항목 | 값 |
|---|---|
| 서버 idle timeout | 40초 (Bun idleTimeout), 서버가 25초마다 ping 프레임 |
| 클라이언트 ping | 10초마다, 5초 안에 pong이 없으면 연결을 끊고 재연결 |
| 메시지 최대 크기 | 16KB (그림 맞히기 붓질 메시지만 32KB) |
| 속도 제한 | 연결당 토큰 버킷 초당 20개, 최대 40개. 채팅은 0.5초에 1개. 넘으면 버리고 계속 넘으면 연결 종료 |
| 압축 | permessage-deflate 사용(1KB 이상 메시지만 효과) |
| 서버 송신 버퍼 | 연결당 1MB 넘게 밀리면(느린 클라이언트) 연결을 끊음 → 재연결 시 전체 상태로 회복 |
9. 실시간 경쟁 게임(할리갈리, 도블 등)
- 먼저 누른 사람은 서버 도착 순서로 정한다. 같은 시점에 판정 창을 열고, 첫 도착을 처리한 뒤 30ms 동안 들어온 같은 종류 행동은 "늦음"으로 알려 준다.
- 각자의 왕복 지연(RTT)을 화면 구석에 표시하고, 방 옵션으로 "지연 보정"(클라이언트가 누른 시각 − RTT/2로 순서 결정, 단 100ms 이내 차이만 보정)을 켤 수 있다. 기본은 꺼짐(조작 가능성 때문에). 세부는 각 게임 문서.
10. 서버 재시작 시
- 종료 신호(SIGTERM)를 받으면 새 연결을 받지 않고, 모든 연결에
bye: restart를 보낸 뒤 DB를 정리하고 종료(최대 10초). - 클라이언트는
restart를 받으면 "잠시 후 자동으로 다시 연결돼요" 표시 후 1~3초 무작위 대기 후 재연결. - 새 서버는 시작할 때 진행 중이던 방을 DB에서 복구(
04-stability-security.md3절)한 다음 연결을 받는다.