Java/Spring Boot

[Java] Spring Boot WebSocket + STOMP 이해하고 구성하기 -1: 초기 구성 및 간단 소켓 연결

adjh54 2024. 9. 28. 13:07
해당 글에서는 Spring Boot 기반 WebSocket에 대해 이해하고 초기 설정 이후 WebScocket에 연결하는 방법에 대해 알아봅니다


 
 

1) Spring Boot WebSocket


💡 Spring Boot WebSocket

- Spring Framework에서 제공하는 기능으로, 실시간 양방향 통신을 가능하게 해 줍니다. Web Socket을 사용하면 서버와 클라이언트 간의 연결이 지속적으로 유지되어, 데이터를 실시간으로 주고받을 수 있습니다.

- 이는 채팅 애플리케이션, 실시간 알림 시스템, 주식 거래 플랫폼 등에서 유용하게 사용됩니다.
https://blog.amigoscode.com/p/6-common-ways-to-build-apis
💡 [참고] 소켓 통신에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.

[데이터 통신] 소켓 통신(Socket Communication) 이해하기 : 송신-수신 통신 과정

해당 글에서는 소켓 통신의 흐름의 이해를 돕기 위해 작성한 글입니다.1) 소켓 통신(Socket Communication)💡 소켓 통신(Socket Communication)- 네트워크에서 두 컴퓨터 간의 ‘실시간 양방향 통신’을 제공

adjh54.tistory.com

 
 

1. STOMP(Simple or Streaming Text Oriented Messaging Protocol)


💡 STOMP(Simple or Streaming Text Oriented Messaging Protocol)

- 텍스트 기반의 메시징 프로토콜을 의미합니다. 클라이언트와 메시지 브로커 간의 통신을 간단하고 효율적으로 수행할 수 있도록 설계되었습니다. 이는 WebScoket에서 쉽게 메시지를 주고받을 때 사용이 됩니다.
- 해당 프로토콜은 Websocket을 사용하여 클라이언트와 서버 간의 메시지 교환을 구조화하고 표준화하는 데 사용됩니다.
명령어설명
CONNECT클라이언트가 서버에 연결을 요청할 때 사용합니다.
SEND클라이언트 또는 서버가 특정 목적지로 메시지를 보낼 때 사용합니다.
SUBSCRIBE클라이언트가 특정 목적지의 메시지를 구독할 때 사용합니다.
UNSUBSCRIBE클라이언트가 특정 목적지의 메시지 구독을 취소할 때 사용합니다.
DISCONNECT클라이언트가 서버와의 연결을 종료할 때 사용합니다.

Getting Started | Using WebSocket to build an interactive web application

In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t

spring.io

 
 

💡 [참고] WebSocket 내에서 Java에서의 Stomp도 있지만, Node 기반의 Socket.io도 존재합니다. 이에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.

[Node] Socket.io 기반 소켓 서버 구축 방법 -1 : 구성 요소 및 흐름 + React 기반 채팅 화면 구성

해당 글에서는 소켓 통신을 위해 Socket.io를 기반으로 소켓 서버를 구현하고, 채팅을 하는 예시를 구축해 봅니다.  💡 [참고] 아래의 Socket.io와 관련된 글들을 참고하시면 도움이 됩니다분류링크S

adjh54.tistory.com

 
 
 

2) Spring Boot WebSocket 구성요소


 

1. WebSocketConfigurer 인터페이스


💡WebSocketConfigurer 인터페이스

- Spring Framework에서 WebSocket 구성을 위한 인터페이스입니다. 이 인터페이스를 구현하면 WebSocket 엔드포인트와 핸들러를 등록할 수 있습니다.

- 즉, 해당 엔드포인트는 클라이언트가 '웹 소켓을 연결'하기 위한 엔드포인트 입니다. Spring Boot Server가 로컬에서 실행하고 8080 포트인 경우 http://localhost:8080/[엔드포인트]로 접근을 합니다.
기능설명
WebSocket 엔드포인트 등록registerWebSocketHandlers() 메서드를 통해 WebSocket 엔드포인트를 등록할 수 있습니다.
핸들러 매핑특정 URL 패턴에 WebSocketHandler를 매핑할 수 있습니다.
CORS 설정WebSocket 연결에 대한 CORS(Cross-Origin Resource Sharing) 설정을 할 수 있습니다.
SockJS 지원SockJS 폴백 옵션을 활성화하여 WebSocket을 지원하지 않는 브라우저에 대한 대체 솔루션을 제공할 수 있습니다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/myHandler")
                .setAllowedOrigins("*")
                .withSockJS();
    }
}

WebSocketConfigurer (Spring Framework 6.1.13 API)

docs.spring.io

 

💡 [참고] WebSocketHandlerRegistry의 주요 메서드
메서드설명
addHandler(WebSocketHandler handler, String... paths)WebSocket 핸들러를 지정된 URL 경로에 등록합니다.
setAllowedOrigins(String... origins)허용된 오리진(도메인)을 설정합니다. CORS(Cross-Origin Resource Sharing) 정책을 관리하는 데 사용됩니다.
withSockJS()SockJS 폴백 옵션을 활성화합니다. WebSocket을 지원하지 않는 브라우저에 대한 대체 솔루션을 제공합니다.
setOrder(int order)핸들러의 우선순위를 설정합니다. 낮은 값일수록 높은 우선순위를 가집니다.
addInterceptors(HandshakeInterceptor... interceptors)WebSocket 핸들셰이크 과정에 인터셉터를 추가합니다.
setHandshakeHandler(HandshakeHandler handshakeHandler)사용자 정의 핸드셰이크 핸들러를 설정합니다.

 

WebSocketHandlerRegistration (Spring Framework 6.1.13 API)

Add more handlers that will share the same configuration (interceptors, SockJS config, etc).

docs.spring.io

 

2. TextWebSocketHandler 클래스


💡 TextWebSocketHandler

- Spring Framework에서 제공하는 WebSocket 핸들러 클래스입니다. 이 클래스는 '텍스트 기반의 WebSocket 메시지를 처리'하는 데 특화되어 있습니다.
특징설명
텍스트 메시지 처리문자열 형태의 WebSocket 메시지를 쉽게 처리할 수 있습니다.
추상 클래스TextWebSocketHandler는 추상 클래스로, 개발자가 필요한 메서드를 오버라이드하여 사용합니다.
WebSocketHandler 인터페이스 구현WebSocketHandler인터페이스를 구현하여 WebSocket의 기본적인 생명주기 메서드를 제공합니다.
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class MyWebSocketHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        // 메시지 처리 로직
        session.sendMessage(new TextMessage("서버에서 보내는 응답: " + payload));
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 연결 설정 후 로직
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        // 연결 종료 후 로직
    }
}

 

💡 [참고] TextWebSocketHandler와 같이 텍스트 처리외에 다른 웹 소켓 처리방식들
클래스설명
AbstractWebSocketHandler빈 메서드로 구현된 WebSocketHandler의 편리한 기본 클래스입니다.
BinaryWebSocketHandler바이너리 메시지만 처리하는WebSocketHandler구현을 위한 편리한 기본 클래스입니다.
PerConnectionWebSocketHandler각 WebSocket 연결에 대해 WebSocketHandler인스턴스를 초기화하고 소멸시키며 다른 모든 메서드를 위임하는 WebSocketHandler입니다.
TextWebSocketHandler텍스트 메시지만 처리하는 WebSocketHandler구현을 위한 편리한 기본 클래스입니다.

 
 
 

3. WebSocketMessageBrokerConfigurer 인터페이스


💡 WebSocketMessageBrokerConfigurer

- Spring Framework에서 WebSocket 및 STOMP 메시징을 구성하는 데 사용되는 인터페이스입니다. 이 인터페이스를 구현하면 WebSocket 통신을 위한 다양한 설정을 할 수 있습니다.

- @EnableWebSocketMessageBroker 어노테이션을 통해서 WebSocket 메시징을 활성화하는 메서드입니다.
- 이를 사용하여 STOMP 프로토콜을 통한 WebSocket 메시지를 주고받을 수 있도록 메시징을 구성할 수 있습니다.
기능설명
메시지 브로커 구성클라이언트와 서버 간의 메시지 라우팅을 관리합니다.
WebSocket 엔드포인트 등록클라이언트가 연결할 수 있는 WebSocket 엔드포인트를 정의합니다.
메시지 핸들러 설정들어오는 메시지를 처리할 핸들러를 구성합니다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

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

 
 

💡 [참고] WebSocketMessageBrokerConfigurer 클래스의 주요 메서드
메서드반환 타입설명
addArgumentResolversvoid커스텀 컨트롤러 메서드 인자 타입을 지원하기 위한 리졸버를 추가합니다.
addReturnValueHandlersvoid커스텀 컨트롤러 메서드 반환 값 타입을 지원하기 위한 핸들러를 추가합니다.
configureClientInboundChannelvoidWebSocket 클라이언트로부터 들어오는 메시지를 위한 MessageChannel을 구성합니다.
configureClientOutboundChannelvoidWebSocket 클라이언트로 나가는 메시지를 위한 MessageChannel을 구성합니다.
configureMessageBrokervoid메시지 브로커 옵션을 구성합니다.
configureMessageConvertersboolean주석이 달린 메서드의 메시지 페이로드 추출 및 메시지 전송 시 사용할 메시지 변환기를 구성합니다.
configureWebSocketTransportvoidWebSocket 클라이언트로부터 받은 메시지와 클라이언트로 보내는 메시지 처리와 관련된 옵션을 구성합니다.
getPhaseIntegerSmartLifecycle 타입의 WebSocket 메시지 처리 빈이 실행되어야 하는 단계를 반환합니다.
registerStompEndpointsvoid각각 특정 URL에 매핑되는 STOMP 엔드포인트를 등록하고, 선택적으로 SockJS 폴백 옵션을 활성화하고 구성합니다.

 
 
 

 
 

3) 개발 환경 구축 및 웹 소켓 구축


1. 구성 환경


💡 구성 환경

- 해당 글은 아래의 환경하에 WebScocket 구성이 되어 있습니다.
구성환경 버전
Java17
spring-boot3.3.2
spring-boot-starter-websocket3.3.2
Lombok-

 
 

2. 라이브러리 추가


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-websocket:3.3.2'
}

Maven Repository: org.springframework.boot » spring-boot-starter-websocket » 3.3.2
 
 

💡 [참고] 아래와 같은 패키지 구조로 구성을 하였습니다.

 
 
 

3. Chrome 확장 프로그램 : Web Socket Client


💡Chrome 확장 프로그램 : Web Socket Client

- Chorme 브라우저에서 WebSocket 연결을 위한 확장 프로그램을 설치합니다.

- 이를 사용하는 이유는 Websocket 연결에 대한 테스트와 간단한 데이터를 주고받는 환경을 테스트를 수행하기 위해 사용됩니다.
https://chromewebstore.google.com/detail/web-socket-client/lifhekgaodigcpmnakfhaaaboididbdn?hl=ko

Web Socket Client - Chrome 웹 스토어

A tool for manual web socket testing.

chromewebstore.google.com

 
 

4. WebSocketConfig 클래스 구성


💡 WebSocketConfig 클래스 구성

- WebSocket 최초 연결을 위해 구성하는 환경 구성 파일 클래스입니다.

1. @EnableWebSocket

- WebSocket 사용을 활성화하고 @Configuration 어노테이션을 통해 환경파일임을 지정합니다.
- 이 어노테이션을 사용하면 Spring 애플리케이션에서 WebSocket 기능을 사용할 수 있습니다.


2. WebSocketConfigurer(interface)

- WebSocketConfigurer 인터페이스로부터 구현체 registerWebSocketHandlers 메서드를 구성합니다.
- 이 인터페이스를 구현하면 registerWebSocketHandlers 메서드를 통해 WebSocket 핸들러를 등록할 수 있습니다.


3. registerWebSocketHandlers()

- WebSocketConfigurer 인터페이스로부터 오버라이딩 받은 WebSocket 핸들러를 구성합니다.
- 클라이언트에서 /ws-stomp 경로로 WebSocket 연결을 시도하면 ChatWebSocketHandler으로 연결을 처리하게 핸들러를 등록합니다.
package com.adjh.springbootwebsocket.config;

import com.adjh.springbootwebsocket.config.handler.ChatWebSocketHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * Spring Framework에서 WebSocket 구성을 위한 클래스입니다.
 *
 * @author : jonghoon
 * @fileName : WebSocketConfig
 * @since : 8/15/24
 */
@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketConfigurer {

    private final ChatWebSocketHandler chatWebSocketHandler;

    /**
     * WebSocket 연결을 위해서 Handler를 구성합니다.
     *
     * @param registry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        System.out.println("[+] 최초 WebSocket 연결을 위한 등록 Handler");
        registry
                // 클라이언트에서 웹 소켓 연결을 위해 "ws-stomp"라는 엔드포인트로 연결을 시도하면 ChatWebSocketHandler 클래스에서 이를 처리합니다.
                .addHandler(chatWebSocketHandler, "ws-stomp")
                // 접속 시도하는 모든 도메인 또는 IP에서 WebSocket 연결을 허용합니다.
                .setAllowedOrigins("*");
    }

}

 
 

5. ChatWebSocketHandler 클래스 구성


💡 ChatWebSocketHandler 클래스 구성

- WebSocket 연결 이후 연결을 처리하는 핸들러를 의미합니다.
- TextWebSocketHandler를 상속받아서 최초 소켓 세션을 연결하고 소켓/전송 오류가 발생했을 때 및 ‘텍스트 기반 메시지’를 보내거나 받을 수 있도록 처리를 가능하게 합니다.

1. afterConnectionEstablished() : 연결 성공

- WebSocket 협상이 성공적으로 완료되고 WebSocket 연결이 열려 사용할 준비가 된 후 호출됩니다. 성공을 하였을 경우 session 값을 추가합니다.

2. handleTextMessage() : 메시지 전달

- 새로운 WebSocket 메시지가 도착했을 때 호출됩니다.전달 받은 메시지를 순회하면서 메시지를 전송합니다.
- message.getPayload()를 통해 메시지가 전달이 됩니다.

3. afterConnectionClosed() : 소켓 종료 및 전송 오류

- WebSocket 연결이 어느 쪽에서든 종료되거나 전송 오류가 발생한 후 호출됩니다.
- 종료 및 실패하였을 경우 해당 세션을 제거합니다.
package com.adjh.springbootwebsocket.config.handler;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 텍스트 기반의 WebSocket 메시지를 처리를 수행하는 Handler 입니다.
 *
 * @author : jonghoon
 * @fileName : ChatWebSocketHandler
 * @since : 8/15/24
 */
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {

    // WebSocket Session들을 관리하는 리스트입니다.
    private static final ConcurrentHashMap<String, WebSocketSession> clientSession = new ConcurrentHashMap<>();

    /**
     * [연결 성공] WebSocket 협상이 성공적으로 완료되고 WebSocket 연결이 열려 사용할 준비가 된 후 호출됩니다.
     * - 성공을 하였을 경우 session 값을 추가합니다.
     *
     * @param session
     * @throws Exception
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("[+] afterConnectionEstablished :: " + session.getId());
        clientSession.put(session.getId(), session);
    }

    /**
     * [메시지 전달] 새로운 WebSocket 메시지가 도착했을 때 호출됩니다.
     * - 전달 받은 메시지를 순회하면서 메시지를 전송합니다.
     * - message.getPayload()를 통해 메시지가 전달이 됩니다.
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("[+] handleTextMessage :: " + session);
        System.out.println("[+] handleTextMessage :: " + message.getPayload());

        clientSession.forEach((key, value) -> {
            System.out.println("key :: " + key + "  value :: " + value);
            if (!key.equals(session.getId())) {  //같은 아이디가 아니면 메시지를 전달합니다.
                try {
                    value.sendMessage(message);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });

    }

    /**
     * [소켓 종료 및 전송 오류] WebSocket 연결이 어느 쪽에서든 종료되거나 전송 오류가 발생한 후 호출됩니다.
     * - 종료 및 실패하였을 경우 해당 세션을 제거합니다.
     *
     * @param session
     * @param status
     * @throws Exception
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws IOException {
        clientSession.remove(session);
        System.out.println("[+] afterConnectionClosed - Session: " + session.getId() + ", CloseStatus: " + status);
    }
}

 
 

💡 [참고] TextWebSocketHandler 인터페이스 메서드
메서드리턴 타입설명
afterConnectionEstablished(WebSocketSession session)voidWebSocket 협상이 성공적으로 완료되고 WebSocket 연결이 열려 사용할 준비가 된 후 호출됩니다.
afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus)voidWebSocket 연결이 어느 쪽에서든 종료되거나 전송 오류가 발생한 후 호출됩니다.
handleMessage(WebSocketSession session, WebSocketMessage<?> message)void새로운 WebSocket 메시지가 도착했을 때 호출됩니다.
handleTransportError(WebSocketSession session, Throwable exception)void기본 WebSocket 메시지 전송 중 오류가 발생했을 때 처리합니다.
supportsPartialMessages()booleanWebSocketHandler가 부분 메시지를 처리하는지 여부를 반환합니다.

 

WebSocketHandler (Spring Framework 6.1.13 API)

Invoked after the WebSocket connection has been closed by either side, or after a transport error has occurred.

docs.spring.io

 

6. WebSocketStompBrokerConfig


💡 WebSocketStompBrokerConfig

- WebSocket 및 STOMP 메시징 처리를 구현하는 WebSocketMessageBrokerConfigurer 인터페이스의 구현체입니다.
- 이를 통해 WebSocket 통신을 위한 다양한 설정을 구성할 수 있습니다.

1. @EnableWebSocketMessageBroker

- WebSocket 메시지 브로커를 활성화합니다. 이 애노테이션을 사용하면, 해당 클래스는 WebSocket 메시징을 위한 설정을 할 수 있습니다.


2. WebSocketMessageBrokerConfigurer

- WebSocket 연결을 처리하는 핸들러를 의미합니다. TextWebSocketHandler를 상속하여 ‘텍스트 기반 메시지’를 보내거나 받을 수 있도록 처리가 가능합니다.


3. configureMessageBroker(MessageBrokerRegistry config)

- 메시지 브로커를 구성하는 메서드로, 메시지 브로커가 특정 목적지로 메시지를 라우팅 하는 방식을 설정합니다.
- enableSimpleBroker() 메서드를 통해 접두사를 지정하여 클라이언트가 접두사로 시작하는 주제를 “구독(Sub)”하여 메시지를 받을 수 있습니다.
- setApplicationDestinationPrefixes() 메서드를 통해 접두사로 시작하는 클라이언트가 서버로 메시지를 “발행(Sub)” 이 접두사를 사용합니다.


4. registerStompEndpoints()

- STOMP(WebSocket 메시지 브로커 프로토콜) 엔드포인트를 등록하는 메서드로, 클라이언트가 WebSocket에 연결할 수 있는 엔드포인트를 정의합니다.

- addEndpoint() : 클라이언트가 WebSocket에 연결하기 위한 엔드포인트를 "/ws-stomp"로 설정합니다.
- setAllowedOrigins() : 클라이언트의 origin을 명시적으로 지정합니다.
- withSockJS() :WebSocket을 지원하지 않는 브라우저에서도 SockJS를 통해 WebSocket 기능을 사용할 수 있게 합니다.

 

package com.adjh.springbootwebsocket.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;

/**
 * STOMP를 사용하여 메시지 브로커를 설정합니다
 * WebSocket 메시지 브로커의 설정을 정의하는 메서드들을 제공합니다. 이를 통해 메시지 브로커를 구성하고 STOMP 엔드포인트를 등록할 수 있습니다.
 *
 * @author : jonghoon
 * @fileName : WebSocketStompBrokerConfig
 * @since : 8/15/24
 */

@Configuration                      // 설정 클래스로 지정합니다.
@EnableWebSocketMessageBroker       // WebSocket 메시지 브로커를 활성화합니다.
public class WebSocketStompBrokerConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * configureMessageBroker() : 메시지 브로커 옵션을 구성합니다.
     *
     * @param config
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 구독(sub) : 접두사로 시작하는 메시지를 브로커가 처리하도록 설정합니다. 클라이언트는 이 접두사로 시작하는 주제를 구독하여 메시지를 받을 수 있습니다.
        // 예를 들어, 소켓 통신에서 사용자가 특정 메시지를 받기위해 "/sub"이라는 prefix 기반 메시지 수신을 위해 Subscribe합니다.
        config.enableSimpleBroker("/sub");

        // 발행(pub) : 접두사로 시작하는 메시지는 @MessageMapping이 달린 메서드로 라우팅됩니다. 클라이언트가 서버로 메시지를 보낼 때 이 접두사를 사용합니다.
        // 예를 들어, 소켓 통신에서 사용자가 특정 메시지를 전송하기 위해 "/pub"라는 prefix 기반 메시지 전송을 위해 Publish 합니다.
        config.setApplicationDestinationPrefixes("/pub");
    }

    /**
     * registerStompEndpoints() : 각각 특정 URL에 매핑되는 STOMP 엔드포인트를 등록하고, 선택적으로 SockJS 폴백 옵션을 활성화하고 구성합니다.
     *
     * @param registry
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /*
         * addEndpoint : 클라이언트가 WebSocket에 연결하기 위한 엔드포인트를 "/ws-stomp"로 설정합니다.
         * withSockJS : WebSocket을 지원하지 않는 브라우저에서도 SockJS를 통해 WebSocket 기능을 사용할 수 있게 합니다.
         */
        registry
                // 클라이언트가 WebSocket에 연결하기 위한 엔드포인트를 "/ws-stomp"로 설정합니다.
                .addEndpoint("/ws-stomp")
                // 클라이언트의 origin을 명시적으로 지정
                .setAllowedOrigins("<http://localhost:3000>")
                // WebSocket을 지원하지 않는 브라우저에서도 SockJS를 통해 WebSocket 기능을 사용할 수 있게 합니다.
                .withSockJS();
    }
}

 

[ 더 알아보기 ]
💡 WebSocketConfig에서 지정한 엔드포인트와 WebSocketStompBrokerConfig에서 지정한 엔드포인트는 무슨 차이가 있는가?

1. WebSocketConfig 엔드포인트

- 기본적인 WebSocket 연결을 위한 것입니다. 이는 클라이언트가 최초로 WebSocket 연결을 설정할 때 사용하는 URL입니다.

2. WebSocketStompBrokerConfig 엔드포인트

- STOMP 프로토콜을 사용하는 WebSocket 연결을 위한 것입니다. 이 엔드포인트는 STOMP 클라이언트가 서버와 STOMP 세션을 설정하기 위해 사용합니다.


💡 그럼 최초 연결을 위해서는 WebSocketConfig 엔드포인트를 이용하고, 구독/발행을 할 때는 WebSocketStompBrokerConfig의 엔드포인트를 이용하는 게 맞는 걸까?

- 맞습니다. 최초 WebSocket 연결을 위해서는 WebSocketConfig 엔드포인트를 이용하고, 구독/발행 시에는 WebSocketStompBrokerConfig에서 정의한 엔드포인트와 정의한 접두사 (/sub, /pub)을 사용합니다.

 
 

7. ChatMessageDto


💡 ChatMessageDto

- 클라이언트와 데이터를 주고 받기 위해 구성한 DTO입니다.
- 지역 변수로 content 값을 통해 메시지를 구성하며, sender를 통해서 보내는 주체를 지정하였습니다.
package com.adjh.springbootwebsocket.dto;

import lombok.Data;

/**
 * 구독자와 수신자 간의 메시지를 주고받는 형태를 구성한 Object입니다.
 *
 * @author : jonghoon
 * @fileName : ChatMessage
 * @since : 8/16/24
 */
@Data
public class ChatMessageDto {
    private String content;
    private String sender;

    public ChatMessageDto(String content, String sender) {
        this.content = content;
        this.sender = sender;
    }
}

 
 

8. ChatController


💡 ChatController

- 해당 Controller에서는 STOMP를 사용하여 메시지를 처리하는 컨트롤러입니다.
- @MessageMapping를 통해서 메시지로 특정 경로로 들어오는 메시지를 처리합니다.
구분설명엔드포인트
발행자(Publisher)메시지를 전송하는 역할을 수행합니다/pub/messages
구독자(Subscriber)메시지를 수신하는 역할을 수행합니다/sub/message
package com.adjh.springbootwebsocket.controller;

import com.adjh.springbootwebsocket.dto.ChatMessageDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

/**
 * WebSocket 데이터를 처리를 수행한 Controller입니다.
 *
 * @author : jonghoon
 * @fileName : ChatController
 * @since : 8/15/24
 */
@RestController
public class ChatController {
    private final SimpMessagingTemplate template;       // 특정 사용자에게 메시지를 보내는데 사용되는 STOMP을 이용한 템플릿입니다.

    @Autowired
    public ChatController(SimpMessagingTemplate template) {
        this.template = template;
    }

    /**
     * Message 엔드포인트로 데이터와 함께 호출을 하면 "/sub/message"를 수신하는 사용자에게 메시지를 전달합니다.
     *
     * @param chatMessageDto
     * @return
     */
    @MessageMapping("/messages")
    public ChatMessageDto send2(@RequestBody ChatMessageDto chatMessageDto) {
        template.convertAndSend("/sub/message", chatMessageDto.getContent());       // 구독중인 모든 사용자에게 메시지를 전달합니다.
        return chatMessageDto;
    }
}

 
 
 
 
 
 
 

4) 결과 확인 : 소켓 서버 연결(Chrome Extension)


 

1. 소켓 서버 연결


💡 소켓 서버 연결

- 지정한 Endpoint로 접속하여 소켓 서버의 연결을 확인하였습니다.
- 해당 코드에서는 ws://localhost:8081/ws-stomp로 연결을 수행하였습니다.

 
 

2. 소켓 서버 간 통신


💡 소켓 서버 간 통신

- 해당 Google Extenstion 내에서는 Message Text라는 기능이 있어서, 두 개의 브라우저 간에서 각각 세션아이디가 다르기에 다른 세션 간의 메시지가 서로 통신이 됨을 확인하였습니다.

 
 
 

3. 소켓 서버 내 콘솔 확인


💡 소켓 서버 내 콘솔 확인

- 소켓 서버 내에서 ChatWebSocketHandler 클래스 내에 구현한 handleTextMessage() 메서드의 작성해 둔 콘솔 메시지를 통해서 아래와 같이 출력이 잘됨을 확인하였습니다.

 
 
 

4. 구성한 React 앱 내에서 직접 메시지를 전송합니다.


💡 구성한 React 앱 내에서 직접 메시지를 전송합니다.

- 위에서 구성한 웹 소켓 서버는 아래의 엔드포인트로 호출을 통해 데이터를 실시간으로 주고받을 수 있습니다.
구분엔드포인트설명
소켓 서버 연결http://localhost:8081/ws-stomp소켓 서버에 연결을 하는데 사용이 되는 엔드포인트 입니다.
메시지 전송/pub/messages메시지는 전송하기 위해 사용되는 엔드포인트입니다.
메시지 수신/sub/message메시지를 수신하기 위해 사용되는 엔드포인트입니다.

 

💡 [참고] 해당 구성 내용은 아래의 Repository 내에서 확인이 가능합니다.

blog-codes/spring-boot-websocket at main · adjh54ir/blog-codes

Contributor9 티스토리 블로그 내에서 활용한 내용들을 담은 레포지토리입니다. Contribute to adjh54ir/blog-codes development by creating an account on GitHub.

github.com

blog-codes/react-chatting at main · adjh54ir/blog-codes

Contributor9 티스토리 블로그 내에서 활용한 내용들을 담은 레포지토리입니다. Contribute to adjh54ir/blog-codes development by creating an account on GitHub.

github.com

 
 

💡 [참고] 해당 구성한 WebSocket 서버의 클라이언트를 구성에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.

[React] STOMP 기반 소켓 서버 활용하기 : @stomp/stompjs, sockjs-client

해당 글에서는 WebSocket 서버와의 연결을 통해서 STOMP Client를 이용하여 실시간 웹 소켓 통신을 구현하는 방법에 대해 알아봅니다  💡 [참고] STOMP을 이용한 웹 소켓 및 Socket.io를 이용한 소켓 연결

adjh54.tistory.com

 
 
 
오늘도 감사합니다. 😀