우와한 개발자

[Spring] WebSocket이란? 스프링 프로젝트 웹소켓 구현

by 우와한개발자

1. Web socket 이란?

  • 클라이언트와 서버 간 양방향 통신을 지원하는 프로토콜
  • HTTP와 달리 한 번 연결되면 연결을 유지 (Persistent Connection)
  • 실시간 채팅, 알림, 주식 시세 등에 활용

1) HTTP vs WebSocket 비교

구분 HTTP WebSocket
통신 방식 단방향 (요청-응답) 양방향
연결 유지 요청마다 연결/종료 연결 유지 (Persistent)
실시간성 낮음 높음
오버헤드 매 요청마다 헤더 포함 최초 연결 후 헤더 없음
사용 사례 일반 웹 요청 채팅, 알림, 실시간 데이터

 

2) 전체 흐름

 

 

3) WebSocketHandler 인터페이스 메서드

리턴 타입 메서드 설명
void afterConnectionEstablished(WebSocketSession session) WebSocket 연결이 성공적으로 수립된 후 호출됨
void handleMessage(WebSocketSession session, WebSocketMessage<?> message) 클라이언트로부터 메시지를 수신했을 때 호출됨
void handleTransportError(WebSocketSession session, Throwable exception) 메시지 전송 중 오류 발생 시 호출됨
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) WebSocket 연결이 종료될 때 호출됨 (정상 종료 또는 오류 포함)
boolean supportsPartialMessages() WebSocket 메시지를 부분적으로 처리할지 여부

 

4) WebSocketHandler 인터페이스 구현 클래스

클래스명 클래스 설명
AbstractWebSocketHandler 기본 메서드가 비어있는 WebSocketHandler 추상 클래스
BinaryWebSocketHandler 바이너리 메시지만 처리하는 WebSocketHandler (텍스트 메시지는 거부됨)
ExceptionWebSocketHandlerDecorator 핸들러에서 발생한 예외를 처리하고 세션을 종료하는 데코레이터
LoggingWebSocketHandlerDecorator WebSocket 라이프사이클 이벤트에 대한 로그를 추가하는 데코레이터
PerConnectionWebSocketHandler 연결마다 새로운 WebSocketHandler 인스턴스를 생성하여 처리
SockJsWebSocketHandler SockJS 지원을 위한 핸들러 (fallback, heartbeat 처리 포함)
SubProtocolWebSocketHandler STOMP 등 하위 프로토콜 메시지를 처리하는 핸들러
TextWebSocketHandler 텍스트 메시지만 처리하는 WebSocketHandler (바이너리 메시지는 거부)
WebSocketHandlerDecorator 기존 WebSocketHandler를 감싸 기능을 확장하는 데코레이터

 

2. WebSocket 구현 : Spring Boot (Maven)

1) pom.xml

  • Spring Boot에서는 spring-boot-starter-websocket 하나만 추가하면 WebSocket 관련 설정이 자동으로 처리됨
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

 

2) WebSocketConfig

  • WebSocket 엔드포인트를 등록하고 핸들러와 URL을 연결
// config/WebSocketConfig.java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final ChatWebSocketHandler chatWebSocketHandler;

    public WebSocketConfig(ChatWebSocketHandler chatWebSocketHandler) {
        this.chatWebSocketHandler = chatWebSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatWebSocketHandler, "/ws/chat")
                .setAllowedOrigins("*");  // CORS 허용 (운영 환경에서는 도메인 지정 권장)
    }
}

 

3) WebSocketHandler 구현

  • TextWebSocketHandler를 상속해서 텍스트 메시지를 처리
  • 연결된 모든 세션에 메시지를 브로드캐스트하는 채팅 구현 예시
// handler/ChatWebSocketHandler.java
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {

    // 연결된 세션 목록 (멀티스레드 환경에서 안전한 CopyOnWriteArrayList 사용)
    private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    // 연결 수립 시
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
        System.out.println("연결됨 : " + session.getId());
    }

    // 메시지 수신 시 → 연결된 모든 클라이언트에게 브로드캐스트
    @Override
    protected void handleTextMessage(WebSocketSession session,
                                     TextMessage message) throws Exception {
        for (WebSocketSession s : sessions) {
            if (s.isOpen()) {
                s.sendMessage(message);
            }
        }
    }

    // 연결 종료 시
    @Override
    public void afterConnectionClosed(WebSocketSession session,
                                      CloseStatus status) {
        sessions.remove(session);
        System.out.println("연결 종료 : " + session.getId());
    }
}
⚠️CopyOnWriteArrayList를 사용하는 이유
- 여러 클라이언트가 동시에 연결/해제될 때 ArrayList는 동시성 문제가 발생할 수 있어, 멀티스레드 환경에서 안전한 CopyOnWriteArrayList를 사용

 

4) 클라이언트 (JavaScript)

const socket = new WebSocket("ws://localhost:8080/ws/chat");

// 연결 수립 시
socket.onopen = () => {
    console.log("WebSocket 연결 성공");
    socket.send("안녕하세요!");
};

// 메시지 수신 시
socket.onmessage = (event) => {
    console.log("받은 메시지 : " + event.data);
};

// 연결 종료 시
socket.onclose = () => {
    console.log("WebSocket 연결 종료");
};

// 오류 발생 시
socket.onerror = (error) => {
    console.error("WebSocket 오류 : ", error);
};

 

3. WebSocket 구현 - Spring Legacy (XML 설정)

1) pom.xml

  • Spring Legacy에서는 spring-websocket과 spring-messaging을 별도로 추가
<!-- Spring WebSocket -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.3.30</version>
</dependency>

<!-- Spring Messaging (WebSocket 메시지 처리에 필요) -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.3.30</version>
</dependency>

<!-- Jackson (JSON 직렬화) -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
</dependency>

 

2) servlet-context.xml 또는 Java Config

  • servlet-context.xml 방식
<!-- servlet-context.xml -->
<websocket:handlers>
    <websocket:mapping handler="chatWebSocketHandler" path="/ws/chat"/>
    <websocket:handshake-interceptors>
        <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
    </websocket:handshake-interceptors>
</websocket:handlers>

<bean id="chatWebSocketHandler"
      class="com.example.handler.ChatWebSocketHandler"/>

 

  • Java Config 방식
// config/WebSocketConfig.java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new ChatWebSocketHandler(), "/ws/chat")
                .addInterceptors(new HttpSessionHandshakeInterceptor())
                .setAllowedOrigins("*");
    }
}

 

3) WebSocketHandler 구현

  • Spring Boot와 동일하게 TextWebSocketHandler를 상속.
// handler/ChatWebSocketHandler.java
public class ChatWebSocketHandler extends TextWebSocketHandler {

    private final List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session,
                                     TextMessage message) throws Exception {
        for (WebSocketSession s : sessions) {
            if (s.isOpen()) {
                s.sendMessage(message);
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session,
                                      CloseStatus status) throws Exception {
        sessions.remove(session);
    }
}

 

4. Spring Boot vs Spring Legacy 비교

구분 Spring Boot Spring Legacy
의존성 spring-boot-starter-websocket 1개 spring-websocket + spring-messaging 별도 추가
설정 방식 Java Config (@EnableWebSocket) XML 또는 Java Config
자동 설정 대부분 자동 처리 수동 Bean 등록 필요
핸들러 구현 동일 (TextWebSocketHandler 상속) 동일 (TextWebSocketHandler 상속)

블로그의 정보

우와한개발자 님의 블로그

우와한개발자

활동하기