웹에서 실시간 기능을 구현할 때가 종종 있다.
이때 우리는 주로 웹 소켓을 고려하게 된다.
지난 해커톤에서 프로젝트를 하면서 SSE를 알게 되어 둘이 차이점과 사용 방법을 알아보고자 한다.
웹소켓은 서버와 클라이언트 양방향으로 통신이 가능하다.
프로토콜은 websocket 프로토콜이 따로 있다. (ws:// or wss://)
특징
예를 들어 실시간 채팅, google docs 같은 협업 편집 도구 등에서 사용한다.
Server-Sent Events로, 서버 -> 클라이언트의 단방향이다.
프로토콜은 HTTP 기반으로 EventSource API나 fetch() + ReadableStream를 사용한다.
특징
뉴스 업데이트, 실시간 알림 등에서 사용할 수 있다.
💡 EventSource API
사용 방식: const eventSource = new EventSource(url);
자동 재연결이 기본적으로 지원된다.
헤더 설정은 기본적으로 불가능하다.
CORS제한은 서버가 ccess-Control-Allow-Origin을 설정해야 사용 가능하다.
EventSource.onmessage, EventSource.onerror 이벤트 기반이다.
💡fetch() + ReadableStream
사용 방식: fetch(url).then(res => res.body.getReader().read())
직접 setTimeout()으로 재연결해야 한다.
fetch()를 사용하는 방식이기에 Authorization 등 헤더 설정이 가능하다. 또한, CORS 설정이 가능하고, 더 세밀한 제어가 가능하다.
우리 팀이 필요한 기능은 친구 요청에서 필요한 실시간 알림이었다.
그렇기에 굳이 양방향인 웹소켓보다 SSE가 적절하다고 판단했다.
또한, 친구 요청은 JWT 토큰 인증이 필요했기에 EventSource API 대신 fetch() + ReadableStream을 사용해서 SSE를 구현했다. (로그인한 사용자만 가능했기에!)
👩🏻💻 코드 개요
__1. useEffect로 SSE를 연결한다.
useEffect(() => {
if (!TOKEN) {
console.error("JWT 토큰이 없습니다.");
return;
}
connectSSE();
return () => {
console.log("SSE 연결 해제");
if (readerRef.current) {
readerRef.current.cancel();
}
};
}, [TOKEN]);
이때 TOKEN은 로컬 스토리지에서 getItem을 해온다.
컴포넌트가 마운트(useEffect)될 때 SSE 연결을 시작하고, 언마운트 시 연결을 해제한다.
readerRef.current.cancel(); → 연결 해제 시 스트림 리더를 정리하는 코드다.
connectSSE: 서버와 연결
const connectSSE = async (): Promise<void> => {
console.log("SSE 연결 시도");
const attemptConnection = async (): Promise<void> => {
try {
const response = await fetch(
`${import.meta.env.VITE_API_BASE_URL}/api/connect`,
{
method: "GET",
headers: {
Authorization: `Bearer ${TOKEN}`,
"Content-Type": "application/json",
},
credentials: "include",
}
);
if (!response.ok) {
throw new Error(`서버 응답 실패: ${response.status}`);
}
서버에서 받은 메시지 읽기
const reader = response.body?.getReader();
if (!reader) {
throw new Error("스트림 리더를 가져올 수 없습니다.");
}
readerRef.current = reader;
const decoder = new TextDecoder();
while (true) {
const { value, done } = await reader.read();
if (done) break;
const text = decoder.decode(value, { stream: true }).trim();
console.log("[MESSAGE] 새 알림 도착:", text);
setHasNotification(true);
setHasNotification(true)로 알림이 도착했음을 상태 업데이트한다. 연결 실패 시 3초 후 재연결한다
} catch (error) {
console.error("❌ SSE 연결 실패, 3초 후 재시도");
setTimeout(() => {
connectSSE();
}, 3000);
}
이런식으로 Sse를 구현할 수 있다.
SSE -> 단방향 업데이트(서버 -> 클라이언트)가 필요한 경우
WebSocket -> 양방향 실시간 통신이 필요한 경우
WebSocket이 더 강력한 기능을 제공하지만, 단순한 서버 푸시 업데이트라면 SSE가 더 간편하고 효율적일 수 있다.
각 특징을 고려해 본인 프로젝트에 어떤걸 선택할지 고르는게 좋을 것 같다 !!