Team-def.recre-backend.README.md {swjungle}


META README

본 파일은 깃허브 리포지토리 recre-backend README.md 파일에 작성할 내용의 초안을 담았습니다. 서비스 소개, 게임 소개, 배포 방법, 포스터, 최종발표영상 까지는 recre-frontend와 동일한 내용을 담을 예정이고, 그 뒤에는 발표나 포스터에서 못다 이야기한 기술적인 챌린지들을 정리할 예정입니다.

RecRe

Screenshots

나만무_중간발표_최승현

나만무_중간발표_최승현

Architectures

최종 수정.png

Build with NestJS

먼저 필요한 의존성들을 설치합니다.

npm i

다음으로 환경변수들을 .env 파일에 정의합니다. 다음 변수들이 필요합니다:

# 호스트 유저정보를 저장할 DB서비스

DB_HOST=
DB_USER_PASSWORD
DB_USER_NAME
DB_DATABASE
DB_PORT

# 구글 소셜로그인을 위해 필요한 클라이언트 ID

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_CALLBACK_URL=

KAKAO_CLIENT_ID=
KAKAO_CLIENT_SECRET=
KAKAO_CALLBACK_URL=

NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
NAVER_CALLBACK_URL=

# 로그인된 호스트들을 인가하기 위한 JWT 토큰과 관련한 정보

JWT_ACCESS_TOKEN_SECRET=
JWT_ACCESS_TOKEN_EXPIRATION_TIME=
JWT_REFRESH_TOKEN_SECRET=
JWT_REFRESH_TOKEN_EXPIRATION_TIME=

# 클라이언트 서버의 주소

CLIENT_URL=

# 프론트/백엔드 공통적으로 사용될 도메인 이름, 예를 들어 www.recre.com이 있다면, recre.com이 DOMAIN입니다

DOMAIN=

# 본 서비스가 동작할때 Listen할 포트번호

LISTEN_PORT=

그리고 다음 명령어를 통해 각각 개발용과 프로덕션용 모드로 실행할 수 있습니다. NestJS 커맨드에 대한 자세한 설명은 공식문서를 참고하세요.

npm run start:dev
npm run start:prod

Project Directory Structure

NestJS는 Controller & Service 구조로 이루어져 있으며, 각각의 컴포넌트들이 Module 단위로 분리되어 있습니다. 아래 Tree는 실제 파일들의 구조를 간략하게 소개한 텍스트입니다.

src
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── auth ############################## 호스트 사용자 인증 / 인가 모듈
│   ├── auth.controller.ts
│   ├── auth.module.ts
│   └── auth.service.ts
├── main.ts
├── session ########################### 캐치마인드, 무궁화꽃이 피었습니다 게임로직 & Socket.io 인터페이스
│   ├── catch.gateway.ts
│   ├── redgreen.gateway.ts
│   ├── session.guard.ts
│   ├── session.module.ts
│   └── socket.extension.ts
├── session-info ###################### 게임, 플레이어, 호스트 상태를 관리하는 모듈
│   ├── entities
│   │   ├── catch.game.entity.ts
│   │   ├── catch.player.entitiy.ts
│   │   ├── host.entity.ts
│   │   ├── player.entity.ts
│   │   ├── redgreen.game.entity.ts
│   │   ├── redgreen.player.entity.ts
│   │   └── room.entity.ts
│   ├── session-info.module.ts
│   └── session-info.service.ts
└── user ############################## 호스트 정보를 관리하는 모듈
    ├── dto
    │   ├── create-user.dto.ts
    │   └── update-user.dto.ts
    ├── entities
    │   └── user.entity.ts
    ├── user.controller.ts
    ├── user.module.ts
    └── user.service.ts

기술적 챌린지

소켓 유령 제거 & 지속적인 연결성 보장

중간에 끊긴 소켓통신에 대한 사용자 식별 및 접속유지 프로토콜 구현 #16

Pasted image 20231212152442.png

게임 공정성을 위한 지연시간 극복

무궁화꽃이피었습니다 게임 공정성 향상 (Notion)

관련 링크

https://github.com/Team-def/recre-backend/pull/104

https://github.com/Team-def/recre-backend/issues/87

web socket latency 관련 블로그 (3-way)

socket.io latency 계산식 (1-way)

cloudflare.com - what is latency

문제상황

게임플레이에 지장을 줄 정도로 판정이 가혹했습니다. 지연시간을 생각하지 않아 stop 이벤트 이전에 발송된 run이 뒤늦게 도착해 게임오버가 되는 경우가 발생했습니다.

해결방안

지연시간이 존재하면 극복하면 되는 법. 플레이어 클라이언트가 주기적으로 서버에 ping 이벤트를 보내 서버가 응답한 acknowledgement를 받을때까지의 시간을 구합니다. 이 시간을 Round Trip Time, 줄여서 RTT라고 부릅니다. RTT는 client → server → client 2-way이기 때문에 이를 절반으로 나누어야 1-way 지연시간을 구할 수 있습니다.

Pasted image 20231212164243.png

Show Me the Code

client:

const start = performance.now();
socket.emit("ping", {start}, (res: {start: number}) => {
	const end = performance.now();
	const latency = (end - res.start) / 2;
	console.log(`latency: ${latency}ms`);
});

server:

레이턴시 측정을 위해 ping 이벤트에 ack를 보내주는 루틴

@SubscribeMessage('ping')
ping(client: Socket, payload: { start: number }) {
    return { start: payload.start };
}

player run 이벤트에 따른 죽음판정정책

/**
 * 지연시간 기반 죽음판정 정책
 */
private doesPlayerHaveToDie(game: RedGreenGame, latency: number): boolean {
	const CONSTANT_MS = 200; // stop 메시지 날아온 시간으로부터 최소 인정시간
	const admitTime = game.last_killer_time + CONSTANT_MS + latency;
	const currentTime = performance.now();
	if (currentTime > admitTime) {
		Logger.debug(`${currentTime - admitTime}ms 만큼 늦었습니다. (latency: ${latency})`, 'doesPlayerHaveToDie');
		return true;
	}
	Logger.debug(`${admitTime - currentTime}ms 만큼 빨랐습니다. (latency: ${latency})`, 'doesPlayerHaveToDie');
	return false;
}