- 발행일
[ZBF] 웹 소켓(WebSocket): 실시간 통신을 위한 강력한 도구
[ZBF] 웹 소켓(WebSocket): 실시간 통신을 위한 강력한 도구
이 글은 네이버 블로그에 2024년 5월 28일에 올렸던 것을 그대로 옮겨온 것입니다.
1. 웹 소켓 소개
웹 소켓(WebSocket)은 클라이언트와 서버 간의 양방향 통신을 가능하게 하는 프로토콜입니다. HTTP와 달리, 웹 소켓은 단일 연결을 통해 지속적인 데이터 전송을 허용하여 실시간 애플리케이션에 적합합니다. 특히, 채팅 애플리케이션과 비디오 스트리밍 및 판독 시스템에서 유용하게 사용됩니다.
2. 웹 소켓을 이용한 채팅 애플리케이션
채팅 애플리케이션은 실시간으로 메시지를 주고받는 기능이 필수적입니다. 웹 소켓을 사용하여 간단한 채팅 애플리케이션을 구현해 보겠습니다.
2.1 서버 설정
먼저, Node.js와 WebSocket 라이브러리를 사용하여 서버를 설정합니다.
// 서버 코드 (Node.js)
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', ws => {
ws.on('message', message => {
console.log(`Received: ${message}`);
// 모든 클라이언트에게 메시지 브로드캐스트
server.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.send('Welcome to the chat!');
});
console.log('WebSocket server is running on ws://localhost:8080');
2.2 클라이언트 설정
HTML과 JavaScript를 사용하여 클라이언트를 설정합니다.
<!-- 클라이언트 코드 (HTML) -->
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Chat</title>
</head>
<body>
<input id="messageInput" type="text" placeholder="Type a message..." />
<button onclick="sendMessage()">Send</button>
<div id="chatLog"></div>
<script>
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected to the WebSocket server');
};
ws.onmessage = event => {
const chatLog = document.getElementById('chatLog');
const message = document.createElement('p');
message.textContent = event.data;
chatLog.appendChild(message);
};
function sendMessage() {
const input = document.getElementById('messageInput');
ws.send(input.value);
input.value = '';
}
</script>
</body>
</html>
3. 웹 소켓을 이용한 비디오 판독 시스템
비디오 판독 시스템은 실시간으로 비디오 스트림을 분석하고 판독 결과를 사용자에게 전달해야 합니다. 웹 소켓을 사용하여 이러한 시스템을 구현할 수 있습니다.
3.1 서버 설정
먼저, Node.js와 WebSocket 라이브러리를 사용하여 서버를 설정하고, 비디오 스트림을 처리하는 로직을 추가합니다.
// 서버 코드 (Node.js)
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8081 });
server.on('connection', ws => {
ws.on('message', message => {
console.log(`Received frame: ${message}`);
// 비디오 프레임 처리 로직
const processedFrame = processFrame(message);
// 클라이언트에게 처리된 프레임 전송
ws.send(processedFrame);
});
ws.send('Ready to receive video frames');
});
function processFrame(frame) {
// 여기에서 비디오 프레임을 처리하고 결과를 반환하는 로직을 작성
return `Processed: ${frame}`;
}
console.log('WebSocket server for video processing is running on ws://localhost:8081');
3.2 클라이언트 설정
HTML과 JavaScript를 사용하여 클라이언트를 설정하고, 비디오 스트림을 웹 소켓 서버로 전송합니다.
<!-- 클라이언트 코드 (HTML) -->
<!DOCTYPE html>
<html>
<head>
<title>WebSocket Video Processing</title>
</head>
<body>
<video id="videoElement" autoplay></video>
<div id="resultLog"></div>
<script>
const ws = new WebSocket('ws://localhost:8081');
ws.onopen = () => {
console.log('Connected to the WebSocket server');
};
ws.onmessage = event => {
const resultLog = document.getElementById('resultLog');
const result = document.createElement('p');
result.textContent = event.data;
resultLog.appendChild(result);
};
const videoElement = document.getElementById('videoElement');
navigator.mediaDevices.getUserMedia({ video: true })
.then(stream => {
videoElement.srcObject = stream;
const mediaRecorder = new MediaRecorder(stream);
mediaRecorder.ondataavailable = event => {
ws.send(event.data);
};
mediaRecorder.start(100); // 100ms마다 데이터 전송
})
.catch(error => {
console.error('Error accessing media devices.', error);
});
</script>
</body>
</html>
웹 소켓 연결이 끊어졌을 때 자동으로 재연결하는 방법
웹 소켓 연결이 끊어졌을 때 자동으로 재연결하는 기능을 구현하려면 재연결 로직을 작성해야 합니다.
1. 기본 재연결 로직
재연결을 위해 웹 소켓을 초기화하고, onclose 이벤트 핸들러에서 재연결을 시도합니다.
let ws;
let reconnectInterval = 1000; // 재연결 간격 초기 설정 (1초)
function connect() {
ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
console.log('Connected to the WebSocket server');
reconnectInterval = 1000; // 연결 성공 시 재연결 간격 초기화
};
ws.onmessage = event => {
console.log('Message from server:', event.data);
};
ws.onclose = () => {
console.log('Connection closed, attempting to reconnect...');
setTimeout(() => {
reconnectInterval = Math.min(reconnectInterval * 2, 30000); // 재연결 간격 증가 (최대 30초)
connect();
}, reconnectInterval);
};
ws.onerror = error => {
console.error('WebSocket error:', error);
ws.close(); // 오류 발생 시 연결 종료
};
}
connect();
재연결 로직 설명
- connect 함수는 웹 소켓 연결을 초기화합니다.
- ws.onopen 이벤트 핸들러는 연결이 성공했을 때 호출되며, 재연결 간격을 초기화합니다.
- ws.onmessage 이벤트 핸들러는 서버로부터 메시지를 수신했을 때 호출됩니다.
- ws.onclose 이벤트 핸들러는 연결이 닫혔을 때 호출되며, 재연결을 시도합니다.
- ws.onerror 이벤트 핸들러는 오류가 발생했을 때 호출되며, 연결을 종료합니다.
웹 소켓을 사용하여 실시간에 대한 개발을 할 때 고려해야 할 점
웹 소켓을 사용하여 서버와 클라이언트 간의 빠르고 효율적인 통신이 필요합니다. 다음은 이를 구현할 때 고려해야 할 주요 사항들입니다.
1. 지연 시간(Latency) 최소화
- 지연 시간은 중요한 요소입니다. 서버와 클라이언트 간의 통신을 최적화하여 지연 시간을 최소화해야 합니다.
- 서버의 위치를 최적화하고, 클라이언트의 지리적 위치에 따른 서버 분산을 고려합니다.
2. 상태 동기화(State Synchronization)
- 상태를 클라이언트와 서버 간에 동기화해야 합니다. 이를 위해 상태 업데이트를 주기적으로 전송하고, 서버에서의 상태 변경을 클라이언트에 빠르게 반영합니다.
- 불필요한 데이터 전송을 줄이기 위해 필요한 정보만 전송하도록 합니다.
3. 안정적인 연결 유지
- 연결이 끊어졌을 때 자동으로 재연결하는 로직을 구현합니다.
- 재연결 후 상태를 복원할 수 있도록 합니다.
4. 보안 고려
- 데이터 전송 시 보안 프로토콜(예: WSS)을 사용하여 데이터를 암호화합니다.
- 클라이언트와 서버 간의 인증 및 권한 부여를 통해 불법적인 접근을 방지합니다.
5. 서버 확장성
- 많은 사용자가 동시에 접속할 수 있도록 서버를 확장 가능하게 설계합니다.
- 로드 밸런싱을 사용하여 여러 서버에 부하를 분산시킵니다.
웹 소켓과 HTTP의 차이점
웹 소켓과 HTTP는 서로 다른 목적을 가진 프로토콜로, 주요 차이점은 다음과 같습니다.
1. 연결 방식
- HTTP: 요청-응답 기반의 단방향 통신. 클라이언트가 요청을 보내면 서버가 응답을 보냅니다. 각 요청마다 새로운 연결이 생성되고 종료됩니다.
- 웹 소켓: 양방향 통신이 가능한 지속적인 연결. 초기 연결 이후 클라이언트와 서버 간에 자유롭게 메시지를 주고받을 수 있습니다.
2. 실시간 데이터 전송
- HTTP: 실시간 데이터 전송에 적합하지 않습니다. 주기적으로 요청을 보내야 하므로 지연이 발생할 수 있습니다.
- 웹 소켓: 실시간 데이터 전송에 적합합니다. 지속적인 연결을 통해 즉각적인 데이터 전송이 가능합니다.
3. 데이터 오버헤드
- HTTP: 각 요청과 응답마다 헤더 정보를 포함하여 데이터 오버헤드가 큽니다.
- 웹 소켓: 초기 핸드셰이크 이후에는 데이터 프레임만 전송하여 오버헤드가 적습니다.
4. 사용 사례
- HTTP: 정적인 웹 페이지, API 요청 등 단방향 통신이 주로 필요한 경우.
- 웹 소켓: 채팅 애플리케이션, 실시간 게임, 실시간 데이터 스트리밍 등 양방향 통신이 필요한 경우.
웹 소켓을 사용하여 클라이언트 간의 P2P 통신을 설정
웹 소켓은 기본적으로 클라이언트와 서버 간의 통신을 위해 설계된 프로토콜입니다. 클라이언트 간의 직접적인 P2P (Peer-to-Peer) 통신을 지원하지 않습니다. 하지만, 웹 소켓을 통해 시그널링(Signaling) 서버를 사용하여 WebRTC 연결을 설정하고 P2P 통신을 구현할 수 있습니다.
WebRTC와 웹 소켓을 사용한 P2P 통신
- 시그널링 서버: WebRTC 연결을 설정하기 위해 클라이언트 간의 시그널링 정보를 교환할 시그널링 서버가 필요합니다. 이 역할을 웹 소켓 서버가 수행할 수 있습니다.
- WebRTC 연결 설정:
- 클라이언트는 시그널링 서버(웹 소켓 서버)를 통해 서로의 연결 정보를 교환합니다.
- WebRTC를 사용하여 직접적인 P2P 연결을 설정합니다.
다음은 간단한 예제입니다.
시그널링 서버 (Node.js + WebSocket):
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', ws => {
ws.on('message', message => {
// 수신한 메시지를 다른 모든 클라이언트에게 브로드캐스트
server.clients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});
console.log('WebSocket signaling server is running on ws://localhost:8080');
클라이언트 (WebRTC + WebSocket):
<!DOCTYPE html>
<html>
<head>
<title>WebRTC P2P Example</title>
</head>
<body>
<video id="localVideo" autoplay></video>
<video id="remoteVideo" autoplay></video>
<script>
const localVideo = document.getElementById('localVideo');
const remoteVideo = document.getElementById('remoteVideo');
const ws = new WebSocket('ws://localhost:8080');
let localStream;
let peerConnection;
ws.onmessage = async (message) => {
const data = JSON.parse(message.data);
if (data.offer) {
await peerConnection.setRemoteDescription(data.offer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
ws.send(JSON.stringify({ answer }));
} else if (data.answer) {
await peerConnection.setRemoteDescription(data.answer);
} else if (data.candidate) {
await peerConnection.addIceCandidate(data.candidate);
}
};
async function start() {
localStream = await navigator.mediaDevices.getUserMedia({ video: true });
localVideo.srcObject = localStream;
peerConnection = new RTCPeerConnection();
peerConnection.addStream(localStream);
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify({ candidate: event.candidate }));
}
};
peerConnection.onaddstream = (event) => {
remoteVideo.srcObject = event.stream;
};
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
ws.send(JSON.stringify({ offer }));
}
start();
</script>
</body>
</html>
HTTP/2와 웹 소켓의 차이점
HTTP/2
- 프로토콜 타입: 전송 계층 프로토콜.
- 연결 방식: 단방향 통신. 클라이언트가 요청을 보내고 서버가 응답을 반환.
- 주요 기능:
- 멀티플렉싱: 단일 연결에서 다수의 요청 및 응답을 동시에 처리.
- 헤더 압축: 헤더 정보를 압축하여 전송 효율성 증대.
- 서버 푸시: 서버가 클라이언트의 요청 없이도 리소스를 푸시할 수 있음.
- 사용 사례: 웹 페이지 로딩 최적화, API 요청 등.
웹 소켓
- 프로토콜 타입: 응용 계층 프로토콜.
- 연결 방식: 양방향 통신. 연결 후 클라이언트와 서버 간에 자유롭게 메시지 주고받기 가능.
- 주요 기능:
- 지속적 연결: 한번 연결되면 양방향으로 데이터 전송 가능.
- 낮은 오버헤드: 초기 핸드셰이크 이후 작은 데이터 프레임 전송.
- 사용 사례: 실시간 채팅, 게임, 실시간 데이터 스트리밍.
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', ws => {
ws.on('message', message => {
console.log(`Received: ${message}`);
// 모든 클라이언트에게 메시지 브로드캐스트
server.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.send('Welcome to the chat!');
});
console.log('WebSocket server is running on ws://localhost:8080');
import React, { useState, useEffect, useRef } from 'react';
function App() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const ws = useRef(null);
useEffect(() => {
ws.current = new WebSocket('ws://localhost:8080');
ws.current.onopen = () => {
console.log('Connected to the WebSocket server');
};
ws.current.onmessage = event => {
const newMessage = event.data;
setMessages(prevMessages => [...prevMessages, newMessage]);
};
ws.current.onclose = () => {
console.log('Disconnected from the WebSocket server');
};
return () => {
ws.current.close();
};
}, []);
const sendMessage = () => {
if (input) {
ws.current.send(input);
setInput('');
}
};
return (
<div>
<h1>WebSocket Chat</h1>
<div>
{messages.map((message, index) => (
<div key={index}>{message}</div>
))}
</div>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}
export default App;
- 서버 코드:
- Node.js와 WebSocket을 사용하여 웹 소켓 서버를 설정합니다.
- 클라이언트가 연결되면 connection 이벤트가 발생하고, 메시지를 수신하면 message 이벤트가 발생합니다.
- 수신된 메시지를 모든 연결된 클라이언트에게 브로드캐스트합니다.
- 클라이언트 코드:
- React를 사용하여 간단한 채팅 인터페이스를 생성합니다.
- useRef를 사용하여 웹 소켓 인스턴스를 생성하고, useEffect를 사용하여 컴포넌트가 마운트될 때 웹 소켓 연결을 설정합니다.
- 메시지를 수신하면 onmessage 이벤트 핸들러가 호출되어 메시지를 상태에 추가합니다.
- 사용자가 메시지를 입력하고 전송 버튼을 누르면 sendMessage 함수가 호출되어 입력된 메시지를 웹 소켓을 통해 서버로 전송합니다.