WebSocket은 웹 애플리케이션에서 실시간 양방향 통신을 구현하기 위한 표준 프로토콜이다. 전통적인 HTTP 통신이 클라이언트의 요청-서버 응답 패턴에 의존하는 반면, WebSocket은 하나의 TCP 연결을 통해 서버와 클라이언트가 서로 독립적으로 데이터를 전송할 수 있는 전이중 통신을 지원한다.
WebSocket 프로토콜의 핵심 특성
WebSocket은 OSI 7계층 모델의 애플리케이션 층에 위치하는 프로토콜로서, TCP 기반의 영구 연결을 구축한다. 이 프로토콜의 가장 큰 장점은 연결 수립 후 클라이언트와 서버 모두 상대방에게 먼저 데이터를 전송할 수 있다는 점이다. 또한 HTTP와 달리 통신 과정에서 전송되는 제어 프레임의 크기가 매우 작아 오버헤드를 최소화할 수 있다.
주요 특징은 다음과 같다:
- TCP 프로토콜을 기반으로 동작
- 프로토콜 식별자는 ws이며, 암호화 버전은 wss
- 동일 출처 정책(Same-Origin Policy)의 제한을 받지 않아 도메인 간 통신 가능
- 상태를 유지하는 연결로 통신 시 상태 정보 재전송 불필요
- 텍스트와 바이너리 데이터 모두 전송 지원
WebSocket 연결 구현
연결 수립
JavaScript에서 WebSocket 객체 생성자를 사용하여 서버와의 연결을 초기화한다.
// 서버 엔드포인트 주소로 WebSocket 클라이언트 생성
const socket = new WebSocket('wss://api.example.com:8443/ws');
// 연결 객체 상태 확인
console.log(socket);
연결 객체에는 연결 상태, 수신 데이터 처리 콜백, 프로토콜 정보 등의 속성이 포함되어 있다.
연결 상태 및 이벤트 처리
| 상태 코드 | 의미 |
|---|---|
| 0 | 연결 수립 중 |
| 1 | 연결 완료, 통신 가능 |
| 2 | 연결 종료 대기 중 |
| 3 | 연결 종료됨 |
const socket = new WebSocket('wss://api.example.com:8443/ws');
// 연결 성공 시 실행
socket.addEventListener('open', function(event) {
console.log('서버 연결 성공');
socket.send('안녕하세요!');
});
// 오류 발생 시 실행
socket.addEventListener('error', function(event) {
console.log('연결 오류 발생:', event);
});
// 메시지 수신 시 실행
socket.addEventListener('message', function(event) {
console.log('서버からのメッセージ:', event.data);
});
// 연결 종료 시 실행
socket.addEventListener('close', function(event) {
console.log('연결 종료:', event.code, event.reason);
});
자동 재연결 구현
네트워크 문제나 서버 재시작 등으로 연결이 끊어질 수 있으므로, 연결 종료를 감지하여 자동으로 재연결을 시도하는 로직이 필요하다.
class WebSocketClient {
constructor(url) {
this.url = url;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.addEventListener('open', () => {
console.log('연결 성공');
this.heartbeat();
});
this.socket.addEventListener('close', (event) => {
console.log('연결 종료, 재연결 시도...');
setTimeout(() => this.connect(), 5000);
});
this.socket.addEventListener('message', (event) => {
console.log('수신:', event.data);
});
}
heartbeat() {
if (this.heartbeatTimer) {
clearTimeout(this.heartbeatTimer);
}
this.heartbeatTimer = setInterval(() => {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send('PING');
}
}, 30000);
}
send(data) {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
}
}
disconnect() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
}
this.socket.close();
}
}
Keep-Alive 메커니즘
장시간 연결이 유지되면 서버나 네트워크 장비에서 연결을 자동으로 종료할 수 있다. 이를 방지하기 위해 주기적으로 Ping/Pong 메시지를 교환하는 Keep-Alive 메커니즘을 구현한다.
function startHeartbeat(connection, interval = 30000) {
let timerId = null;
const sendHeartbeat = () => {
if (connection.readyState === WebSocket.OPEN) {
connection.send(JSON.stringify({ type: 'heartbeat' }));
}
};
timerId = setInterval(sendHeartbeat, interval);
return () => {
if (timerId) {
clearInterval(timerId);
}
};
}
// 사용 예시
const ws = new WebSocket('wss://api.example.com:8443/ws');
const stopHeartbeat = startHeartbeat(ws);
// 연결 종료 시Heartbeat 중지
ws.addEventListener('close', stopHeartbeat);
인증 토큰 전달 방법
WebSocket 연결 시 인증이 필요한 경우, 여러 가지 방법으로 토큰을 전달할 수 있다.
방법 1: 연결 수립 후 메시지로 전송
const ws = new WebSocket('wss://api.example.com:8443/ws');
const authToken = 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
ws.addEventListener('open', () => {
ws.send(JSON.stringify({
type: 'auth',
token: authToken
}));
});
방법 2: 쿼리 파라미터로 전달
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...';
const ws = new WebSocket(`wss://api.example.com:8443/ws?token=${encodeURIComponent(token)}`);
방법 3: 서브프로토콜 이용
const ws = new WebSocket('wss://api.example.com:8443/ws', ['jwt', 'token']);
WebSocket 프로토콜은 실시간 채팅 애플리케이션, 실시간 데이터 시각화, 온라인 게임, 알림 시스템 등 다양한 분야에서 활용되며, 현대 웹 개발에서 필수적인 기술로 자리잡았다.