TroubleShooting & Study/SpringBoot

[WebSocket] WebSocket으로 채팅 구현하기 - 이론편 (특징, 동작과정, STOMP)

DH_0518 2024. 8. 25. 23:10
WebSocket

 

 

 WebSocket을 공부하기 전에는, WebSocket이 그저 양방향 통신을 제공하는 특정 기술을 말하는 줄 알았다. 하지만 WebSocket이란 기술을 의미하는 것이 아니라 양방향 통신을 제공하기 위해 개발된 '프로토콜'을 의미한다.

 간단하게 설명하자면, WebSocket 통신을 사용하는 URI로 HTTP Request를 보냈을 때 요청이 올바르다면 protocol을 HTTP에서 WS(WebSocket)으로 Upgrade 한다. 그 이후에는 WS 프로토콜을 사용하여 양방향 통신을 진행한다고 생각하면 된다.

 

 

 

WebSocket의 특징과 통신 과정을 자세히 알아보자

WebSocket 특징

  • HTTP 통신과 다르게 서버에서 Response를 받고 난 이후에도 Connection을 그대로 유지한다
  • 초기 연결을 제외하면, 클라이언트의 추가적인 Request 없이 Connection이 끊기지 않고 서버 -> 클라이언트로의 데이터 전송이 가능하다는 점이 SSE와 동일하다
  • 하지만 SSE는 클라이언트 -> 서버로의 데이터 전송시 HTTP Request를 사용해야 하지만, WebSocket은 HTTP Request가 아니라 서버->클라이언트와 동일하게 WS Protocol을 사용하여 클라이언트 -> 서버로의 데이터 전송이 가능하다
  • 기존의 TCP 프로토콜을 사용한 3-way Handshake 이후에, HTTP Request를 통해 추가적인 Handshake 과정을 진행해야 한다
  • 프로토콜 요청은 [http(s)://~]이 아니라 [ws://~]로 시작한다

 

WebSocket 동작 과정

  1. 클라이언트-서버의 TCP/IP 접속이 먼저 진행된다
  2. 이후 클라이언트에서 WebSocketHandler가 설정된 URI로 HTTP Request를 보낸다
    • Request의 Header에는 [Upgrade: WebSocket]이 포함되어야 한다 (upgrade, websocket 대소문자 구분 x)
    • HTTP Method는 'GET'을 사용해야 하며, HTTP v1.1 이상을 사용해야 한다
  3. 서버에서 Request에 대한 WebSocket Handshake 진행한다
    • Header에 Upgrade 필드를 확인하여 올바른 WebSocket Upgrade 요청인지 확인한다
    • 요청이 올바르다면 이를 수락하고, Response의 HTTP Status를 101 Switching으로 설정하여 클라이언트로 돌려준다
  4. Handshake가 성공적으로 완료되면 HTTP 연결이 WebSocket 연결로 Upgrade 되고, 이후에는 WebSocket 양방향 통신이 진행된다
    • 이때 request-response 구조가 아니라, 양방향 데이터 스트림을 통해 자유롭게 데이터를 주고받는 구조이다

 

 

 

 

 

 

STOMP

 

 

 WebSocket만을 가지고도 양방향 통신을 구현할 수 있다. 하지만 WebSocket을 공부하다 보면, 항상 'STOMP'가 따라다니는 것을 볼 수 있다. 그럼 도대체 STOMP가 뭐길래 계속 언급되는 걸까?

 

 STOMP(Simple Text Oriented Messaging Protocol)란 WebSocket 위에서 동작하는 프로토콜로, 클라이언트와 서버가 전송할 메시지의 '유형, 형식, 내용'들을 정의하는 메커니즘이다.

 WS는 메시지의 유형(Text, Binary)을 정의하고 있긴 하지만, 그 메시지의 내용이나 구조까지는 정의하고 있지 않다. 이로 인해 클라이언트와 서버가 서로 다른 메시지 형식을 사용한다면 구현이 복잡해지고 확장성이 떨어질 수 있다.

 또한 WS만 사용했을 때엔 채팅방에 연결된 session들을 직접 관리해서 해당 session으로 채팅 데이터를 전송해야 하기에 Handler 구현이 복잡해진다.

 

STOMP는 이러한 WS의 단점을 극복하여 효율적인 메시지 전송을 할 수 있도록 명확한 메시지 구조를 정해둔 프로토콜이다.

 

STOMP 특징

  • 메시지의 '유형, 형식, 내용'을 정의한다
  • 메시지의 헤더에도 값을 세팅할 수 있어서, 헤더값을 기반으로 인증 처리를 구현할 수도 있다
  • Pup/Sub 구조로 동작한다. 따라서 채팅방에 연결된 세션을 관리할 필요가 없다
  • 스프링 STOMP가 내장하고 있는 SimpleBroker나 RabbitMQ, Redis, Kafka와 같은 외부 메시징 시스템을 STOMP Broker로 사용할 수 있다
  • HTTP에서 모델링 되는 Frame 기반 프로토콜이다
  • 내부 핸들러를 사용하므로 따로 핸들러를 구현할 필요가 없다


STOMP Frame

<커멘드>
SEND or SUBSCRIBE를 지시
<헤더>
헤더정보를 key:value로 설정한다
보통 destination: /topic/.. 이나 destination: /queue/ 등의 형태로 사용된다
destination은 STOMP 서버 구현체마다 달라질 수 있다

<내용>
헤더에서 빈 줄을 추가하고 설정한다
전송할 payload 값들이 json 형태로 들어간다
Ex)
SEND
destination: /pub/chat
content-type: application/json

{"chatRoomId":5, "type": "MESSAGE", "writer": "client2"}^@

 

 

외부 message queue를 브로커로 사용하려면 연결을 또 설정해줘야 하는데, message queue 자체 설정과 연결 설정 등을 추가로 구현해줘야 해서 그냥 SimpleBroker를 쓰는게 더 나을 수도 있다. 하지만 고라니님 글을 보면 다음과 같은 이유로 외부 메시지 큐를 브로커로 두는 것을 추천한다

  1. 이용자 수가 증가하여 처리해야 하는 데이터가 많아지면, SimpleBroker는 서버의 메모리를 사용하기 때문에 채팅뿐만 아니라 다른 비즈니스 로직에도 성능 영향을 미칠 수 있다
  2. 채팅 서버는 스프링 서버에 종속되어 있다. 따라서 여러 서버로 배포된 상태로 로드밸런싱을 사용한다면, 같은 채팅방에 있다 하더라도 다른 서버 간 메시지 상태나 데이터가 공유되지 않는다

SimpleBroker는 간단한 사이드 프로젝트를 진행할 때 WS와 STOMP의 흐름을 이해하는 정도로는 좋을 것 같지만, 확실히 실제 운영을 위해서는 외부 메시지 큐를 사용하는 방법도 알아야 할 것 같다.

 

다음 WS 글에서는 Kafka를 사용해서 채팅방을 구현해 봐야겠다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Reference

 

[Spring Boot] WebSocket과 채팅 (1)

일전에 WebSocket(웹소켓)과 SockJS를 사용해 Spring 프레임워크 환경에서 간단한 하나의 채팅방을 구현해본 적이 있다. [Spring MVC] Web Socket(웹 소켓)과 Chatting(채팅) 기존 공부 용도의 게시판(?)에 여러

dev-gorany.tistory.com