
해외선물 HTS 웹 화면을 만들면서 가장 오래 붙잡고 있던 문제는 웹소켓이 에러 없이 조용히 끊기는 경우였습니다. 화면은 멀쩡해 보이는데 시세가 30초째 멈춰 있고, 사용자는 멈춘 가격을 보고 주문을 넣습니다. 실시간 시세 화면에서 연결 끊김을 감지하고, 재연결하고, 끊긴 동안 놓친 데이터를 복구한 방법을 정리합니다.
웹소켓은 TCP 위에서 돕니다. 서버가 정상적으로 연결을 닫으면 onclose가 오지만, 다음 경우에는 브라우저가 한참 동안 끊긴 줄 모릅니다.
이때 readyState는 여전히 OPEN입니다. 그래서 이벤트를 기다리지 말고 직접 확인해야 합니다.
가장 단순하고 확실한 방법은 마지막으로 메시지를 받은 시각을 기록해 두고, 일정 시간 아무것도 안 오면 끊긴 것으로 보는 것입니다. 시세가 뜸한 종목도 있으니 서버가 몇 초마다 가벼운 하트비트를 보내게 합니다.
let lastMsgAt = Date.now();
ws.onmessage = (e) => {
lastMsgAt = Date.now();
handle(JSON.parse(e.data));
};
// 서버 하트비트 5초 간격 기준, 15초 무소식이면 끊긴 것으로 판단
setInterval(() => {
if (Date.now() - lastMsgAt > 15000) ws.close(4000, 'stale');
}, 3000);
close()를 직접 호출해 onclose 경로로 합류시키면 재연결 로직을 한 곳에서만 관리할 수 있습니다.
브라우저 이벤트도 같이 씁니다. online 이벤트가 오거나 탭이 다시 보이면(visibilitychange) 즉시 상태를 확인합니다. 절전 복귀 직후가 가장 흔한 끊김 시점입니다.
끊기자마자 무한 재시도하면 서버가 재시작될 때 모든 클라이언트가 동시에 몰려 서버를 다시 쓰러뜨립니다. 대기 시간을 두 배씩 늘리고, 무작위 값을 섞어 접속 시점을 흩어 놓습니다.
let retry = 0;
function connect() {
ws = new WebSocket(URL);
ws.onopen = () => { retry = 0; resubscribe(); resync(); };
ws.onclose = () => {
const base = Math.min(30000, 1000 * 2 ** retry++);
setTimeout(connect, base / 2 + Math.random() * base / 2);
};
}
retry를 0으로 되돌립니다.웹소켓은 연결이 새로 생기면 서버 쪽 구독 상태도 새로 시작합니다. 재연결 후 보고 있던 종목, 호가창, 체결 목록 구독을 다시 보내야 합니다. 클라이언트가 현재 구독 목록을 항상 들고 있게 하고, onopen에서 그 목록을 통째로 다시 보내는 방식이 가장 단순합니다.
재연결만 하면 끊긴 사이의 체결과 가격 변화가 빠집니다. 차트에 구멍이 나고 호가창이 틀린 상태로 남습니다.
스냅샷을 받는 동안 들어오는 증분은 버퍼에 쌓아 두었다가, 스냅샷 시퀀스보다 뒤의 것만 적용해야 순서가 꼬이지 않습니다.
실시간 금융 화면에서는 이게 제일 중요합니다. 끊겨 있는 동안에는 멈춘 가격이 실시간인 것처럼 보이면 안 됩니다.
onclose만 믿지 말고 마지막 수신 시각과 하트비트로 끊김을 직접 감지합니다.