웹소켓 처리를 위한 기본 개념 다시 정리
NestJS
서버 애플리케이션 프레임워크
클라이언트의 http 요청이나 웹소켓 이벤트를 처리하고, 비즈니스 로직을 실행하며, 여러 모듈과 서비스를 체계적으로 구성한 상태에서 db와 연결해 백엔드를 구성
Socket.io
클라이언트와 서버 간 실시간, 양방향 통신을 가능하게 한다.
Redis
레디스는 인메모리 데이터 저장소로, 매우 빠른 속도로 데이터를 읽고 쓸 수 있다.
(예: 방 정보, 플레이어 상태, 투표 기록, 역할 할당 등)
게임 진행 상황을 빠르게 업데이트하고, 여러 클라이언트에 최신 상태로 제공하는데 유용
Docker
도커는 애플리케이션을 컨테이너라는 독립된 환경에서 실행할 수 있게 해주는 도구로, 이 프로젝트에서는 redis 서버를 손 쉽게 실행하기 위해 사용. 시스템에 별도로 레디스를 설치하지 않아도, 컨테이너 안에서 레디스를 실행ㅇ시켜 개발 및 테스트 가능
서버 구조 도식화(임시)
- 클라이언트:
- 웹 브라우저에서 HTTP API 요청은 API 서버로, 실시간 게임 이벤트 처리는 웹소켓 연결을 통해 게임 서버로 전달
- EC2 인스턴스 내 서비스:
- API 서버 (NestJS):
- EC2 인스턴스에 직접 배포되어 HTTP API 요청(인증, 사용자 관리 등)을 처리하며, RDS 데이터베이스와 연동하여 영속 데이터를 관리
- 게임 서버 (도커 컨테이너):
- 도커 컨테이너로 운영되며, 실시간 게임 로직과 이벤트 처리를 담당
- Redis 서버를 구독하여 API 서버에서 발행하는 게임 시작 이벤트 등의 메시지를 받아 처리
- API 서버 (NestJS):
- RDS 데이터베이스:
- API 서버가 영속 데이터를 저장하고 관리하기 위해 사용하는 관계형 데이터베이스
- Redis 서버:
- API 서버가 게임 시작 이벤트와 같은 메시지를 발행하며, 게임 서버는 Redis를 구독하여 실시간 메시지를 전달
게임서버 관련 웹소켓 명세서(임시)
마피아 게임의 로직을 대략적으로 정리한 후 필요한 기능들을 정리하였다.
Event Name | Direction | Payload Example | Description / Notes |
CONNECT | Client → Server | json { "token": "사용자 인증 토큰", "userId": "사용자 아이디" } |
최초 연결 및 사용자 인증 요청. |
AUTH_SUCCESS | Server → Client | json { "userId": "사용자 아이디", "message": "인증 성공", "roomInfo": null } |
인증 성공 시 응답. (옵션: 현재 참여 중인 룸 정보 포함) |
AUTH_FAILURE | Server → Client | json { "errorCode": "AUTH_001", "message": "인증 실패. 토큰이 유효하지 않습니다." } |
인증 실패 시 응답. |
ROOM:JOIN | Client → Server | json { "roomId": "방 아이디", "userId": "사용자 아이디" } |
게임룸 입장 요청. 서버는 Redis의 룸 상태를 업데이트. |
ROOM:LEAVE | Client → Server | json { "roomId": "방 아이디", "userId": "사용자 아이디" } |
게임룸 퇴장 요청. 서버는 참가자 목록을 갱신 후 브로드캐스트. |
ROOM:UPDATE | Server → Clients (Broadcast) | json { "roomId": "방 아이디", "players": [ { "userId": "user1", "ready": false, "alive": true}, { "userId": "user2", "ready": true, "alive": true } ], "gamePhase": "waiting" } |
게임룸 상태(참가자, 준비 상태, 단계 등) 변경 시 전체 클라이언트에 전송. |
ROOM:MAX_PLAYERS_REACHED | Server → Clients | json { "roomId": "방 아이디", "message": "모든 플레이어가 입장했습니다. 10초 후 자동 게임 시작됩니다." } |
설정된 최대 인원(8명) 도달 시 알림. |
ROOM:COUNTDOWN_START | Server → Clients | json { "roomId": "방 아이디", "countdown": 10, "message": "게임 시작까지 10초 남았습니다." } |
최대 인원 도달 후 10초 카운트다운 시작 알림. |
TIMER:UPDATE | Server → Clients | json { "roomId": "방 아이디", "phase": "countdown", "remainingTime": 7, "message": "게임 시작까지 7초 남았습니다." } |
카운트다운 진행 중 남은 시간 업데이트 (주기적으로 전송). |
ROOM:GAME_START | Server → Clients | json { "roomId": "방 아이디", "phase": "role_assignment", "startTime": "ISO8601 타임스탬프", "message": "자동 게임 시작합니다. 역할을 분배합니다." } |
카운트다운 종료 후 자동 게임 시작. 역할 분배 단계로 전환됨. |
ROLE:ASSIGN | Server → 특정 클라이언트 (개별) | json { "roomId": "방 아이디", "userId": "수신자 아이디", "role": "mafia } |
citizen |
ROOM:DAY_START | Server → Clients | json { "roomId": "방 아이디", "phase": "day", "dayNumber": 1, "startTime": "ISO8601 타임스탬프", "message": "DAY1 시작: 토의 및 투표를 진행하세요." } |
역할 분배 후 첫 번째 낮(DAY1) 시작 알림. |
CHAT:MESSAGE | Client → Server / Broadcast | json { "roomId": "방 아이디", "userId": "사용자 아이디", "message": "채팅 내용", "timestamp": "ISO8601 타임스탬프" } |
낮 단계 토의 중 채팅 메시지 전송 (서버는 이를 전체 브로드캐스트). |
VOTE:MAFIA | Client → Server | json { "roomId": "방 아이디", "userId": "투표자 아이디", "voteFor": "투표 대상 사용자 아이디" } |
1차 투표: 마피아 의심 투표 요청. |
VOTE:MAFIA_RESULT | Server → Clients | json { "roomId": "방 아이디", "phase": "day", "voteType": "mafia", "result": { "targetUserId": "최다 득표자 아이디", "voteCount": 4 }, "message": "1차 투표 결과, [targetUserId] 에게 가장 많은 표가 모였습니다." } |
1차 투표 결과 전파. 최다 득표자 결정 및 공개. |
VOTE:SURVIVAL | Client → Server | json { "roomId": "방 아이디", "userId": "투표자 아이디", "vote": "yes |
no" } ``` |
VOTE:SURVIVAL_RESULT | Server → Clients | json { "roomId": "방 아이디", "phase": "day", "voteType": "survival", "result": { "targetUserId": "최다 득표자 아이디", "finalDecision": "dead |
alive" }, "message": "[targetUserId] 의 생존 여부가 투표 결과 [finalDecision] 처리되었습니다." } ``` |
ROOM:NIGHT_START | Server → Clients | json { "roomId": "방 아이디", "phase": "night", "nightNumber": 1, "startTime": "ISO8601 타임스탬프", "message": "NIGHT 단계 시작: 특수 능력을 사용하세요." } |
낮 투표 후 밤(NIGHT) 단계 시작 알림. |
ACTION:MAFIA_TARGET | Client → Server | json { "roomId": "방 아이디", "userId": "마피아 플레이어 아이디", "targetUserId": "공격 대상 사용자 아이디" } |
마피아가 공격할 대상을 선택. |
ACTION:DOCTOR_PROTECT | Client → Server | json { "roomId": "방 아이디", "userId": "의사 플레이어 아이디", "targetUserId": "보호 대상 사용자 아이디" } |
의사가 보호할 대상을 선택. |
ACTION:POLICE_CHECK | Client → Server | json { "roomId": "방 아이디", "userId": "경찰 플레이어 아이디", "targetUserId": "조사 대상 사용자 아이디" } |
경찰이 조사할 대상을 선택. (조사 결과는 개별 POLICE:RESULT로 전송) |
ROOM:NIGHT_RESULT | Server → Clients | json { "roomId": "방 아이디", "nightNumber": 1, "result":{ "killedUserId": "사망 처리된 사용자 아이디 또는 null", "details": "의사 보호로 인해 살해 취소됨 또는 마피아 공격 성공" }, "message": "밤 동안의 사건 결과: [details]" } |
밤 동안의 특수 능력 결과 처리 후 결과 전파. |
POLICE:RESULT | Server → 특정 클라이언트 (경찰) | json { "roomId": "방 아이디", "userId": "경찰 플레이어 아이디", "targetUserId": "조사 대상 사용자 아이디", | "role": "mafia |
citizen |
ROOM:DAY_START (후속) | Server → Client | json { "roomId": "방 아이디", "phase": "day", "dayNumber": 2, "startTime": "ISO8601 타임스탬프", "message": "DAY2 시작: 밤 사건 결과 - [요약]" } |
밤 결과 처리 후 다음 날(DAY2) 단계 시작 알림. |
GAME:OVER | Server → Client | json { "roomId": "방 아이디", "winningTeam": "citizens |
mafia", "finalState": { "players": [ { "userId": "user1", "role": "citizen", "alive": true }, { "userId": "user3" , "role": "mafia", "alive": false } ] }, "message": "게임 종료: [winningTeam] 승리!" } |
ERROR | Server → Client | json { "errorCode": "ERROR_CODE", "message": "오류 상세 설명", "details": {} } | 요청 처리 중 발생한 오류 정보를 전송. |