websocketbackend
실시간 게임 서버에서 배운 것 — 클라이언트를 믿지 마라
사이드로 실시간 파티 게임을 만들면서 제일 크게 배운 원칙 하나: 서버가 진실의 원천(source of truth)이어야 한다.
클라이언트 상태를 믿으면 생기는 일
처음 설계는 이랬다. 클라이언트가 "내 차례에 힌트 제출" 이벤트를 보내면 서버는 그대로 브로드캐스트. 편했다. 그리고 devtools 열면 남의 차례에도 제출이 됐다.
실시간 게임에서 검증 없는 이벤트는 전부 취약점이다.
서버 권위 모델
모든 상태 전이를 서버의 순수 함수로 옮겼다.
export function submitHint(room: Room, playerId: string, text: string) {
if (room.phase !== "HINT") return { ok: false, error: "지금은 힌트 차례가 아니에요" };
const expected = room.round.turnOrder[room.round.turnIndex];
if (expected !== playerId) return { ok: false, error: "아직 당신 차례가 아니에요" };
// ...검증 통과 후에만 상태 변경
}
클라이언트는 얇게 — 서버가 보내주는 상태를 렌더만 한다. 역할 같은 비밀 정보는 브로드캐스트에서 빼고 개인 소켓으로만 보낸다.
// 공통 상태는 방 전체에
io.to(room.code).emit("room:state", publicState(room));
// 비밀(각자의 역할)은 개인에게만
socket.emit("role:assigned", roleFor(room, playerId));
게임 로직은 프레임워크 없이
상태머신을 소켓 핸들러에서 분리해 순수 함수로 두면 테스트가 이렇게 쉬워진다.
조회 1