스택큐힙리스트

Spring Boot 실시간 채팅 서버 만들기 — WebSocket + STOMP, 하트비트까지 완전 정복 본문

개발

Spring Boot 실시간 채팅 서버 만들기 — WebSocket + STOMP, 하트비트까지 완전 정복

스택큐힙리스트 2025. 7. 23. 08:41
반응형

왜 WebSocket + STOMP인가?

채팅·알림처럼 양방향 저지연이 필수인 기능은 HTTP Polling이나 SSE만으로 한계가 있습니다. WebSocket으로 영구 연결을 열고, STOMP 프로토콜로 채널·헤더·구독 개념을 입혀주면 메시지 라우팅이 쉬워집니다. Spring Boot는 spring-websocket 모듈 하나로 이 조합을 깔끔히 지원해 왔고, 2025년 현재 Boot 3.3에서도 설정이 거의 변하지 않았습니다.


1️⃣ 의존성 한 줄

implementation("org.springframework.boot:spring-boot-starter-websocket")

외부 브로커(RabbitMQ, Redis Streams 등)를 쓰려면 대응 스타터를 추가하세요.


2️⃣ WebSocket 설정 클래스

@Configuration
@EnableWebSocketMessageBroker
class WebSocketConfig(
    private val taskScheduler: TaskScheduler   // 하트비트용
) : WebSocketMessageBrokerConfigurer {

    override fun registerStompEndpoints(registry: StompEndpointRegistry) {
        registry.addEndpoint("/ws")            // 핸드셰이크 경로
                .setAllowedOrigins("https://myapp.com")
                .withSockJS()                  // 레거시 브라우저 대처
    }

    override fun configureMessageBroker(registry: MessageBrokerRegistry) {
        registry.enableSimpleBroker("/topic", "/queue")
                .setHeartbeatValue(longArrayOf(10_000, 20_000)) // S↔C 10s/20s
                .setTaskScheduler(taskScheduler)                // 필수!
        registry.setApplicationDestinationPrefixes("/app")      // 서버 수신 네임스페이스
    }
}

TaskScheduler가 없으면 심플 브로커는 하트비트를 못 보냅니다. 값은 [서버보내기, 서버받기] ms 쌍으로, 0이면 해당 방향 하트비트 비활성화.


3️⃣ 클라이언트(Javascript) 연결 예시

import { Client } from '@stomp/stompjs';

const client = new Client({
  brokerURL: 'wss://myapp.com/ws',
  debug: console.log,
  reconnectDelay: 5000,              // 자동 재연결
  heartbeatIncoming: 20000,          // 클라이언트→서버
  heartbeatOutgoing: 10000           // 서버→클라이언트
});
client.activate();

@stomp/stompjs가 탭 비활성 상태에서 JS 스레드가 멈춰 하트비트를 못 보내는 이슈가 있습니다. 실무에서는 heartbeatIncoming=0으로 끊김을 감수하고 서버만 보내도록 해도 안정적이라는 사례가 많습니다.


4️⃣ 하트비트 전략 베스트 프랙티스

  1. 10s/20s 규칙 — 서버는 10 초마다 ping, 20 초 이상 무응답이면 연결 종료. Spring 문서도 기본 예제로 권고합니다.
  2. 대규모 부하 테스트 — 수천 클라이언트를 한 머신에서 시뮬레이션할 땐 클라이언트 하트비트를 끄세요. 연결마다 스케줄 태스크가 생겨 성능 편차가 커집니다.
  3. 브라우저 백그라운드 — 모바일·데스크톱 브라우저가 탭을 잠그면 JS 타이머가 멈춥니다. 서버 단에서만 하트비트를 보내고, 클라이언트는 재연결 로직으로 복원하세요.
  4. 외부 브로커 — RabbitMQ / Artemis 같은 STOMP 브로커를 쓰면 클러스터 환경에서도 하트비트를 자동 중계해 줍니다. 반대로 심플 브로커는 인메모리라 인스턴스 간 공유가 안 되므로 스케일아웃 시 반드시 외부 브로커나 Redis Pub/Sub를 병행하세요.
  5. TLS 필수 — wss://로 업그레이드하지 않으면 중간 프록시가 하트비트를 잘라내거나 연결을 강제로 종료하는 사례가 있습니다.

5️⃣ 핸드셰이크·메시지 흐름, 한눈에

  1. Handshake — 브라우저가 /ws로 Upgrade: websocket 요청 → Spring의 DefaultHandshakeHandler가 101 응답, 세션 생성.
  2. Send — 클라가 /app/chat.send 등으로 메시지 전송 → @MessageMapping 메서드 → 브로커(/topic/room1)로 publish.
  3. Subscribe — 클라가 /topic/room1 구독 → 브로커가 세션 목록에 추가 → 새 메시지 오면 모든 세션에 push.
  4. Heartbeat — taskScheduler 트리거로 서버→클라 빈 프레임 전송, 클라 응답 대기 → 타임아웃 시 SessionDisconnectEvent.

6️⃣ 통합 테스트 팁

WebSocketStompClient + taskScheduler를 주입해 실제 포트를 열고, client.connect() 후 subscribe() 콜백에서 CountDownLatch를 풀어 성공 여부를 검증하세요. 하트비트 간격을 짧게(1s) 줄이면 테스트가 빨라집니다.


마치며

40줄 남짓의 설정으로도 Spring Boot 3.x에서 안 끊기는 채팅 서버를 만들 수 있습니다. 하트비트만 제대로 잡아두면 브라우저 백그라운드·네트워크 홀수 마이크로단절까지 대부분 커버됩니다. “속도는 WebSocket, 구조화는 STOMP” — 이 조합은 2025년에도 여전히 최강입니다.

반응형
Comments