WebSocket, STOMP, SockJS에 대해 알아보자
Why use WebSocket, STOMP, SockJs?
이번에 채팅을 구현할 때 WebScocket, STOMP, SockJS를 이용해서 구현했는데 각각은 무엇이고 왜 쓰는지 알아보자
HTTP vs WebSocket
우리는 보통 웹 프로토콜로 HTTP(HyperText Transfer Protocol)를 사용해왔는데 이 HTTP는 비연결성으로 클라이언트와 서버간 연결을 유지하지 않기 때문에 매번 정보를 주고 받을 때 마다 연결을 맺고 끊는 과정이 필요한데 이 과정에서 많은 비용이 들기 때문에 실시간 정보를 주고 받는 곳(채팅, 게임 등)에는 적합하지 않았다.
그래서 효율적인 양방향 통신을 구현하기 위한 기술인 웹소켓(WebSocket)이 등장하게 되었다. 웹소켓을 사용하게 되면 클라이언트와 서버가 한번 연결을 맺으면 계속 유지되고 서로 양방향 통신이 가능해진다.
SockJS
하지만 웹소켓 같은 경우 모든 환경에서 지원해주지 않았기 때문에 웹소켓을 지원하지 않는 환경에서는 사용할 수 없다는 단점이 있었는데 SockJS를 이용하면 지원하지 않는 환경에서도 가능하게 해준다.
SockJS는 다양한 기술을 이용해 웹소켓을 지원하지 않는 환경에서도 정상적으로 동작하도록 해준다. 전송 타입은 크게 다음 3가지로 나눠진다.
- WebSocket
- HTTP Streaming
- HTTP Long Polling
STOMP(Simple Text Oriented Messaging Protocol)
메시징 방식만 잘 정의하면 STOMP를 쓰지 않고 WebSocket만으로도 잘 만들 수 있으나 해당 메시지가 어떤 요청인지 어떻게 처리해야 하는지 등 추가적으로 구현해주어야 될 것이 많다.
STOMP는 메시지 브로커를 이용하여 쉽게 메시지를 주고 받을 수 있는 프로토콜로 발신자가 메시지를 발행하면 수신자가 그것을 수신하는 Pub(발행) - Sub(구독) 형태로 이루어져 있다. 쉽게 말하면 누군가 어떤 채팅창을 구독(Sub)하고 있다면 그 채팅방에서 채팅(Pub, 발행)을 보내면 구독하고 있던 구독자들은 모두 채팅을 받는 것이다.
구독, 발행하는 과정을 보기전 STOMP가 어떤 형태로 되어있는지 먼저 보자. STOMP는 프레임 단위 프로토콜으로 커맨드, 헤더, 바디로 이루어져 있다.
COMMAND
header1:value1
header2:value2
Body^@
- COMMAND: SUBSCRIBE(구독), SEND(발행), MESSAGE(BroadCasting, 전체 전송)
- header: destination, type, subscription … 등
예를 들어 클라이언트가 1번 채팅방에 구독을 하면 아래 처럼 발송이 될 수 있다.
SUBSCRIBE
id: sub-0
destination: /topic/room/1
Pub(발행), Sub(구독) 흐름
아래 그림에서 /app을 발행(/pub)으로 /topic을 구독(/sub)으로 치환해서 보면 더 쉽다.
- MESSAGE : 헤더 및 페이로드를 포함한 메시지
- SimpleAnnotationMethod : @MessageMapping을 이용해 메시지 처리
- MessageHandler : 메시지 처리를 위한 계약
- SimpleBroker : subscription을 메모리에 저장하고 연결된 client에게 메시지를 보냄
- 발행(Pub)
- destination에 /app으로 들어오게 되면 @MessagingMapping 애노테이션이 붙은 스프링 컨트롤러로 매핑되게 되고 컨트롤러에서 메시지를 처리한 후에 /topic으로 브로커에게 전달하면 브로커는 MESSAGE COMMAND를 이용해 구독하고 있는 모든 구독자들에게 response를 전송한다.
- 구독(Sub)
- destination에 /topic으로 들어오게 되면 스프링 컨트롤러를 거치지 않고 브로커에게 바로 접근하게 되는데 SUBSCRIBE COMMAND의 경우가 여기에 해당되며 이 경우 브로커가 구독자를 메모리에 저장하여 관리한다.
다음과 같은 상황에 어떤 과정들이 일어나는지 한번 보자
클라이언트0번이 1번 채팅방에 구독
SUBSCRIBE id: sub-0 destination: /topic/room/1
위에서 구독(Sub)흐름에 해당하는 과정이 일어나게 된다.
클라이언트1번이 1번 채팅방에서 메시지를 전송
SEND destination: /app/{@MessageMapping Endpoint} content-type:application/json {"roomId":1, "type": MESSAGE", "sender":"client1"}
위에서 발행(Pub) 흐름에 해당하는 과정이 일어나게 되어 컨트롤러에서 메시지 처리가 일어난 후 모든 구독자에게 메시지를 Broadcasting(전체 전송)을 하기 위해 아래와 같은 MESSAGE COMMAND 전송
MESSAGE destination: /topic/room/1 content-type:application/json subscription:sub-0 message-id:dlfsrch-0 {"roomId":1, "type": MESSAGE", "sender":"client1"}
참고:
- 웹소켓 지원 환경
- https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#websocket-stomp
*틀린 부분이 있으면 언제든지 말씀해 주시면 공부해서 수정하겠습니다.