본문 바로가기
공부/FE

실시간으로 서버에서 데이터를 가져와보기 (feat. Polling/Websocket/SSE)

by Piva 2024. 11. 3.
  • 회사에서 다양한 프로젝트를 진행하며, 서버에서 실시간으로 갱신되는 데이터를 가져오는 작업을 몇 번 경험했다.
  • 그 과정에서 다양한 방식을 접하고 경험할 수 있었는데, 간단하게 복습 겸 정리해보기로.

Polling 방식

  • 새로운 데이터를 받아오기 위해 API를 일정한 간격으로 계속해서 호출하는 것. 즉 클라이언트가 주기적으로 서버에 요청을 보낸다.
  • setInterval 등으로 일정 간격을 두고 서버에 데이터를 요청하는 방식으로 구현하면 되기 때문에, 다른 방식들에 비해 비교적 구현이 간단하다.
  • 다만, 새로 가져올 데이터가 없더라도 무조건 요청을 보내기 때문에 비효율적이고 서버의 부담을 증가시킨다는 단점이 존재한다.
  • 또한, 갱신된 데이터를 제때제때 받아오는 방식이 아니기 때문에 실시간성이 떨어진다.

 

  위에서 언급한대로 React에서 setInterval 을 사용하여 구현한 코드는 대강 아래와 같은 형태가 된다.

 

import { useEffect } from 'react';

const DELAY = 1000 * 2; // 2초에 한 번 가져온다고 가정

const Page = () => {
  useEffect(() => {
    const fetch = () => {}; // Some sample fetching code...
		
    const id = setInterval(fetch, DELAY);
		
    return () => {
      clearInterval(id);
    };
  }, []);

  return (
    /* ... */
  );
};

 

 

SSE (Server Sent Events)

  • 위 폴링 방식의 문제점은, 실제로 새로운 데이터가 있는지 없는지 클라이언트에서 확인이 불가하기 때문에 일정 간격으로 계속해서 확인할 수 밖에 없다는 것이다.
    • 이를 해결할 수 있는 방법 중 하나가 바로 SSE 이다.
  • SSE는 ‘Server Sent Events’의 약자로, 단어 그대로 ‘서버에서 이벤트를 보냄으로써 클라이언트가 새로운 데이터를 받아 업데이트 할 수 있게 하는 기술’을 의미한다.
    → 따라서 폴링 방식과는 달리, 서버가 새로운 데이터를 실시간으로 보내줌으로써 불필요한 데이터 페칭 문제가 사라진다.

 

실제 구현

  • EventSource 인터페이스를 사용하여 서버와의 연결을 구현할 수 있다.
다만 브라우저에 따라 EventSource를 지원하지 않는 경우가 있기 때문에, EventSource를 통해 SSE 연결을 구축할 때는 event-source-polyfill 라이브러리를 많이 사용함.

 

  • 연결 과정은 대강 아래와 같다.
  1. EventSource 객체를 사용해 SSE 연결을 개시한다.
  2. 서버로부터 메시지를 수신했을 때 수행할 동작을 지정한다.
    1. onmessage 속성에 동작을 할당
    2. addEventListener를 통해 할당
    하는 2가지의 방법이 있다.
  3. 연결이 더 이상 필요하지 않을 때(ex. 현재 페이지에서 나감), close 를 통해 연결을 끊는다.

 

간략하게 위 과정을 실제 코드로 구현하면 아래와 같아진다.

(event-source-polyfill 라이브러리 사용)

 

import { useEffect } from 'react';
import { EventSourcePolyfill } from 'event-source-polyfill';

const Page = () => {
  const sseRef = useRef<EventSourcePolyfill>(null);
	
  useEffect(() => {
    // SSE 연결 시작
    sseRef.current = new EventSourcePolyfill('SSE URL', {
      // configuration for connection
        /* ... */
    });
		
    sseRef.current.onmessage = (e: MessageEvent) => {
      // Do something with the data from server...
    };
		
    sseRef.current.onerror = (error: Event) => {
      // Handle Error...
    };
  }, []);
	
  return () => {
    if (sseRef.current) {
      sseRef.current.close();
    }
  };
};

 

  • event-source-polyfill 라이브러리에서 EventSourcePolyfill을 가져와 선언한다.
  • useEffect를 통해 SSE 연결을 수행하고, onmessage 및 onerror를 처리할 함수를 등록함으로써 서버에서 받은 메시지 및 발생한 에러를 어떻게 처리할지 설정한다.
  • useEffect의 클린업 함수를 통해, 현재의 SSE 연결을 close 를 사용하여 끊는다.

 

WebSocket

  • 앞서 살펴본 SSE 방식의 특징은, 클라이언트는 서버가 보내는 데이터를 수신할 뿐 반대로 클라이언트에서 서버로 데이터를 송신할 수는 없다는 것이다.
  • 그러나 '웹소켓'을 사용하면 서버와 클라이언트 사이의 양방향 통신이 가능하다! 즉, 클라이언트도 서버에 데이터를 보낼 수 있다.

 

실제 구현

  • 전체적인 흐름은 SSE랑 유사하다. 다만 이번에는 WebSocket 을 사용하여 연결을 수행한다.
  1. WebSocket 객체를 사용해 연결을 개시한다.
    • 이 때, 연결에 사용하는 프로토콜은 ws 혹은 wss 이다. (http/https 이 아니다!)
    • ws:// : 일반 웹소켓 연결 시 사용하는 프로토콜.
    • wss:// : 보안 웹소켓 연결 시 사용하는 프로토콜. (https 같이)
  2. 서버로부터 메시지를 수신했을 때 수행할 동작을 지정한다.
    • SSE와 같이 onmessage를 통해 할당하는 법, addEventListener를 통해 할당하는 법 2가지가 있다.
  3. 연결이 더 이상 필요하지 않을 때 close 를 통해 연결을 끊는다.

 

위 과정도 간단하게 구현하면 아래와 같아진다. 전체적인 흐름은 SSE의 코드와 거의 비슷하다.

 

import { useEffect } from 'react';

const Page = () => {
  const websocketRef = useRef<WebSocket>(null);
	
  useEffect(() => {
    // WebSocket 연결 시작
    websocketRef.current = new WebSocket('ws://example.url.com');
		
    websocketRef.current.onmessage = (e: MessageEvent) => {
    // Do something with the data from server...
  };
		
    websocketRef.current.onerror = (error: Event) => {
      // Handle Error...
    };
  }, []);
  
  const sendMessage = (message: string) => {
    // send message from client to server
  	websocketRef.current?.send(message);
  };
	
  return () => {
    if (websocketRef.current) {
      websocketRef.current.close();
    }
  };
};

 

  • useEffect 내부에서 WebSocket 생성자를 통해 웹소켓 연결을 수행하고, onmessage 및 onerror를 처리할 함수를 등록함으로써 서버에서 받은 메시지 및 발생한 에러를 어떻게 처리할지 설정한다.
  • useEffect의 클린업 함수를 통해, 현재의 웹소켓 연결을 close 를 사용하여 끊는다.
  • 서버에 데이터를 송신하고자 할 때는 send 메서드를 사용한다.

 


  • 이번에 스스로 구현을 해본건 웹소켓 연결이었다. 연결과 처리 등의 굉장히 기본적인 부분만 일단 구현되어 있어, 나중에 좀더 보완을 해보고 싶다는 생각이 들었다.
  • 별개로 실제로 자주 거론되는 데이터 송수신 방법에 대해 경험하는 계기가 되어, 비교하고 정리하는 과정이 꽤 즐거웠다!