이번에 새로 시작하게 된 프로젝트에서 실시간 통신을 이용하여 구현해야하는 기능이 존재하여 이에 대한 공부를 하기 위하여 아티클을 작성하게 되었다. 실시간 통신 방법중에 대표적인 Polling방식과 WebSocket방식에 대하여 알아보고 차이점 및 장단점 그리고 구현 방법에 대해서 알아보는 시간을 갖겠습니다.
시작하기에 앞서 전통적인 HTTP통신 방식에 대한 소개를 하겠습니다.
=> 전통적인 방법인 HTTP(Hyper Transfer Protocol)통신 이라는 방법을 사용하여 데이터를 주고 받았다.
1. 클라이언트가 연결을 열고, 서버에 데이터를 요청(Request)한다.
2. 서버는 요청에 대한 응답(클라이언트의 요청에 따른 결과값, Response)을 클라이언트에 다시 보낸다.
3. 클라이언트가 서버에 요청을 보내고, 요청에 대한 응답을 받으면 통신이 종료된다.
이해하기 어렵다면? 문자메세지라고 생각하면 편함.
클라이언트가 n초 간격으로 request를 서버로 계속 날려서 response를 전달받는 방식
=> 사실 실시간인 것처럼 작동할 뿐, 지속적으로 클라이언트에서 request(요청)을 날려서 마치 실시간인 것처럼 작동하게 하는 방법이다.
=> 그런데 요즘은 실시간 주식거래에서 초단위 그래프, 채팅 등등 (찐)실시간 통신이 필수적으로 필요로 하는 기능들이 점차 늘어나고 있다.
실시간으로 정보를 서버에게 받으려면 클라이언트에서 요청을 보내야하는데, 2초마다 요청을 계속해서 보낸다고 생각해보자 => 너무 비효율적
서버에게 요청을 보내지 않아도 서버에서 데이터를 클라이언트로 넘겨주면 효율적이지 않을까? => 그래서 나오게 된게 websocket(웹소켓)!!!
웹소켓은 클리이언트와 서버 간의 지속적인 양방향 연결을 제공하여 실시간 데이터 전송과 이벤트 기반 통신을 가능하게 한다
polling(HTTP 방식)은 문자,websocket은 전화라고 생각하면 이해하기 편함
아직 와이어 프레임이 확정은 아니지만
✔️친구 접속 중 상태 표시
=> 이게 제일 중요해 보임
✔️친구 요청
=> 굳이? 실시간성 보장하지 않더라도 재접속 했을 때, 요청 와있으면 친구 요청 뜰 수 있게 하면 쫌 그런가? 암튼 실시간성 보장해주면 좋아보인다
✔️친구 집중 시간 표시
✔️친구가 접속중인 사이트 표시
=> 이 기능은 익스텐션으로 구현해야 할 듯! but, websocket을 사용해야한다는 점은 공통점이다.
작심 서비스에 실시간성을 통한 기능이 정말 많아 보인다..... 할수있다!
import React, { useState, useEffect } from 'react';
const WebSocketComponent = () => {
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket('ws://your-websocket-server-url');
socket.onopen = () => {
console.log('WebSocket connection established');
};
socket.onmessage = (event) => {
const newMessage = event.data;
setMessages((prevMessages) => [...prevMessages, newMessage]);
};
socket.onclose = () => {
console.log('WebSocket connection closed');
};
socket.onerror = (error) => {
console.error('WebSocket error:', error);
};
// Cleanup on unmount
return () => {
socket.close();
};
}, []);
const sendMessage = () => {
const socket = new WebSocket('ws://your-websocket-server-url');
socket.send(message);
setMessage('');
};
return (
<div>
<h1>WebSocket Chat</h1>
<div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button onClick={sendMessage}>Send</button>
</div>
<div>
{messages.map((msg, index) => (
<div key={index}>{msg}</div>
))}
</div>
</div>
);
};
export default WebSocketComponent;
1. WebSocket 설정 및 연결
우선 WebSocket 서버에 연결하는 코드를 작성해야 한다. 이를 위해 useEffect 훅을 사용하여 컴포넌트가 마운트될 때 WebSocket을 설정하고, 언마운트될 때 연결을 정리하도록 한다.
2. WebSocket 연결 및 메시지 전송
메시지를 서버로 전송하기 위한 sendMessage 함수를 작성.
3. WebSocket을 통한 메시지 처리
서버로부터 수신한 메시지를 처리하기 위해 onmessage 핸들러를 사용한다. 이 핸들러는 수신한 메시지를 messages 상태에 추가하는 역할을 한다.
cf) useRef를 사용하여 컴포넌트가 리렌더링되더라도 동일한 WebSocket 객체를 참조하게 해주어서 WebSocket 연결을 효율적으로 관리하는 방법도 있음.
- useEffect를 사용하여 컴포넌트가 마운트될 때 WebSocket 연결을 설정하고, 언마운트될 때 연결을 정리.
- WebSocket의 onmessage 핸들러를 사용하여 서버로부터 수신한 메시지를 처리.
- sendMessage 함수 => 사용자가 입력한 메시지를 서버로 전송.
- open – 커넥션이 제대로 만들어졌을 때 발생함
- message – 데이터를 수신하였을 때 발생함
- error – 에러가 생겼을 때 발생함
- close – 커넥션이 종료되었을 때 발생함
import { useEffect, useState } from 'react';
interface Props {
// 필요한 Props 타입 정의
}
const WebSocketComponent = (props: Props) => {
const [isConnected, setIsConnected] = useState<boolean>(false);
const [friendsStatus, setFriendsStatus] = useState<Record<string, boolean>>({});
useEffect(() => {
const userId = 'yourUserId'; // 현재 사용자의 ID
const ws = new WebSocket(`ws://localhost:8080?userId=${userId}`); // 백엔드 url
ws.onopen = () => {
setIsConnected(true);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setFriendsStatus((prevStatus) => ({
...prevStatus,
[data.userId]: data.isOnline,
}));
};
ws.onclose = () => {
setIsConnected(false);
};
return () => {
ws.close();
};
}, []);
return (
<div>
<h1>Friends Status</h1>
{Object.entries(friendsStatus).map(([id, isOnline]) => (
<div key={id}>
{id}: {isOnline ? 'Online' : 'Offline'}
</div>
))}
</div>
);
};
export default WebSocketComponent;
const [isConnected, setIsConnected] = useState<boolean>(false);
const [friendsStatus, setFriendsStatus] = useState<Record<string, boolean>>({});
- isConnected는 웹소켓이 연결되어 있는지를 나타내는 상태이다.
- friendsStatus는 친구들의 접속 상태를 저장하는 객체, 객체의 키는 친구의 userId이고, 값은 온라인 여부를 나타내는 boolean 값이다.
useEffect(() => {
const userId = 'yourUserId'; // 현재 사용자의 ID
const ws = new WebSocket(`ws://localhost:8080?userId=${userId}`); // 백엔드 url
ws.onopen = () => {
setIsConnected(true);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setFriendsStatus((prevStatus) => ({
...prevStatus,
[data.userId]: data.isOnline,
}));
};
ws.onclose = () => {
setIsConnected(false);
};
return () => {
ws.close();
};
}, []);
useEffect 훅을 사용하여 컴포넌트가 마운트될 때 웹소켓 서버에 연결하고, 언마운트될 때 연결을 종료한다.
- const userId = 'yourUserId'; 부분에서 현재 사용자의 ID를 설정.
- new WebSocket(ws://localhost:8080?userId=${userId}); 부분에서 웹소켓 서버에 연결합니다. 왜? ws://를 사용하냐 간단하게 보안에 더 좋다고 생각하면 된다.
- ws.onopen 이벤트 핸들러는 웹소켓이 성공적으로 연결되었을 때 호출되며, isConnected 상태를 true로 설정한다.
- ws.onmessage 이벤트 핸들러는 서버로부터 메시지를 수신했을 때 호출되며, 수신한 데이터를 파싱하여 friendsStatus 상태를 업데이트한다.
- ws.onclose 이벤트 핸들러는 웹소켓 연결이 종료되었을 때 호출되며, isConnected 상태를 false로 설정합니다.
- return () => { ws.close(); }; 부분에서 컴포넌트가 언마운트될 때 웹소켓 연결을 종료한다.
아티클을 작성하면서 문득 든 생각!
"친구가 접속해있는지 표시해주는 기능은 양방향 통신이 필요한게 아니라, 서버에서 보내주는 데이터를 클라이언트에서 표시해주기만 하면 되는데 굳이 websocket을 사용해야 될까?"
그래서 찾아보았다
SSE (Server-Sent Events): SSE는 서버에서 클라이언트로 단방향 이벤트 스트림을 제공. WebSocket과 비슷하게 실시간 업데이트가 가능하지만, 양방향 통신은 지원하지 않는다. 주로 서버에서 클라이언트로 자주 업데이트를 푸시할 필요가 있는 상황에서 사용된다
=> 친구의 접속 상태와 같은 실시간 업데이트에 적합해 보임.(양방향 통신이 필요 없으니까)
React-Query 및 SWR: React-Query와 SWR은 데이터 페칭 라이브러리로, 실시간성을 제공하는 데는 제한적. 주기적으로 데이터를 새로 고침(polling)하여 업데이트를 받을 수 있지만, WebSocket이나 SSE만큼 즉각적이지 않음. 주기적 업데이트가 허용되는 상황에서는 사용할 수 있지만, 실시간성이 중요한 경우에는 덜 적합.
import { useState, useEffect } from 'react';
interface FriendStatus {
friendId: number;
online: boolean;
}
const FriendStatus = (): JSX.Element => {
const [friendStatus, setFriendStatus] = useState<FriendStatus | null>(null);
useEffect(() => {
const eventSource = new EventSource('http://localhost:3000/events');
eventSource.onmessage = (event) => {
const data: FriendStatus = JSON.parse(event.data);
setFriendStatus(data);
};
eventSource.onerror = (error) => {
console.error('EventSource failed: ', error);
eventSource.close();
};
return () => {
eventSource.close();
};
}, []);
if (!friendStatus) {
return <div>Loading friend status...</div>;
}
return (
<div>
<p>Friend ID: {friendStatus.friendId}</p>
<p>Status: {friendStatus.online ? 'Online' : 'Offline'}</p>
</div>
);
};
export default FriendStatus;
=> 구조나 사용 방식도 socket과 유사하다!
작심 서비스의 친구 접속 중 상태 표시, 친구 집중 시간 표시는 단순히 사용자에게 데이터를 띄워주는 것이 아니라. 내 정보도 내 친구가 알 수 있어야하기 때문에, 내 상태와 정보도 서버로 보내주어야한다. 즉, 양방향 통신인 websocket을 사용하는게 맞다!