- 회사에서 다양한 프로젝트를 진행하며, 서버에서 실시간으로 갱신되는 데이터를 가져오는 작업을 몇 번 경험했다.
- 그 과정에서 다양한 방식을 접하고 경험할 수 있었는데, 간단하게 복습 겸 정리해보기로.
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 라이브러리를 많이 사용함.
- 연결 과정은 대강 아래와 같다.
- EventSource 객체를 사용해 SSE 연결을 개시한다.
- 서버로부터 메시지를 수신했을 때 수행할 동작을 지정한다.
- onmessage 속성에 동작을 할당
- addEventListener를 통해 할당
- 연결이 더 이상 필요하지 않을 때(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 을 사용하여 연결을 수행한다.
- WebSocket 객체를 사용해 연결을 개시한다.
- 이 때, 연결에 사용하는 프로토콜은 ws 혹은 wss 이다. (http/https 이 아니다!)
- ws:// : 일반 웹소켓 연결 시 사용하는 프로토콜.
- wss:// : 보안 웹소켓 연결 시 사용하는 프로토콜. (https 같이)
- 서버로부터 메시지를 수신했을 때 수행할 동작을 지정한다.
- SSE와 같이 onmessage를 통해 할당하는 법, addEventListener를 통해 할당하는 법 2가지가 있다.
- 연결이 더 이상 필요하지 않을 때 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 메서드를 사용한다.
- 이번에 스스로 구현을 해본건 웹소켓 연결이었다. 연결과 처리 등의 굉장히 기본적인 부분만 일단 구현되어 있어, 나중에 좀더 보완을 해보고 싶다는 생각이 들었다.
- 별개로 실제로 자주 거론되는 데이터 송수신 방법에 대해 경험하는 계기가 되어, 비교하고 정리하는 과정이 꽤 즐거웠다!
'공부 > FE' 카테고리의 다른 글
웹소켓 연결을 해보자 (커스텀 웹소켓 훅 만들어보기) (0) | 2024.11.16 |
---|---|
Hydration이란? (0) | 2024.11.07 |
PostMessage 알아보기 (3) | 2024.10.20 |
[Vite] Vite 알아보기 (0) | 2024.07.14 |
[웹 기본 쌓기] 렌더링, 리플로우/리페인트 (1) | 2024.01.10 |