[Spring Boot] WebSocket & STOMP을 활용한 실시간 채팅 구현

이성민·2025년 5월 21일
post-thumbnail

이미지 출처 : https://asfirstalways.tistory.com/359

WebSocket이란?

WebSocket은 웹 앱과 서버 간의 지속적인 연결(persistent connection) 을 유지하여 양방향 통신을 가능하게 하는 프로토콜입니다. 기존 HTTP 통신의 단점을 보완하여, 클라이언트와 서버가 서로 데이터를 실시간으로 주고받을 수 있는 구조를 제공합니다.


WebSocket이 등장한 이유

초기 웹 통신은 Request → Response 구조인 HTTP 프로토콜 기반의 단방향 통신이었습니다. 실시간 알림, 채팅 등에서는 다음과 같은 문제가 있었습니다:

  • 서버가 클라이언트에게 능동적으로 알림을 줄 수 없음
  • 클라이언트가 계속해서 서버에 주기적으로 polling 요청을 보내야 함 → 서버 부하 증가
  • 빠른 반응이 필요한 서비스에서는 지연 시간 발생

이러한 문제를 해결하기 위해 WebSocket이 등장했습니다.


WebSocket의 특징

  • 양방향 통신 (Full-Duplex)
    → 서버와 클라이언트가 동시에 데이터를 주고받을 수 있습니다.

  • 지속적인 연결 유지
    → HTTP와 달리, 요청-응답 후 연결이 끊기지 않고 계속 유지됩니다.

  • 헤더 오버헤드 없음
    → 한번 연결되면 그 후에는 가벼운 패킷만 주고받아 효율적입니다.

  • 실시간 서비스에 최적화
    → 채팅, 게임, 주식 시세, 알림 등 실시간성이 중요한 서비스에 적합합니다.


WebSocket의 단점

  • 서버 부하 증가
    WebSocket은 연결을 계속 유지하기 때문에, 수천~수만 개의 커넥션을 동시에 처리할 경우 서버 리소스를 많이 차지합니다. (polling을 이용했을 때의 부하랑 다름)

  • 연결 상태 관리 필요
    클라이언트 연결이 끊기거나 에러가 발생할 경우 재연결 로직을 프론트에서 구현해야 하며, 서버에서도 상태를 적절히 관리해야 합니다.


WebSocket 동작 구조

1. Handshake (HTTP → WebSocket 업그레이드)

WebSocket은 초기에는 HTTP로 연결 요청을 보내고, Upgrade 헤더를 통해 WebSocket으로 프로토콜 전환을 요청합니다.

요청 예시:

GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13

응답 예시:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=

→ 이 응답이 오면 클라이언트와 서버는 WebSocket 연결을 맺고 실시간으로 통신할 수 있습니다.

2. 데이터 전송

WebSocket 연결 이후에는 클라이언트와 서버가 자유롭게 데이터를 주고받습니다. 이때 전송되는 데이터는 메시지(message) 단위로 나뉘며, 내부적으로는 여러 프레임(frame) 으로 구성됩니다.

3. 연결 종료

어느 한 쪽에서 종료 요청을 보내면 WebSocket 연결은 닫히며 양측 모두 close 이벤트를 받습니다.


STOMP란?

WebSocket은 실시간 통신에 뛰어난 성능을 자랑하지만, 채팅방 구현처럼 메시지를 주제별로 구독/발행하거나, 사용자별 라우팅, 접속/해제 상태 관리까지 직접 처리해야 하는 경우에는 한계가 있습니다.

이를 해결하기 위한 프로토콜이 STOMP (Simple Text Oriented Messaging Protocol) 입니다.

STOMP는 WebSocket 위에서 동작하는 메시징 프로토콜로, HTTP처럼 명확한 명령과 구조를 제공합니다. 메시지는 텍스트 기반이며 헤더와 바디로 구성되어, 서버와 클라이언트 간 통신을 더 구조화할 수 있습니다.

STOMP의 역할과 기능

  • CONNECT: 클라이언트가 서버에 연결할 때 사용
  • SEND: 특정 목적지로 메시지를 보냄
  • SUBSCRIBE: 특정 주제(채널)를 구독하여 메시지를 받을 수 있음
  • UNSUBSCRIBE: 구독 해제
  • DISCONNECT: 연결 종료

즉, WebSocket이 단순히 데이터를 보내는 통신선을 만든다면, STOMP는 그 위에서 "메시지를 어떻게 전달하고 받아야 하는지에 대한 규칙"을 만드는 역할입니다.


Spring Boot에서 WebSocket + STOMP 사용 예시

Spring에서는 STOMP를 이용해 다음과 같은 고급 메시징 기능을 구현할 수 있습니다:

  • @MessageMapping으로 서버 내 라우팅 구성
  • 클라이언트가 /pub/... 주소로 메시지를 보내고,
  • 서버는 /sub/... 주소를 구독한 클라이언트에게 실시간 메시지를 전달
  • 내부 메시지 전달은 SimpMessagingTemplate으로 처리

STOMP 덕분에 서버 코드는 단순해지고, 프론트는 주제만 잘 구독하면 실시간 데이터를 받을 수 있게 됩니다.


WebSocket + STOMP 구조 (Spring Boot 기반 실시간 채팅)

실제로 제가 구현한 실시간 채팅 기능은 다음과 같은 구성입니다:

WebSocket 설정 (백엔드)

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub");
        registry.setApplicationDestinationPrefixes("/pub");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws/chat")
                .setAllowedOriginPatterns("*")
                .withSockJS();
    }
}

메시지 컨트롤러

@Controller
@RequiredArgsConstructor
public class ChatSocketController {

    private final ChatMessageService chatMessageService;
    private final UserService userService;
    private final SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/chat/send")
    public void send(@Payload ChatMessageSocketDto dto) {
        User sender = userService.getUserById(dto.getSenderId());
        ChatMessageResponseDto savedMessage = chatMessageService.sendMessage(dto.getRoomId(), sender, dto.getContent());

        messagingTemplate.convertAndSend("/sub/chat/room/" + dto.getRoomId(), savedMessage);
    }
}

결론

  • WebSocket은 실시간 채팅처럼 빠른 응답이 필요한 서비스에서 매우 유용
  • 하지만 메시지 구조, 구독/발행 같은 고급 기능은 STOMP가 함께 해야 실용적임
  • Spring에서는 @MessageMapping, SimpMessagingTemplate, 그리고 STOMP 조합으로 구독 기반 채팅 시스템을 깔끔하게 구현할 수 있음
  • 프론트에서는 채널(/sub/...)을 구독하고 메시지를 /pub/...으로 보내는 방식으로 쉽게 연동 가능
  • 실시간 서비스에서는 WebSocket + STOMP 조합이 강력한 선택지
profile
BE 개발자

0개의 댓글