본문 바로가기
IT이야기/PROJECT

[React + SpringBoot] 1대1 랜덤채팅 프로젝트 - 화면 및 기능 설계 (실시간 통신, 매칭)

by JI_NOH 2024. 3. 8.

목적

- 로스트 아크 게임 유저들을 위한 깐부 찾기 랜덤채팅 사이트

구성

- 프론트 : React, Shadcn, Javascript, Socket, STOMP
- 백 : SpringBoot, Java, MySQL, Redis, WebSocket, STOMP


 

 

목차

    앞선 포스팅에서 간단한 프로젝트 소개와 구성에 대해 안내했다.

    이번에는 내가 구상한 기능에 대한 소개와 어떤 프레임워크와 라이브러리를 통해 해당 기능을 구현하는지에 대한 큰 설명을 해보려고 한다.

     

    당 프로젝트의 가장 핵심은 '1대1 실시간 통신' 그리고 '매칭 서비스' 이다.

    실시간 통신을 위해 WebSocket + STOMP를 사용했고

    매칭 서비스를 위해 Redis를 사용했다.

     

     

     

     

     

    1대1 실시간 통신


    - WebSocket

    아마 실시간 통신을 구현하려고 하는 사람이라면 '웹소켓'은 많이 들어봤을 것이다.

    우리가 흔히 사용하는 HTTP 통신의 경우는 요청-응답 이라는 구조로 통신이 이루어지다보니 서로가 지속적으로 메세지를 보내는 요청을 하는 작업에 있어서는 상당히 비효율적일 수 밖에 없다.

     

    웹소켓은 결국 한 번 연결을 맺은 뒤, 계속 유지되기에 효율적이며 실시간성을 보장하는 듯 하는 역할을 할 수 있게 한다.

     

     

    - STOMP (Simple Text Oriented Messages Protocol)

    웹소켓에 이어 함께 사용한 STOMP는 프로토콜 방식 중 하나로 간단한 메세지 전송을 위한 publisher - subscriber 방식으로 진행된다.

    실시간채팅 관련된 자료들을 찾다보면 WebSocket만 쓰는 경우도 있을 거고 WebSocket+STOMP를 쓰는 경우도 있었을 것이다.

     

    좀 더 정확히 얘기하자면 STOMP는 웹소켓 위에서 동작하는 프로토콜 방식 중 하나인데 단순 웹소켓만으로 실시간통신을 구현 한다면 어떤 프로토콜로 통신할 것이며 연결 주소마다 새로운 핸들러를 구현하고 설정해줘야하는 작업 또한 이루어 져야 한다.

     

    즉, 어떤 프로토콜을 사용할지 고민하고 어떻게 메세지들을 파싱할 지에 대한 고민을 하지 않게 해주는 역할을 한다고 보면 되겠다.

    마찬가지로 STOMP 를 찾아본다면 많이 봤을 그림이다.

    처음 이 그림을 봤을 때 상당히 혼란스러웠던게 /app 으로 가는 것과 / topic으로 가는 것, 거기다 /app으로 보낸 것을 브로커를 지나쳐 다시 /topic으로 보낸다고..? 하면서 상당히 혼란스러웠었다.

     

    말하자면 두 가지 경우의 수가 있어서 그림이 저런식으로 표기된 것 이었는데

     

    CASE 1.

    구독자들은 /topic 이라는 경로를 구독하고 있다.

    발신자는 /app 경로로 메세지를 발신한다. 그리고 SimpleAnnotationMehtod를 통해 해당 메세지를 가공 후 /topic 경로로 다시 전송한다.

    최종적으로 브로커가 /topic 경로를 구독하고 있는 구독자에게 전달한다.

     

    CASE2.

    구독자들은 /topic 이라는 경로를 구독하고 있다.

    발신자는 /topic 경로로 메세지를 발신한다. (별도 가공이 필요없는 경우에 해당한다)

    최종적으로 브로커가 /topic 경로를 구독하고 있는 구독자에게 전달한다.

     

     

     

     

     

     

     

    매칭 서비스


    -Redis

    데이터베이스의 일종으로 InMemory 데이터 저장소이다.

    문자열, 리스트, 해시, Sorted Set 등 다양한 데이터 타입을 지원하며 빠른 속도가 강점이라고 많이 들어봤을 것이다. AWS 에서 소개하는 Redis를 보면 최고의 성능이 필요한 웹, 모바일, 게임, 광고 기술 및 IoT 애플리케이션 등에 쓸 수 있다고 하며 결국 빠른 조회와 자유로운 데이터 셋의 사용 등이 필요한 경우에 빛을 발한다고 볼 수 있겠다.

    그 강점이 나올 수 있는 이유는 디스크가 아닌 메모리에 저장함으로써 빠른 접근이 가능한 것이다. 또한 싱글 스레드로 Context 전환비용이나 lock등의 작업이 필요하지 않은 것 또한 이유 중 하나이다.

     

    해서 매칭서비스는 계속 DB에 접근하여 매칭 조건들을 확인해야하며 매칭이 완료 된 후에는 더이상 볼 필요가 없는 데이터들로 이루어져있으니 Redis를 이용하여 작업한다면 Mysql을 통해 작업하는 것보다 비용 절감이 된다고 판단하였다.

     

     

     

     

     

     

     

    화면 기능


    사설이 길었다.

    화면기능과 설계에 있어서 앞선 이론들에 대해 알 필요가 있기에 정리 겸 언급을 해봤다.

     

    웹사이트 화면 구성은 4 페이지로 진행했다.

    1. 로그인 화면
    2. 선호 매칭 옵션 설정 화면
    3. 매칭 대기 화면
    4. 채팅 화면

     

    가장 중요 포인트는 3. 매칭 대기 화면 그리고 4. 채팅 화면 이겠다.

     

     

    - 매칭 대기 화면

    - 화면 기능 설명

    1. 매칭 옵션은 여러 옵션들 중 최대 3개까지 선택한다.

    2. 기본적으로 3개가 모두 동일한 경우에만 매칭을 시켜준다.

    3. 단, 20초가 경과하도록 매칭이 되지 않는다면 '계속 대기' or '선택옵션 -1개로 재매칭'을 선택할 수 있다.

    4. 매칭이 된다면 채팅방을 생성하며 양 측이 동시 입장하게 된다.

     

    - Redis + STOMP 적용

    1. 매칭 시작을 하면 우선적으로 Redis 대기 리스트에 동일 매칭 옵션을 가진 사람을 조회한다.

    2. 동일 옵션이 있다면 등록된 사람 기준으로 채팅방이 생성된다. 이 때 Redis 대기 리스트에 등록된 채로 기다리던 사람은 STOMP /sub를 통해 매칭이 됨을 확인 후 채팅방에 입장하게 된다.

    3. 동일 옵션이 없다면 본인의 정보가 Redis 대기 리스트에 등록된다.

    4. 등록 순을 우선순위로 가져가기 위해 Sorted-Set 을 이용하였으며 등록시간이 가중치다.

    5. 다른 사람이 매칭을 돌릴 때 Redis 대기 리스트를 조회하다가 일치한다면 본인 기준으로 방이 생성된다.

    6. 20초가 지나도록 매칭이 되지 않는다면 '등록한 옵션개수 -1'만큼 일치하는 사람을 다시 리스트에서 찾는다. 단, 상대방도 옵션이 2개만 매칭되도 상관없어야 한다.

    7. 리스트에 없다면 Redis 대기 리스트에 재등록한다. 이 때 우선순위는 최초 등록시간을 그대로 가져가기 때문에 변동되진 않으며 key값만 변동 된다.

    *Redis에서 Sorted-Set을 사용하였으며 key(옵션수:옵션내용) value(id,roomKey) 데이터로 구성함.

    *매칭용 STOMP endpoint를 따로 뒀다.

     

     

    - 채팅 화면

    - 화면 기능 설명

    1. 입장하는 순간에 소켓 통신이 이루어지며 실시간으로 채팅이 가능하다.

    2. 채팅 내용은 모두 저장된다. 랜덤 매칭이지만 다음에 다시 접속했을 때도 계속 채팅을 이어갈 수 있게 하기 위함이다.

    3. 한 명이 방을 퇴장하면 퇴장 안내문구가 뜨며, 두 사람이 모두 퇴장하면 방은 삭제된다.

    한 명이 퇴장을 하면 남은 사람은 채팅방에서 채팅은 불가능하다. 내용만 확인 가능하다.

     

    - WebSocket + STOMP 적용

    1. 채팅 입장과 동시에 프론트에서 SocketJs를 통해 소켓이 연결된다.

    2. 메세지를 입력 후 전송하면 프론트가 /pub/chat/message 를 통해 발행을 한다.

    3. 백에서 해당 엔드포인트로 @MessageMapping("/chat/message") 발행 내역을 받은 후 redisTemplate.convertAndSend("/sub/chat/room/"+ message.getRoomKey(), message); 를 이용해 발행처리를 한다.

    4. 프론트에서 /sub/chat/room/{roomKey} 를 구독하고 있는 사람들은 해당 메세지를 받아서 화면에 표기한다.

     

     

    이번 포스팅에서는 간단한 흐름에 대해서만 설명해봤다.

    다음 포스팅에서는 실제로 프로젝트에서는 어떻게 적용이 되었는지 자세히 설명할 예정이다.

     

     

    참고 - https://velog.io/@guswns3371/WebSocket-Spring

    https://velog.io/@msung99/%EC%9B%B9%EC%86%8C%EC%BC%93%EC%9D%B4%EB%9E%80

    https://ryanpark.dev/24