Web Socket Protocol 이란?
두 프로그램간 양방향으로 메시지를 교환하기 위한 통신규약을 말한다. IETF의 RFC6455 에서 표준 프로토콜로 정의하고, W3C에 의해서 웹 기술 표준으로 정의하고 있다.
웹 소켓의 특징
1. 양방향 통신
클라이언트와 서버가, 서로 통신을 주고받을 수 있다. HTTP 프로토콜은 단방향 통신이 특징인 것과 차별점을 가진다.
2. 지속적인 연결
HTTP 통신은 비연결성을 가진다. 한번 요청, 응답을 주고받으면 종료되는 것이 기본 특징이다. Web Socket은 한번 연결이 되면, close 할 때까지 지속적으로 서로 메시지를 주고받을 수 있다. 그러므로, 불필요한 handshake 과정이 없고, 불필요하게 큰 header 정보를 계속 주고받지 않아도 된다. 그래서 실시간 채팅앱, 주식 차트, IoT 기기 센싱 등 다양한 곳에서 사용된다.
웹 소켓의 동작
- HTTP 프로토콜로 handshake 하여, 양쪽 서버 연결 시도
- ws 프로토콜로 메시지 양방향으로 주고받기
- close 호출 시, socket 연결 종료
실제로 연결하고, 브라우저 네트워크탭의 WS 로 필터링하면 세부 정보를 볼 수 있다. HTTP GET 으로 통신하며, 101 Switching Protocols 상태를 받는다.
Node.js 구현 (with ws)
Node.js 라이브러리인 ws 를 활용하여 구현한다. 구현 자체는 매우 간단하다.
- WebSocket 서버를 생성한다.
- 클라이언트에서 메시지 이벤트를 받았을 때, 모든 클라이언트로 데이터를 브로드캐스트해준다.
import { WebSocketServer, WebSocket } from "ws";
import { createServer } from "http";
// HTTP 서버 생성 (WebSocket 연결을 위해 HTTP 서버가 필요)
const server = createServer();
const wss = new WebSocketServer({ server });
// 클라이언트 연결 시 실행될 이벤트 리스너 설정
wss.on("connection", (ws: WebSocket) => {
console.log("New client connected");
// 클라이언트에서 메시지 수신 시 실행되는 이벤트
ws.on("message", (message: string) => {
const parsedMessage = JSON.parse(message);
// 모든 클라이언트에게 브로드캐스트
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(parsedMessage));
}
});
});
});
const PORT = 8080;
server.listen(PORT, () => {
console.log(`Server is running on <http://localhost>:${PORT}`);
});
서버 테스트
Client 를 만들기 어렵다면, terminal 로 웹소켓 서버와 연결할 수 있다.
npm install wscat -g 로 wscat 라이브러리를 설치하고, wscat -c ws://localhost:8080 로 웹소켓 서버와 연결한다.
그러면 prompt 가 가능하고, 메세지를 송수신 해볼 수 있다.
Client 구현 (with React)
React를 활용해서 Web Socket Client 를 구현해보자. 간단한 채팅앱을 구현해볼 것이다.
브라우저에서 기본적으로 제공하는 WebSocket 객체를 활용할 것이다.
- WebSocket Server 와 연결
- 서버의 메세지 이벤트를 받아서, 상태 업데이트
const [socket, setSocket] = useState<WebSocket | null>(null);
const [messages, setMessages] = useState<
{ clientId: string; text: string }[]
>([]);
useEffect(() => {
// WebSocket 서버 연결
const ws = new WebSocket("ws://localhost:8080");
setSocket(ws);
// 서버로부터 메시지 수신
ws.onmessage = (event) => {
const messageData = JSON.parse(event.data);
setMessages((prevMessages) => [...prevMessages, messageData]);
};
return () => {
// 컴포넌트 언마운트 시 WebSocket 연결 종료
ws.close();
};
}, []);
3. 클라이언트에서 메시지를 전송한다.
const sendMessage = () => {
if (socket && input) {
const message = { text: input, clientId };
socket.send(JSON.stringify(message));
setInput("");
}
};
상세한 구현은 아래 repository 에 남긴다.
https://github.com/euijinkk/web-socket-full-practice
구현을 완료하고, 탭을 여러개 띄워서, 2개 이상의 Client 에 접속하여, 하나의 Web Socket server에 여러개의 Client 와 연결한다. 그리고 각 탭에서 채팅을 보내 실시간으로 정보를 주고받을 수 있는지 확인한다.
웹소켓 사용시 주의할 점과 대안
웹소켓은 클라이언트-서버 간의 지속적인 연결을 유지한다. 그러므로, 잘못 사용하면 불필요하게 메모리, CPU 리소스를 소모할 수 있다.
그러므로 꼭 필요할 때만 사용하고, 그렇지 않다면 다른 통신 방법을 사용하는 것이 바람직하다. 꼭 필요한 상황은, 양방향 + 실시간 통신이 요구될 때이다.
대안으로는 Polling, SSE(Server Sent Event) 등이 있다.
Polling 은 HTTP 요청을 주기적으로 보내는 방식을 말한다. 특정 시간 안에, 서버에서 데이터가 오는 것을 확신할 수 있고, 완벽한 실시간성을 요구하지 않는다면, Polling 을 사용하는 것으로 충분하다. 긴 시간 속에서 언제 서버가 update 되는지 알 수 없다면, 불필요한 HTTP 요청이 많아질 것이다. 또한 완벽한 실시간성을 요구한다면, Polling interval 을 줄여야 하고, 그러면 서버 부하가 더 커질뿐더러, HTTP 통신 자체가 느려서 어느정도 통신 지연을 가질 수밖에 없다.
SSE는 Web Socket과 마찬가지로 한번 HTTP 통신으로 서버와 클라이언트를 연결한 후, 서버가 클라이언트로 단방향으로 지속적으로 메시지를 보낼 수 있다. 양방향 통신은 필요없지만, 실시간 통신을 요구하는 경우 이 기능을 사용할 수 있다.
마치며
간단한 형태의 채팅앱을 만들어보며, Server, Client 에서 어떻게 Web Socket 통신하는지 알아보았다. 구현하면서 프론트엔드, 서버 구조 모두에서 다양한 의문이 들었다. 클라이언트에서 Socket 이 끊어질 때를 대응하고, Socket 통신이 필요없을 때는 적절한 시점에 끊어서 메모리 누수를 방지한다거나 하는 고민을 해볼 수 있다. 서버에서는 로드밸런싱은 어떻게 할지, 실시간성을 지키기 위해서 어떻게 Socket 메시지를 처리해야할지, 어떤 DB를 사용해야 할지 등을 생각해볼 수 있다. 생각할 포인트가 너무 많았고, 웹소켓 통신으로 좋은 경험을 전달하는 것이 기술적으로 엄청나게 어려운 문제임을 깨달았다.
'Sofeware Development' 카테고리의 다른 글
React에서 중복호출(aka. 따닥)을 막는 완벽한 방법 (3) | 2024.04.14 |
---|---|
React CleanCode #2. UI Variation에 유연하게 대응하기 (3) | 2024.01.28 |
ErrorBoundary 가 포착할 수 없는 에러와 그 이론적 원리 분석 (3) | 2023.12.10 |
프론트엔드 개발자가 서버를 공부하는 이유 / 학습 방법에 관한 글 (7) | 2023.09.29 |
React CleanCode #1, 합성으로 관심사를 분리하기 (12) | 2023.05.16 |