[Web] STOMP와 WebSocket, SockJS란?

박해인·2025년 5월 5일
0

Web

목록 보기
3/3
post-thumbnail

채팅이나 실시간 통신에 필수로 쓰이는 프로토콜인 WebSocket, 프론트엔드를 연동 중 Stomp 를 연결해달라는 말에 혼란이 왔다. Stomp 는 프로토콜 아닌가? 왜 Stomp를 연결해달라는 거지? 라는 의문이 생겼다. 이 참에 WebSocket과 STOMP 내친김에 SockJS까지 알아보자.

WebSocket이란?

웹소켓은 양방향 통신을 지원하는 프로토콜로, 실시간, 양방향 통신을 가능하게 하는 기술이다. 클라이언트와 서버 간에 한 번 연결이 이루어지면, 그 후로는 지속적으로 연결을 유지하며 데이터를 주고 받을 수 있다. 이를 통해 서버는 클라이언트에게 실시간으로 데이터를 푸시할 수 있다. 따라서 채팅 앱, 게임, 실시간 거래 정보 제공 등에 활용되고 있다.


HTTP, Polling과의 차이점

특징WebSocketHTTPPolling (Long Polling)
통신 방식양방향 통신 (클라이언트 ↔ 서버)단방향 통신 (클라이언트 → 서버)주기적인 요청 (클라이언트 → 서버)
연결 유지연결 유지 (한 번 연결 후 지속적)요청 후 연결 종료연결 유지 (주기적인 요청/응답)
실시간 데이터 처리실시간 데이터 푸시 가능실시간 처리 불편실시간 처리 비효율적
서버 푸시 기능서버가 클라이언트에게 데이터를 푸시 가능불가능 (클라이언트 요청 시 응답)클라이언트가 요청을 보낸 후 서버에서 응답
효율성매우 효율적 (지속적 연결, 실시간)비효율적 (매 요청마다 연결)비효율적 (주기적 요청, 많은 트래픽 발생)
예시채팅, 실시간 게임, 주식 거래 등웹 페이지 요청, API 호출 등실시간 데이터 업데이트가 필요한 경우 (Long Polling 사용)

STOMP란?

STOMP는 Simple Text Oriented Messaging Protocol 의 약자로, 웹소켓 위에서 동작하는 서브 프로토콜이다.
이는 클라이언트와 서버 간 메시지 교환에 있어 메시지의 형식, 목적지, 명령 유형 등을 표준화하는 역할을 한다.

WebSocket 자체는 텍스트 또는 바이너리 형식의 메시지 전송만 정의할 뿐, 그 내용이나 구조에 대해서는 전혀 규약이 없다.

따라서 WebSocket 만 사용하는 경우, 서버는 수신한 메시지가 채팅 메시지인지, 알림 메시지인지, 시스템 메시지인지 직접 구분해야 하며, 어떤 사용자에게 전달되는 메시지인지, 어떤 포맷(JSON, 텍스트 등)인지도 사용자가 명시적으로 정의하고 파싱해야 한다.

예를 들어, 순수 웹소켓으로 구현할 경우 다음과 같은 처리를 개발자가 직접 해야 한다 :

  • 메시지 타입 구분
  • 수신 대상 파악
  • 데이터 파싱 및 처리

하지만 STOMP를 사용하면, 메시지의 목적지(destination), 명령 유형(CONNECT, SEND, SUBSCRIBE, UNSUBSCRIBE, DISCONNECT), 헤더, 본문 등이 표준 형식으로 정의된다.

STOMP 공통포맷

COMMAND
header1:value1
header2:value2

Body^@

전체적으로 위 틀을 이 공통 포맷을 따른다.

1. CONNECT 프레임

WebSocket 위에서 STOMP 프로토콜로 연결 요청을 보낸다.

CONNECT
accept-version:1.2
host:yourserver.com

^@

2. SUBSCRIBE 프레임

서버가 연결을 승인하면 특정 채널이나 목적지를 구독한다.

SUBSCRIBE
id:sub-0
destination:/topic/chatroom.1234

^@
  • id : 구독을 식별할 고유 ID
  • destination : 구독하고자 하는 대상 토픽, 채널이라고 불리움 (ex.특정 채팅방)
  • ^@는 null 문자(0x00) : 메세지 끝을 표시한다.

3. SEND 프레임

채널로 메세지를 보낸다.

SEND
destination:/app/chat.sendMessage

{ "content": "Hello", "sender": "me" }

^@

4. UNSUBSCRIBE 프레임

특정 subscribe id의 구독을 해제할 때 사용한다.

UNSUBSCRIBE
id:sub-0

^@

5. DISCONNECT 프레임

서버와의 STOMP 연결을 종료할 때 사용한다.

DISCONNECT
receipt:77

^@

자 이제 WebSocket과 STOMP 개념은 구분이 잘 갔을 것이다. 그렇다면 클라이언트 입장에서는 STOMP를 어떻게 사용할까? 더불어 WebSocket 연결을 한다는 말과 STOMP 연결을 한다는 말이 무엇을 뜻할까?

- WebSocket 연결한다는 것
클라이언트가 서버에 WebSocket 연결을 설정하여, 이후 실시간으로 메시지를 주고받을 수 있게 됨을 의미한다.

- STOMP를 연결한다는 것
클라이언트가 WebSocket을 통해 STOMP 프로토콜로 메시지를 주고받겠다고 설정하는 것을 의미한다.

그렇다면 frontend 개발자 입장에서는 어떻게 연결을 하면 될까?

  1. WebSocket 연결을 위한 웹소켓 객체 생성하기
var socket = new WebSocket('ws://localhost:8080/chat');
  1. STOMP 클라이언트를 WebSocket 위에 얹어 사용한다.
    ( STOMP는 WebSocket위에서 동작하는 서브 프로토콜이다. )
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
    console.log('Connected: ' + frame);

    stompClient.subscribe('/topic/chat', function(messageOutput) {
        console.log('Received: ' + messageOutput.body);
    });

    stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({'content': 'Hello, STOMP!'}));
});

위의 방식도 있지만 stomp.js 라이브러리 덕에 우리는 websocket 연결 따로 stomp 연결 따로 하지 않아도 된다.

아래는 @stomp/stompjs 를 이용한 WebSocket, STOMP 연결 예시이다.

export function connectWebSocketWithStomp(token, roomId, onMessageCallback) {
  
  if (!token) {
    console.error("🚫 토큰이 없습니다.");
    return;
  }

    // STOMP Client 생성
  stompClient = new Client({
    brokerURL: `ws://211.47.114.99:10/v1/ws-chat/websocket?token=${token}`,
    debug: (str) => console.log("STOMP Debug:", str),
    reconnectDelay: 5000,
    heartbeatIncoming: 4000,
    heartbeatOutgoing: 4000,

    // STOMP로 WebSocket 연결
    onConnect: (frame) => {
      console.log("✅ STOMP 연결 성공", frame);
    
    // STOMP 채팅 채널 구독
      stompClient.subscribe(`/topic/chat/${roomId}`, (message) => {
        console.log("📩 받은 메시지:", message.body);
        if (onMessageCallback) {
          onMessageCallback(JSON.parse(message.body));
        }
      });
    },
    onStompError: (frame) => {
      console.error("❌ STOMP 에러 발생", frame.headers["message"]);
      console.error("🔍 상세 내용:", frame.body);
    },
  });
  stompClient.activate();
}

위에서 주의할 점은, WebSocket 자체는 HTTP처럼 커스텀 헤더를 지원하지 않는다.
만약에 백엔드 개발자가 accessToken 을 헤더로 보내달라고 요청했다면 어떻게 할까?
→ 그냥 쿼리 파라미터로 토큰 넘기면 된다 ㅇㅇ..

brokerURL: `ws://211.47.114.99:10/v1/ws-chat/websocket?token=${token}`,

그렇기 때문에 위처럼 쿼리 파라미터로 토큰을 넘겨주니 잘 연결됐다.


SockJS

돌이켜보니, 예전에 졸업프로젝트를 진행할 때 실시간 알림을 구현하기 위해서 WebSocekt을 채택하여 구현했던 기억이 있다. 이 때 sockJS를 적용하니 안됐던 기억이 있다. 당시에는 Chat GPT도 없던 때라 하나하나 메서드를 지워가며 오류를 찾았던 기억이 있는데 .withSockJS() 메서드가 있을 때 없을 때 차이가 났다.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer{
	
    @Override
	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
		registry.addHandler(myHandler(),"/user")
		.addHandler(socketTextHandler(),"/text")
		.setAllowedOrigins("*")
        .withSockJS();
	}
    
	@Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }
	
	@Bean
	public WebSocketHandler socketTextHandler() {
		return new SocketTextHandler();
	}
}

위에서 WebSocket Handler를 등록하는 메서드에서,

.withSockJS();

라는 코드가 문제가 됐었었다 그럼 SockJS가 뭔지부터 살펴보자

SockJS란?

SockJS는 웹소켓을 지원하지 않는 환경에서 WebSocket 기능을 대체하는 WebSocket 에뮬레이션 라이브러리이다.

WebSocket Emulation 이란?
1. Websocket 연결시도
2. 실패 -> 지원하지 않음
3. HTTP Streaming, Long Polling 등으로 양방향 통신 시도

의 방식으로 WebSocket을 대체한다.

웹소켓의 경우 모든 환경에서 지원하지 않는다. 이 때 SockJS를 이용하면 웹소켓을 지원하지 않는 환경에서도 가능하게 해준다.

어플리케이션이 WebSocket API를 사용하도록 허용하지만, 만약 내가 사용하는 클라이언트 브라우저가 WebSocket을 받아들이지 못하는 상태라면 어플리케이션 코드 변경 없이 런타임 대안을 실행하기 위한 것이다.

이런 상황에서 사용하는 것이 WebSocket Emulation이다.

나는 당시 안드로이드 환경과 Chrome 환경을 사용했었는데 안드일때는 OkHttp라는 라이브러리가 WebSocket을 지원해줬고, 웹개발할때는 위처럼 거의 대부분의 Chrome 이 WebSocket을 지원하고 있었다. ( https://caniuse.com/ 참고 )

그런데 왜 .withSockJS() 메서드 하나만으로 WebSocket 통신이 막혀버리는 것일까. 개념대로라면 SockJS는 WebSocket 연결실패 대책 아닌가?

→ 일부 서버는 SockJS에서 사용하는 xhr-streaming, eventsource 등을 CORS 또는 보안 정책 때문에 막아놓을 수 있다고 한다.
이 경우 withSockJS()를 사용하면 연결은 되지만 실제 통신이 막힌다.
웹 개발할 때 기억을 가다듬어 보니, CORS Error가 떴던 것 같다...

→ 안드로이드에서는 왜 안되는지 잘 모르겠다 ^^;; 사실 테스트 상에서만 안되고 Android에서는 작동했을 수도 있다.. 기억 미흡..

암튼 webSocket에서 CORS에러를 해결할 수도 있지만.. 웹개발을 하는 중이라면 요새 브라우저들은 webSocket을 대부분 지원하니 정신건강에는 그냥 sockJS를 빼는게 좋을 것이다..

여러분들은 이 점 참고하시어 즐개발하시길 🍀

참고

profile
갓생살고싶어라

0개의 댓글

Powered by GraphCDN, the GraphQL CDN