🚵🏻 프로토타입을 만든 이유?
친구들과 팀프로젝트로 실시간 공유 편집기능을 가진 마인드맵 툴 웹 어플리케이션(상당히 어려울 것만 같은...!)을 제작하게 되었다. 하지만 나는 해당 프로젝트의 구조에 대해 어떻게 설계하고 구현해야 할지 전혀 감이 잡히지 않아 가장 간단하게 만들 수 있는 SpringBoot 를 사용한 채팅 메세지 어플리케이션을 참고하여 문서 편집 어플리케이션의 프로토타입을 먼저 제작해 보기로 했다.
✅ 중요 개념
WebSocket
- 두 프로그램 간의 메세지를 교환하기 위한 통신 방법(protocol) 중 하나
- Socket Connection을 유지한 채로 실시간으로 양방향 통신 혹은 데이터 전송이 가능하다
- HTTP와 다르게 상태(Stateful) 프로토콜이다. 즉, 클라이언트와 서버가 한 번 연결되면 같은 연결을 이용해 통신하므로 TCP 커넥션 비용을 아낄 수 있다.
특징
- 양방향 데이터 통신 (Full-Duplex)
- 데이터 송수신을 동시에 처리할 수 있는 통신 방법
- 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있다
- 통상적인 HTTP 통신은 Client가 요청을 보내는 경우에만 Server가 응답하는 단방향 통신
- 실시간 네트워킹
- 웹 환경에서 연속된 데이터를 빠르게 노출 (ex. 채팅, 주식, 비디오 데이터)
- 여러 단말기에 빠르게 데이터를 교환
- 최초 접속시에만 HTTP 프로토콜 위에서 HandShaking하기 때문에 HTTP header를 사용한다
- 웹소켓을 위한 별도의 포트는 없으며 기존 포트(http-80, https-443)를 사용
- frame으로 구성된 메시지라는 논리적 단위로 송수신
- 메시지에 포함될 수 있는 교환가능한 메시지는 텍스트와 바이너리
연결 과정
- 빨간 박스
- Client에서 HTTP 프로토콜에서 WebSocket 프로토콜로 Upgrade 요청(header의 Upgrade 필드에 Websocket을 담아서 보냄)
- Server에서 101 Switching Protocols 가 Response로 오면 WebSocket이 연결
- 이 때, Client와 Server가 key를 주고 받는데 임의의 숫자를 base64로 인코딩한 값으로 서버가 응답으로 보낸 key가 일치하지 않으면 연결이 수립하지 않는다
- 노란 박스
- 연결 후 프로토콜이 ws로 변경되어 Message를 주고받는다
- Message: 여러 frame이 모여서 구성하는 하나의 논리적 메세지 단위
- frame: communication에서 가장 작은 단위의 데이터, 작은 헤더(HTTP의 단점을 해소) + payload로 구성
- 웹 소켓 통신에 사용되는 데이터는 UTF-8 인코딩
- 보라 박스
- 연결을 종료하는 과정에서의 Handshake
한계
- WebSocket은 HTML5 이전의 기술로 구현된 서비스에서는 사용할 수 없다
- Socket.io, SockJS: HTML5 이전의 기술로 구현된 서비스에서 웹 소켓처럼 사용할 수 있도록 도와주는 기술
- JavaScript를 이용하여 브라우저 종류에 상관없이 실시간 웹을 구현
- 브라우저와 웹 서버의 종류와 버전을 파악하여 가장 적합한 기술을 선택하여 사용하는 방식
- WebSocket은 문자열들을 주고 받을 수 있게 해줄 뿐 그 이상의 일을 하지 않으며 주고 받은 문자열의 해독은 온전히 어플리케이션에 맡긴다
- WebSocket은 형식이 정해져 있지 않기 때문에 어플리케이션에서 쉽게 해석하기가 힘들어 sub-protocols을 사용해서 주고 받는 메세지의 형태를 약속하는 경우가 많다
- 이 중 한가지 경우가 STOMP(Simple Text Oriented Message Protocol)
STOMP(Simple Text Oriented Message Protocol)
- 채팅 통신을 하기 위한 형식(protocol)을 정의한다
- HTTP와 유사하게 간단히 정의되어 해석하기 편한 프로토콜이다
- 일반적으로 웹 소켓 위에서 사용된다
- binary 기반이 아닌 텍스트 기반 프로토콜이기 때문에 개발자가 읽기 쉽고 사용하기에 좋다
- 헤더 값을 기반으로 통신 시 인증처리를 구현할 수 있다
동작 방식
- 클라이언트는 메세지를 전송하기 위해 COMMAND로 SEND 또는 SUBSCRIBE 명령을 사용하며, header와 value로 메세지의 수신 대상과 메세지에 대한 정보를 설명할 수 있다
- Publisher(송신자)와 Subscriber(수신자)를 지정하고, Message Broker를 통해 특정 사용자에게만 메세지를 전송하는 기능도 제공
- Message Broker : Publisher로 부터 전달받은 메시지를 Subscriber에게 메시지를 주고 받게 해주는 중간 역할
- 예시
- A, B, C라는 유저가 5번 채팅방에 입장한다(SUBSCRIBE)
- A(Publisher)가 5번 채팅방에서 채팅(Message)를 전송한다
- 5번방 MessageBroker(현재 프로젝트에서는 서버에 존재)가 메세지를 받는다
- 5번방 MessageBroker가 메세지를 확인하고 5번방을 구독하고 있는 Client(Subscriber / A,B,C가 해당)에게 메세지를 전송한다
- A, B, C가 MessageBroker로 부터 메세지를 받는다
- A가 채팅방에서 나간다(DISCONNECT)
pub/sub
- 메세징 패턴 중 하나
- pub(publisher)가 topic에 메세지를 보내면 해당 topic을 구독해놓은 sub(subscriber) 모두에게 메세지가 전송되면서 데이터 교환이 이루어지는 방법
- pub/sub 패턴은 비동기식 메세징 패턴이기 때문에 publisher가 연산해야 할 다른 topic(task)을 publish 하면 topic (task)를 가져갈 subscriber가 받아서 받은 task를 처리하고, 처리하는 시간 동안 publisher는 다른 작업을 수행할 수 있다는 장점이 있다
- publisher(게시자)
- publisher는 message를 생성한 뒤에, topic에 담아두도록 전달해주는 역할
- message(메세지)
- message는 publisher로부터 subscriber에게 최종적으로 전달되는 데이터와 property의 조합입니다.
- topic(토픽), channel
- topic은 task, 즉 업무입니다. publisher가 message를 전달하는 리소스
- subscription(구독)
- subscription은 message 스트림이 subscriber들에게 전달되는 과정을 나타내는 이름을 가지고 있는 리소스
- subscriber(구독자)
- subscriber는 message를 수신하는 역할
📋 구현할 기능
팀 프로젝트를 시작하기 전, 어떤 원리와 구조로 개발해야 하는지 테스트 해보는 프로토타입이었기 때문에 많은 기능을 필요로 하지 않고 아래의 두 가지 기능을 가진 어플리케이션을 제작해보기로 했다.
- 문서 페이지에 접속 했을 때, 현재 페이지에 접속해있는 사용자 목록 표시
- 한 사용자가 수정중 일 때, 다른 사용자에게도 실시간으로 수정된 페이지가 보이며 똑같이 다른 사용자도 수정할 수 있는 기능 - 핵심 기능
사실 두 번째의 기능이 우리가 만들어야할 '실시간 공유 수정'의 핵심 기능이다. 구현할 기능을 위해 참고했던 프로그램은 Google Docs와 Notion 이다.
🤔 어떻게 구현해야 하지?
사실 위의 블로그 글을 참고하며 Spring WebSocket 과 Redis를 사용한 채팅 서비스를 간단하게 만들어 봤다. 그대로 따라하면서 만든 것이기에 따로 글로 쓰진 않지만 내가 원하는 두가지 기능을 구현하는데에 많은 참고가 되었던 것 같다!
해당 블로그에서는 여러 개의 채팅방을 생성할 수 있고, 채팅방에 접속해 있는 인원 수를 표시하며, 일반 채팅 처럼 메세지를 실시간으로 주고받을 수 있는 기능로 되어 있었다.
이를 내가 원하는 기능을 만드는 것으로 변형해 생각해보면 아래와 같이 바꿀 수 있다는 생각이 들었다.
- 채팅방에 접속해 있는 인원 수를 표시
- 기존에는 WebSocket에 연결했을 때의 인원 수를 +1 증가하고, 접속이 종료되었을 때에 인원 수를 -1 하는 방식
- 연결하게 되면 현재 접속중인 유저 Set에 해당 유저의 이름을 추가, 접속이 종료되었을 때에 해당 유저의 이름을 Set에서 삭제하는 방식으로 변형할 수 있다고 생각
- 메세지를 실시간으로 주고 받기
- 기존에는 메세지를 서버에 전송하면 서버에서 해당 채팅방에 접속해 있는 모든 Client에게 메세지를 보내는 방식 (pub/sub)
- 위의 방식을 문서를 수정할 때마다 즉, 타이핑 1번 마다 변경된 내용의 문서를 서버에 전송하며 서버에서는 해당 문서의 변경 내용을 받아 문서에 접속 중인 모든 유저에게 바뀐 문서의 내용을 보내 실시간으로 수정사항을 볼 수 있도록 하는 방식
- 실제로는 이렇게 구현하지 않고 입력되는 글자 하나 하나가 특정한 자료구조를 가지며 문서에서 현재 자신의 위치 정보를 가지고 글자 자료구조의 정보를 서버에 전송하여 다른 구독자들에게 전송하는 것으로 알고 있지만 나는 간단하게 구현하고 싶어 문서 전체의 내용을 메세지로 사용했다!
원래는 프로토타입에 대한 구현코드를 첨부하려 했으나, 제가 코드에 대한 이해가 부족하고 설명을 제대로 하지 못할듯 할 것 같아 생략했습니다. 이후에 좀 더 공부해서 제가 프로젝트에 위의 기술을 적용한 코드를 설명과 함께 첨부하도록 하겠습니다!
🔥 회고
🧳 Keep
- 사용하는 기술에 대한 개념 정리를 먼저 했다
👿 Problem
- 코드에 대한 설명을 추가해야 할 것 같다
🚵🏻 Try
- 기술에 대한 개념 정리에 더해 장점과 단점 및 다른 종류의 기술들도 함께 정리하면 좋을 것 같다
- 위의 설명을 바탕으로 어떤 방식으로 구현했는지에 대한 내용을 추가하면 좋을 것 같다
- 프로젝트를 진행하면서는 구현 코드와 함께 설명도 추가해보도록하자
참고
- https://www.daddyprogrammer.org/post/5290/spring-websocket-chatting-server-enter-qut-event-view-user-count/
- https://www.youtube.com/watch?v=MPQHvwPxDUw
- https://code-lab1.tistory.com/300
- https://choseongho93.tistory.com/266
- https://doozi0316.tistory.com/entry/WebSocket%EC%9D%B4%EB%9E%80-%EA%B0%9C%EB%85%90%EA%B3%BC-%EB%8F%99%EC%9E%91-%EA%B3%BC%EC%A0%95-socketio-Polling-Streaming
- https://tecoble.techcourse.co.kr/post/2021-09-05-web-socket-practice/
- https://www.joinc.co.kr/w/man/12/STOMP
- https://zamezzz.tistory.com/319
- https://resilient-923.tistory.com/402