WebSocket 통신 방식에 대해서

Charlie·2026년 3월 23일

BE

목록 보기
1/1
post-thumbnail

들어가며..

주식 예측 프로그램을 동기들과 제작해보는 프로젝트를 진행 중에, 주식 그래프를 구현하면서 몇 가지 이슈에 부딪혔다. 초기엔 BackEnd에서 구현해본 것이 REST API가 대부분이라 당연히 방향성을 그렇게 잡고 움직였다.

하지만 프론트를 조금 구현하던 중 정보를 받아오는 것이 REST API로 진행하면 매끄럽지 않다는 것을 인지했다. 이러한 문제를 해결하고자 방법을 찾다가 WebSocket을 찾았고, 우리는 공부를 위해 REST와 Websocket을 모두 구현해보고자 한다.

그러기 위해서는 WebSocket에 대한 공부가 필요했고, 공부한 내용을 이렇게 블로그에 정리해보았다.

REST API 와 WebSocket

REST APIWebSocket의 차이를 알아보려면 REST API가 가지는 한계를 알아봐야한다. REST API는 아래와 같은 한계를 가진다.

  • 요청이 반드시 필요하다.
  • 응답을 받으면 바로 연결을 끊어버려서 다시 요청을 보내야 통신이 가능하다.
  • 정보를 전송할때마다 HandShake를 진행한다.

이러한 한계들은 결국 데이터를 빠르게 받아오고 빠르게 처리하는 과정이 필요한 서비스에는 너무도 비효율적인 방식이 되었고, 결국 WebSocket이 탄생하여 오늘날 사용되게 되었다.

Code로 보는 WebSocket

그렇다면 코드로는 어떻게 구현해볼 수 있을까. 내가 자주 사용하는 Spring Boot를 이용해서 이 WebSocket 기술을 구현해보자.

Controller

@Controller
public class StockController {

    private final StockService stockService;
    private final SimpMessagingTemplate messagingTemplate; 

    public StockController(StockService stockService, SimpMessagingTemplate messagingTemplate) {
        this.stockService = stockService;
        this.messagingTemplate = messagingTemplate;
    }

    // React에서 '/app/subscribe' 로 메시지를 보낼 때 실행됨
    @MessageMapping("/subscribe") 
    public void handleSubscribe(@Payload Map<String, String> payload) {
        String symbol = payload.get("symbol");
        Object currentStockData = stockService.getCurrentPrice(symbol); 
        messagingTemplate.convertAndSend("/topic/stock/" + symbol, currentStockData);
    }
}

위 코드는 개략적인 코드를 가져왔다.

WebSocket을 본격적으로 작업하려면 Topic(Room)이라는 개념을 이해해야한다. 웹소캣을 통해 주식 데이터를 받아오면 API를 통해 모든 주식 정보를 다 받아오게 된다. 만약 방이 없다면 이러한 정보들을 처리하지 못하고 받아내면서 브라우저에 강한 무리를 주게 된다.

이를 방지하기 위해 우리는 각 주식에 맞게 "Room"을 나누어서 처리한다. 그러면 사용자(User)는 방을 골라서 본인이 원하는 정보를 담은 Room에 대한 정보를 얻는 것이다. 이 과정을 우리는 Subscribe(구독)이라고 한다.

그리고 여기서 우리는 SimpMessagingTemplate을 주요하게 봐야한다. 이 항목은 이 WebSocket에서 정보를 알려주는 확성기의 역할을 한다. 이 확성기는 모두를 향해 제공되지만 이를 Room을 나누고 구독을 함으로서 각각의 방에, 각각의 User에게 원하는 데이터를 전송해주는 것이다.

이 코드는 아주 예시이기에 현재 가격을 가져오고, Room을 나눠서 특정 Room의 사용자들에게 값이 전달되는 기능만을 가지는 Controller이다.

Service

@Service
public class StockService {

    private final Random random = new Random();
    private double lastClose = 75000;

    public StockPrice getLatestPrice(String symbol) {
        double open  = lastClose + (random.nextDouble() * 400 - 200);
        double close = open      + (random.nextDouble() * 1000 - 500);
        double high  = Math.max(open, close) + random.nextDouble() * 300;
        double low   = Math.min(open, close) - random.nextDouble() * 300;
        lastClose = close;

        return new StockPrice(
                symbol,
                (long) open,
                (long) high,
                (long) low,
                (long) close,
                Instant.now().getEpochSecond()
        );
    }
}

Service는 받아내는 정보에 맞게 수정해야한다. 지금은 임시로 시가, 종가, 고가, 저가를 중심으로 나누어서 작업을 하도록 구성해두었다.

사실 WebSocket 구현 자체와는 크게 중요한 내용이라고 보이진 않는다. 차후 기능이 추가되면 더 자주 사용될 것이다.

Scheduler

@Component
public class StockScheduler {

    private final SimpMessagingTemplate messagingTemplate;
    private final StockService stockService;

    public StockScheduler(SimpMessagingTemplate messagingTemplate, StockService stockService) {
        this.messagingTemplate = messagingTemplate;
        this.stockService = stockService;
    }

    @Scheduled(fixedDelay = 1000)
    public void pushStockPrices() {
        StockPrice price = stockService.getLatestPrice("005930");
        messagingTemplate.convertAndSend("/topic/stock/005930", price);
    }
}

우리는 여기서 특정 종목에 대한 dto를 받아와서 messagingTemplate로 정의된 확성기를 통해 /topic/stock/{stockId} 라는 Room으로 내용을 쏴주는 절차를 밟는다. 1000ms로 작업을 해두었기에 1초마다 새로운 봉차트를 그리는 방향으로 디자인된다. Scheduler가 가지는 가장 중요한 요소는 1초마다 정보를 보내준다는 것이다.

사실상 front-End로 가기위해서는 Scheduler는 필수적인 요소이라고 볼 수 있다.

Summary

전체적인 플로우는 위의 사진과 같이 이해하면 될 것 같다. 전체적인 플로우를 이해하고 코드를 들여다 보면 더 쉽게 느껴지지 않을까 싶다.

물론 기능적으로 앞으로 더 맞춰나갈 것이 많으리라 보지만, 당장 처음 배운 개념과 기술에 대해서 너무 많은 것을 한번에 하기보다는, 하나씩 배워나가며 개념을 정립하고 이를 확장해서 더 좋은 기능들을 추가해 나가는 방향의 작업도 좋지 않을까.

profile
찬찬히 써내려가는 개발일지

0개의 댓글