Spring Boot WebSocket

유기훈·2025년 3월 15일

깃헙: https://github.com/62hoon99/mvc-chatting

WebSocket

WebSocket 개념

WebSocket은 HTTP와 다르게, 연결을 유지하며 양방향 통신이 가능한 프로토콜

  • HTTP: 요청(Request)과 응답(Response) 기반 (단방향)
  • WebSocket: 연결을 유지하고 실시간 양방향 데이터 전송 가능

Spring Boot에서 WebSocket 사용

사용 흐름

1. 클라이언트가 WebSocket 연결을 요청

ws://localhost:8080/ws-chat

2. 서버가 STOMP 메시지 처리

  • /app으로 시작하는 메시지는 컨트롤러(@MessageMapping)에서 처리
  • /topic으로 시작하는 메시지는 구독한 모든 클라이언트에게 전달

3. 클라이언트가 메시지 전송

stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({...}));

4. 서버가 해당 메시지를 처리하고 구독자에게 전달

messagingTemplate.convertAndSend("/topic/chat/" + roomId, message);

5. 클라이언트가 /topic/chat/{roomId}을 구독해서 메시지 수신

사용 예시

WebSocket 설정

  • 클라이언트가 구독하는 경로를 설정한다.
  • 클라이언트가 메시지를 전송하는 경로를 설정한다.
  • 클라이언트와 서버가 웹소켓 연결을 수립할 경로를 설정한다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic"); // 구독용 경로 설정
        registry.setApplicationDestinationPrefixes("/app"); // 메시지 전송 경로
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws-chat") // 웹소켓 엔드포인트 등록
                .withSockJS(); // SockJS 지원 추가
    }
}

WebSocket Controller

  • 클라이언트에서 서버로 전송된 메시지를 받는 컨트롤러이다.
  • (아래 코드 참고) /app/chat.sendMessage 경로로 클라이언트가 보낸 메시지를 /topic/chat/{roomId} 경로를 구독하고 있는 클라이언트에게 전달한다.
@Controller
@RequiredArgsConstructor
public class ChatController {

    private final SimpMessagingTemplate messagingTemplate;

    @MessageMapping("/chat.sendMessage")
    public void sendMessage(@Payload ChatMessage chatMessage) {
        String roomId = chatMessage.getRoomId(); // ChatMessage 안에 있는 roomId 가져오기
        messagingTemplate.convertAndSend("/topic/chat/" + roomId, chatMessage);
    }

    @MessageMapping("/chat.addUser")
    public void addUser(@Payload ChatMessage chatMessage, SimpMessageHeaderAccessor headerAccessor) {
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        String roomId = chatMessage.getRoomId(); // ChatMessage 안에 있는 roomId 가져오기
        messagingTemplate.convertAndSend("/topic/chat/" + roomId, chatMessage, headerAccessor.toMap());
    }

}

클라이언트

  • 아래 코드는 타임리프로 작성된 html 이다.

(아래 코드 참고) 동작 방식
1. /ws-chat 경로로 웹소켓 연결 요청을 보낸다.
2. 연결이 수립되면 /topic/chat/{roomId} 경로를 구독한다. 그리고 /app/chat.addUser 경로로 json 형식의 메시지를 보낸다.
3. 사용자가 메시지를 보내면 /app/chat.sendMessage 경로로 메시지를 보낸다.
4. 구독한 경로(/topic/chat/{roomId})에서 메시지를 수신하면 화면에 받은 메시지를 추가한다.

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>채팅방</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.1/dist/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
<h2 th:text="'채팅방: ' + ${roomId}"></h2>
<h3 th:text="'사용자: ' + ${username}"></h3>

<div id="chat-box">
    <ul id="messageArea"></ul>
</div>

<input type="text" id="messageInput" placeholder="메시지를 입력하세요..." />
<button onclick="sendMessage()">전송</button>

<a href="/chat/">채팅방 목록으로</a>

<script>
    var username = "[[${username}]]";
    var roomId = "[[${roomId}]]";

    var socket = new SockJS('/ws-chat');
    var stompClient = Stomp.over(socket);

    stompClient.connect({}, function () {
        stompClient.subscribe('/topic/chat/' + roomId, function (message) {
            showMessage(JSON.parse(message.body));
        });

        stompClient.send("/app/chat.addUser", {}, JSON.stringify({
            sender: username,
            type: "ENTER",
            roomId: roomId,
            content: username + "님이 입장했습니다."
        }));
    });

    function sendMessage() {
        var messageContent = document.getElementById("messageInput").value;
        if (messageContent) {
            var chatMessage = {
                sender: username,
                content: messageContent,
                type: "CHAT",
                roomId: roomId
            };

            stompClient.send("/app/chat.sendMessage", {}, JSON.stringify(chatMessage));
            document.getElementById("messageInput").value = "";
        }
    }

    function showMessage(message) {
        var messageArea = document.getElementById("messageArea");
        var li = document.createElement("li");
        li.textContent = message.sender + ": " + message.content;
        messageArea.appendChild(li);
    }
</script>
</body>
</html>
profile
개발 블로그

0개의 댓글