이전 프로젝트 정리
이전 포스트에서는 채팅 서버에서 방을 어떻게 생성하고, Redis를 이용해 방의 상태와 생명주기를 관리하는 구조를 정리했다. 방은 단순히 생성되고 사라지는 데이터가 아니라, WAIT → PLAYING → END 로 이어지는 명확한 상태를 가지며, 각 상태에 따라 TTL을 다르게 적용해 안전하게 관리해야 한다고 생각을 가지며 마무리 하였다. 이를 통해 유령방은 자동으로 정리하면서도, 게임이 진행 중인 방이 TTL 만료로 삭제되는 문제는 방지할 수 있었다. 이제 다음으로 해결해야 할 문제는 "방에 어떻게 입장할 것인가"이다. 방 접속은 단순히 인원 수를 증가시키는 작업처럼 보이지만, 실제로는 다음과 같은 요구사항을 동시에 만족해야 한다.
- 최대 인원 수 초과 방지
- 동시 접속 상황에서도 정확한 인원 관리
- 정상적인 요청만 소켓 join 으로 연결
- Redis 상태와 실제 접속 상태의 일관성 유지
이번 포스트에서는 위의 사항들을 고려해서 HTTP 기반 방 접속 로직을 설계하고, 검증이 완료된 경우에만 WebSocket join 이 이루어지도록 전체 흐름을 정리해보려고 한다.
방 참여 로직 설계 전 고려사항
- Redis로 방 관리를 한번 체크 해준다.
먼저 HTTP API에서 Redis를 통해 방 상태를 검증한다.
이 단계에서는 방 존재 여부, 현재 인원 수, 최대 인원 제한과 같은
모든 인증 및 검증 로직을 처리한다. - 그리고 소켓으로 join 시킨다.
검증이 완료된 이후에만 WebSocket을 통해 room join 을 수행한다.
소켓은 상태를 판단하지 않고, 실시간 통신과 연결 관리에만 집중한다. - 소켓으로만 관리하지 않는다.
방 참여를 소켓으로만 처리하지 않는 이유는 명확하다.
방 인원 관리, 정리 로직까지 모두 소켓 이벤트에 포함시키면
소켓 핸들러가 빠르게 비대해지고, 예외 처리와 테스트가 어려워진다. - 소켓은 join을 할 경우에 방이 자동으로 생성된다.
Socket.io 특성상 join 시 방은 자동으로 생성된다.
따라서 방의 생성과 생명주기는 Redis에서 명시적으로 관리하고,
소켓 방은 비어 있는 경우 자연스럽게 정리되도록 맡긴다.
닉네임 설정하기 - HTTP
소켓 서버는 아무 요청이나 받아들여서는 안 된다. JWT 토큰은 소켓 서버 접속 시 전달되어, 이 사용자가 서버에서 정상적인 흐름을 거쳐 발급된 사용자인지를 확인하는 역할을 한다. 결론적으로 닉네임을 설정하면서 JWT 토큰을 발행하고 추후에 소켓서버에 참여할때 전송하여 검증까지 완료시킨다.
async createNickname(nickname: string) {
const result = { nickname , Authorization: await this.jwt.createJwt(nickname) };
return result;
}

방 참여하기 - HTTP
메인코드
import {
BadRequestException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { randomUUID } from 'crypto';
import { RedisService } from 'src/redis/redis.service';
@Injectable()
export class RoomService {
constructor(private readonly redis: RedisService) {}
async joinRoom(roomId: string) {
const key = `room:${roomId}`;
const room = await this.redis.client.hgetall(key);
if (Object.keys(room).length === 0) {
throw new NotFoundException('방이 존재하지 않습니다.');
}
const players = Number(room.players);
const maxPlayers = Number(room.maxPlayers);
if (players >= maxPlayers) {
throw new BadRequestException('방이 가득 찼습니다.');
}
await this.redis.client.hincrby(key, 'players', 1);
return { roomJoin: true, players: players + 1, maxPlayers };
}
}
코드설명
const room = await this.redis.client.hgetall(key);
const maxPlayers = room.maxPlayers;
if (Object.keys(room).length === 0) {
throw new NotFoundException('방이 존재하지 않습니다.');
}
roomId를 기반으로 Redis Hash 전체를 조회한다. 방이 존재한다면 최소한 하나 이상의 필드를 가지고 있어야 하며, 빈 객체가 반환되었다는 것은 방이 이미 삭제되었거나 존재하지 않는다는 의미다. 이 검증을 통해서 생성되지 않은 방이나, 이미 만료되어 사라진 방에 접속하는것을 초기에 차단해버린다.
const players = Number(room.players);
const maxPlayers = Number(room.maxPlayers);
if (players >= maxPlayers) {
throw new BadRequestException('방이 가득 찼습니다.');
}
그리고 마지막으로 방이 풀인지 확인하여 기본적인 검증으로 기본적인 유효성을 검사한다.
await this.redis.client.hincrby(key, 'players', 1);
검증이 완료된 이후에만 현재 인원 수를 1 증가시킨다. 이렇게 HTTP 단계에서 인원 수를 먼저 증가시킴으로써, 소켓 join 이후 발생할 수 있는 상태 불일치 비정상적인 중복 입장 을 어느 정도 예방할 수 있다.
방 참여하기 - Socket
'NestJS > 개발' 카테고리의 다른 글
| 실시간 채팅 서버 개발 - 02 (방 생성하기) (0) | 2026.01.18 |
|---|---|
| 실시간 채팅 서버 개발 - 01 (Redis 및 NestJS 셋팅) (1) | 2026.01.14 |
| Redis/BullMQ 이용하여 연산 작업 따로하기 (0) | 2025.12.23 |
| JWT - Passport 사용하기 (0) | 2025.12.22 |
| JWT - 역할 기반 관리하기 (1) | 2025.12.21 |
