8. 간단한 WebSocket 구현

Dev StoryTeller·2024년 10월 22일
0

이제 Spring으로 간단하게 Websocket을 구현해보자

앞서 설명이 길었던게 무색하리만큼, 실제 기본적인 코드 작성은 간단하다
WebSocket 요청을 처리할 핸들러해당 핸들러를 등록하기 위한 설정파일, 2가지이다.

  • WebSocket 설정 파일
  • WebSocket 핸들러

하나하나 알아보도록 하자


0. 의존성 추가

스프링이 지원하는 웹소켓 의존성를 추가해준다

build.gradle 파일
implementation 'org.springframework.boot:spring-boot-starter-websocket'


1. WebSocket 설정

우선 여기서는 기본 WebSocket 만을 사용할 것이므로, 설정을 위한 어노테이션인 @EnabledWebSocket을 붙여준다
그리고 WebSocketConfigurer를 구현하여 사용한다

이를 코드로 표현하면 다음과 같다

@Configuration
@EnableWebSocket
@RequiredArgsConstructor
public class WebSockConfig implements WebSocketConfigurer {
    private final WebSocketHandler webSocketHandler;

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

여기서 구현한 인터페이스는 간단하게 핸들러를 설정 및 등록하는 기능을 갖고있다

  • registerWebSocketHandlers
    addHandler 메소드로 사용할 핸들러를 등록해주며,
    이후 Origin, OriginPattern메소드들로 핸들러들을 설정한다
    (관련 메소드들은 WebSocketHandlerRegistration를 참고하자)

  • withSockJS()
    SockJS를 사용하겠다는 의미이며, 이것을 붙여야 SockJS를 사용할 수 있다
    해당 메소드를 붙이면 더이상 WebSocket이 아닌 SockJS 설정으로 바뀌며,
    SockJS에 관련한 각종 설정을 할 수 있다
    (관련 메소드들은 SockJsServiceRegistration를 참고하자)


2. 핸들러 종류

websocket 요청을 어떻게 처리할지를 담당하는 부분이다

Spring에서 사용하는 핸들러에는 대표적으로 다음 3가지가 존재한다

  • BinaryWebSocketHandler
  • TextWebSocketHandler
  • SockJsWebSocketHandler

매우 간단하니 빠르게 알아보자

  • TextWebSocketHandler
    이름 그대로 단순한 텍스트 메시지만을 다루는 핸들러이다. 이외의 데이터는 불가하다

  • BinaryWebSocketHandler
    이미지, 파일 등 바이너리 데이터를 다루는 핸들러이다. 마찬가지로 단순 텍스트는 불가하다

  • SockJsWebSocketHandler
    SockJS를 사용하기 위한 핸들러이다

SockJsWebSocketHandler는 잘 모르겠어서 확인해보았는데,
기존에 사용하던 WebSocketHandler에 SockJS를 사용할 수 있도록 바꿔주는 역할은 하는 것 같다

자세히 보니 SockJsWebSocketHandler를 사용하려면
기존의 WebSocketHandler가 있어야 하고,
SockJS 설정을 위한 SockJsServiceConfig도 있어야 하며,
SockFrame에 맞는 WebSocketServerSockJsSession도 생성해야 했다

즉, 기존 핸들러(Text, Binary)에 SockJS 설정을 덧씌워 사용가능하게 한 것!

하지만 withSockJS()를 통해 체이닝 방식으로 충분히 구현 가능하기에, 굳이 사용하지 않아도 될 것 같다


3. 핸들러 작성(Text)

여기서는 간단하게 텍스트 핸들러를 작성해보기로 한다
코드는 다음과 같다

package org.example.multichat;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.util.ArrayList;
import java.util.List;

@Slf4j
@Component
@RequiredArgsConstructor
public class WebSocketHandler extends TextWebSocketHandler {
    private final List<WebSocketSession> webSocketSessionList = new ArrayList<>();

    // 커넥션 맺은 후(세션 리스트에 추가)
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        webSocketSessionList.add(session);
        String msg = session.getId() + " 님이 입장하셨습니다.";
        
        for (WebSocketSession wsSession : webSocketSessionList) {
            wsSession.sendMessage(new TextMessage(msg+ "\n"));
        }
    }

    // 메시지 다루기
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        for (WebSocketSession wsSession : webSocketSessionList) {
            wsSession.sendMessage(new TextMessage(session.getId() + "> " +message.getPayload() + "\n"));
        }
    }

    // 에러 다루기
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        super.handleTransportError(session, exception);
    }

    // 연결이 끊긴 후(채팅방을 나갔으므로, 세션 리스트에서 삭제)
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        for (WebSocketSession wsSession : webSocketSessionList) {
            wsSession.sendMessage(new TextMessage(session.getId() + " 님이 퇴장하셨습니다.\n"));
        }
        webSocketSessionList.remove(session);
        
        super.afterConnectionClosed(session, status);
    }
}

webSocketSessionList멀티 채팅 구현을 위해 추가한 변수이며, 입장/퇴장 시 추가/제거된다

크게 연결 및 종료 관련 메소드(afterConnectionEstablished, afterConnectionClosed)와
텍스트 메시지 처리 메소드(handleTextMessage),
웹소켓 에러 처리(handleTransportError)로 나뉜다

웹소켓 연결된 세션(WebSocketSession)으로 메시지를 보내거나(snedMessage) 인증된 유저정보(getPrincipal)를 가져올 수 있다

이전 편에서는 웹소켓은 메시지(프레임들)를 주고받는다 했는데, Spring은 이를 WebSocketMessage<T> 타입으로 구현했다

웹소켓 데이터 타입과 동일하게 텍스트/바이너리/핑으로 구성되어 있으며, 적절한 것을 사용하면 된다


이로써 간단하게 Spring으로 WebSocket + SockJS를 구현해보았는데, 아직 한단계가 더 남아있다
바로 STOMP 이라는 것인데, 다음 편에서 알아보도록 하자!

profile
개발을 이야기하는 개발자입니다.

0개의 댓글