✅ 1. WebSocket이란?
🔷 HTTP와의 차이부터 이해해보자
항목 | HTTP | WebSocket |
연결 방식 | 요청 → 응답만 1회 | 양방향 연결 유지 |
서버 → 클라이언트 푸시 | ❌ 불가능 | ✅ 가능 |
실시간성 | ❌ 느림 | ✅ 빠름 |
📌 예시:
- HTTP: 클라이언트가 서버에 요청하고, 응답 오면 끝. → 다시 요청해야 새 메시지 받음.
- WebSocket: 클라이언트와 서버가 한번 연결되면 계속 이어짐 → 서버가 실시간으로 메시지를 보낼 수 있음!
🔁 WebSocket 흐름
- 브라우저가 서버에 “WebSocket 열어줘!” 요청
- 서버가 “좋아, 열어줄게” 하고 연결 승인
- 그 다음부터는 클라이언트 <=> 서버 양방향 통신 가능
- 채팅 메시지를 바로바로 주고받을 수 있음 (지연 거의 없음)
✅ 2. STOMP란?
WebSocket은 “통신 파이프”고, STOMP는 그 안에서 메시지를 어떤 규칙으로 주고받을지 정의한 프로토콜.
🔷 STOMP 용어 요약
용어 | 설명 |
@MessageMapping | 클라이언트가 어디로 메시지를 보내는지 설정 |
@SendTo / /sub/... | 서버가 어디로 메시지를 전송(브로드캐스트) 하는지 설정 |
@Payload | 실제로 주고받는 메시지 내용 |
subscribe | 클라이언트가 서버의 메시지를 구독 |
send | 클라이언트가 서버에 메시지를 보냄 |
✅ 이 프로젝트에서는 어떻게 쓸까?
📦 사용 목적
구성 요소 | 역할 |
WebSocket | 클라이언트와 서버 간 연결 유지 |
STOMP | 메시지를 채팅방 별로 구분하고 전송함 (/pub/chat/message, /sub/chat/room/{roomId} 등) |
✅ 흐름 예시 (채팅방 1번에 메시지 보내기)
- 클라이언트 → 서버로 메시지 보냄:
- stomp.send("/pub/chat/message", {}, { roomId:1, message:"안녕" })
- 서버 → 같은 채팅방을 구독 중인 모든 사람에게 브로드캐스트:
- @SendTo("/sub/chat/room/1")
✅ 요약
항목 | 설명 |
WebSocket | 실시간 연결을 위한 통신 기술 |
STOMP | 그 위에서 메시지를 잘 주고받기 위한 약속/규칙 |
우리는 | WebSocket + STOMP를 Spring에서 함께 사용해서 실시간 채팅 구현 중 |
WebSocketConfig 설정하기
package com.example.chat.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 클라이언트가 연결할 엔드포인트 (SockJS 지원)
registry.addEndpoint("/ws-chat")
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
// pub은 클라이언트 -> 서버 전송 prefix
registry.setApplicationDestinationPrefixes("/pub");
// sub은 서버 -> 클라이언트 브로드캐스트 prefix
registry.enableSimpleBroker("/sub");
}
}
ChatController 설정하기
package com.example.chat.controller;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
import com.example.chat.dto.request.ChatMessageDto;
import lombok.RequiredArgsConstructor;
@Controller
@RequiredArgsConstructor
public class ChatController {
private final SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat/message") // /pub/chat/message
public void sendMessage(@Payload ChatMessageDto chatMessageDto) {
String roomId = "/sub/chat/room" + chatMessageDto.getRoomId();
messagingTemplate.convertAndSend(roomId, chatMessageDto);
}
}
WebSocketMessageBrokerConfigurer란?
더보기
WebSocketConfig 클래스는 WebSocketMessageBrokerConfigurer를 구현하고 있음.
✅ 1. WebSocketMessageBrokerConfigurer란?
Spring에서 WebSocket + STOMP 통신을 설정할 수 있게 해주는 인터페이스.
이 인터페이스에는 여러 메서드들이 존재하는데, 전부 다 구현할 필요는 없음.
원하는 기능만 오버라이드해서 설정하면 됨.
🔧 자주 쓰는 메서드들 정리
메서드 | 역할 | 사용 여부 |
registerStompEndpoints() | 클라이언트가 접속할 WebSocket 엔드포인트 등록 | ✅ 사용함 |
configureMessageBroker() | 메시지 브로커(prefix) 설정 (pub/sub 경로) | ✅ 사용함 |
configureClientInboundChannel() | 클라이언트 → 서버 메시지 처리 커스터마이징 | ❌ 안 씀 (필요 시 필터 추가 등) |
configureClientOutboundChannel() | 서버 → 클라이언트 메시지 커스터마이징 | ❌ |
addArgumentResolvers() | @MessageMapping에 들어오는 파라미터 분석기 설정 | ❌ |
configureWebSocketTransport() | 메시지 크기, 버퍼 설정 등 | ❌ |
→ 지금은 가장 핵심 2개만 사용한 것이야. 나머지는 고급 기능이 필요할 때만 설정해.
✅ 2. registerStompEndpoints() 메서드 설명
📌 이건 뭐 하는 함수?
클라이언트가 실제로 WebSocket 연결을 시도할 주소(/ws-chat) 를 지정하는 메서드야.
registry.addEndpoint("/ws-chat")
즉, 클라이언트는 ws://localhost:8080/ws-chat 으로 연결을 시도하게 됨!
📌 그 다음 .setAllowedOriginPatterns("*") 이건 뭐야?
이건 CORS(Cross-Origin Resource Sharing) 관련 설정.
- 기본적으로 브라우저는 다른 도메인 간의 WebSocket 연결을 차단하려고 함.
- 이걸 풀어주지 않으면, 프론트가 연결을 못 함.
- "*"로 설정하면 모든 출처에서 연결 가능하도록 허용해주는 것.
예: localhost:3000 프론트가 localhost:8080/ws-chat으로 연결하려하면 막히는데, 이 설정이 있으면 허용됨!
📌 마지막 .withSockJS() 이건 뭔데?
SockJS는 **WebSocket이 안 되는 환경에서도 fallback(대체 통신)**을 가능하게 해주는 기술.
예: WebSocket을 지원하지 않는 브라우저 (옛날 IE 등)에서는 대신 HTTP 롱 폴링이나 AJAX로 대체해서 동작하게 함
그래서 withSockJS()를 쓰면:
- 브라우저가 WebSocket 지원하면 WebSocket을 씀
- 안 되면 자동으로 fallback 방식으로 대체해줌
개발/테스트 환경에서는 안정성을 위해 넣는 게 일반적
✅ 결과 요약: 왜 이 2개만 설정했는가?
설정 메서드이유registerStompEndpoints() | 클라이언트 연결 주소(/ws-chat)와 fallback(SockJS)을 설정하기 위해 |
configureMessageBroker() | 메시지를 주고받을 pub/sub 주소 prefix를 설정하기 위해 |
나머지 | 고급 기능 or 성능 튜닝 단계에서 필요하므로 지금은 생략 가능 |
- 나중에 configureClientInboundChannel() 같은 메서드를 이용해서 JWT 필터를 끼워넣을 수도 있고,
- 메시지 사이즈나 보낼 수 있는 최대 길이 등을 configureWebSocketTransport()에서 조절할 수도 있음.
✅ 1. configureMessageBroker() 메서드의 의미
이 메서드는 STOMP 메시지를 어떤 경로로 주고받을지 규칙을 설정하는 곳.
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/pub"); // ①
registry.enableSimpleBroker("/sub"); // ②
}
🔹 ① setApplicationDestinationPrefixes("/pub")
- 클라이언트가 서버에게 “보낼 때” 사용하는 경로 prefix.
- @MessageMapping("/chat/message")는 내부적으로 /pub/chat/message에 매핑.
- 즉, 클라이언트가 이 경로로 메시지를 보내면, 서버는 해당 컨트롤러 메서드로 처리함.
예: 클라이언트가 /pub/chat/message로 보내면 → 서버의 @MessageMapping("/chat/message") 메서드가 처리함.
🔹 ② enableSimpleBroker("/sub")
- 서버가 클라이언트에게 **“보내주는 쪽 경로(prefix)”**.
- 클라이언트는 /sub/... 경로를 구독(subscribe)하면 거기로 메시지를 받게 됨.
예: 서버가 /sub/chat/room에 메시지를 보내면 → /sub/chat/room을 구독 중인 모든 클라이언트가 메시지를 받음.
✅ 2. 이걸 실제로 쓰는 코드 예시 분석
@MessageMapping("/chat/message") // ①
@SendTo("/sub/chat/room") // ②
public ChatMessage sendMessage(@Payload ChatMessage chatMessage) { // ③
return chatMessage;
}
🔹 ① @MessageMapping("/chat/message")
- 클라이언트가 /pub/chat/message로 메시지를 보내면 이 메서드가 실행됨
- /pub은 위에서 지정한 prefix,
- /chat/message는 메서드의 STOMP 주소
🔹 ② @SendTo("/sub/chat/room")
- 이 메서드가 리턴한 값을, /sub/chat/room을 구독 중인 모든 클라이언트에게 전송함
즉, 서버가 메시지를 브로드캐스트하는 경로
🔹 ③ @Payload
- 클라이언트가 보낸 **메시지 본문(json)**을 자동으로 ChatMessage 객체로 변환
- @RequestBody와 비슷한 역할(WebSocket용)
✅ 정리: 클라이언트 입장에서 흐름
단계 | 설명 |
1. 연결 | ws://localhost:8080/chat 으로 연결 (엔드포인트) |
2. 구독 | stomp.subscribe("/sub/chat/room") |
3. 메시지 보냄 | stomp.send("/pub/chat/message", {}, JSON.stringify({...})) |
4. 서버 처리 | @MessageMapping("/chat/message") 메서드 실행 |
5. 메시지 전송 | @SendTo("/sub/chat/room")로 메시지 전송됨 |
✅ 추가로 해줄 것
- 엔드포인트 /chat으로 바꾸기:
registry.addEndpoint("/chat")
.setAllowedOriginPatterns("*")
.withSockJS();
필요하면 이 흐름에 맞는 클라이언트 JS 코드나 테스트 HTML도 만들어줄게.
다음으로 가볼까? Redis 연동 or 메시지 저장 or 프론트 테스트 중 어떤 걸 하고 싶어?
SimpMessagingTemplate
더보기
WebSocket + STOMP 기반에서 “서버 → 클라이언트”로 메시지를 보낼 때 사용하는 스프링 전용 컴포넌트.
✅ 그렇다면 꼭 써야 해?
🔹 꼭 써야 하진 않지만, “상황에 따라 필수”임.
상황 | 필요 여부 |
고정된 경로로만 브로드캐스트 | ❌ 안 써도 됨 (@SendTo로 처리 가능) |
동적 경로로 보내야 함 (방 ID별로) | ✅ 꼭 필요함 |
특정 사용자에게만 전송해야 함 | ✅ 필요 (convertAndSendToUser) |
Redis 등 외부 이벤트로 메시지 보낼 때 | ✅ 필요 (컨트롤러 밖에서 메시지 전송 가능) |
✅ 예시 비교
🔸 1) @SendTo 방식 (간단하지만 고정)
@MessageMapping("/chat/message")
@SendTo("/sub/chat/room")
public ChatMessageDto send(ChatMessageDto message) {
return message;
}
장점: 간단함
단점: 방 ID마다 다른 구독 채널 못 씀 (모든 방이 /sub/chat/room에 모임)
🔸 2) SimpMessagingTemplate 방식 (유연)
messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
장점: 방 ID별 구독, 유저별 전송, 비동기 전송, 컨트롤러 외부에서도 사용 가능
단점: 코드가 조금 더 직접적 (하지만 실무에선 필수 수준)
✅ 요약
질문 | 답변 |
SimpMessagingTemplate는 언제 써? | WebSocket + STOMP 기반에서 동적으로 메시지를 브로커로 보낼 때 |
안 써도 가능? | 단순한 브로드캐스트만 한다면 가능하지만 실무에선 거의 필수 |
왜 써야 해? | 채팅방, 유저 등 구독 경로가 유동적인 경우 대응 가능하기 때문 |
'코딩 > sparta TIL' 카테고리의 다른 글
MongoDB & Docker 명령어 기록 (0) | 2025.06.05 |
---|---|
서버 분리 후 정보 주고 받는 방법 메모 (0) | 2025.06.02 |
MongoDB 사용법 (1) | 2025.06.02 |
MongoDB + Docker 연동하기 (Spring, Java, IntelliJ) (0) | 2025.06.02 |
한 프로젝트에서 서버를 분리하는 방법과 프로젝트별 서버 분리 후 연동하는 방법 (백엔드) (0) | 2025.05.29 |