기존 프로젝트를 진행하며 소켓 연결을 관리하고 싶은데 소켓을 전체 프로젝트에서 사용하기 때문에 불가피하게 우선 전역으로 관리하게 되었다. 하지만 전역으로 소켓 연결을 하면 브라우저에서 탭이 늘어나면 그에 따라 소켓 연결도 많아지는 점이 있었기 때문에 거부감이 들었다. 따라서 다른팀이 만든 프로젝트에서 가장 먼저 확인한 부분이 소켓 연결 부분이었고 해당 프로젝트도 불가피하게 소켓 연결을 전역으로 두고 사용하는 것을 알 수 있었다.
소켓 연결을 효율적으로 관리하기 위한 방법을 찾던중 아래 영상을 보게 되었고 SharedWorker 키워드에 대해 알게 되었다.
토스ㅣSLASH 24 - N개의 탭, 단 하나의 웹소켓: SharedWorker
Shared Worker는 웹 워커의 한 종류로, 같은 출처(origin)의 여러 브라우저 컨텍스트(탭, iframe, 다른 워커 등)에서 공유할 수 있는 백그라운드 스크립트이다.
일반적인 Web Worker와 다른 점은 여러개의 탭을 열어서 접근할 경우 하나의 소켓 연결을 이용하기 때문에 효율적으로 소켓 연결을 관리할 수 있다는 장점이 있다.
우선 Shared Worker에 대해 학습하기 위해 Vite를 이용해 만든 React 프로젝트를 이용하기로 했다.
Vite를 이용해 생성하면 가장 처음에 있는 카운팅 기능은 여러 탭에서 진행할 경우 각각의 탭에서 별개의 카운트가 작동한다. 예를 들어 아래와 같이 동작한다.
let count = 0;
const ports = new Set();
self.onconnect = function(e) {
const port = e.ports[0];
ports.add(port);
// 초기 상태 전송
port.postMessage({ type: 'init', value: count });
// 메시지 수신 처리
port.onmessage = function(e) {
if (e.data === 'increment') {
count++;
broadcastAll({ type: 'count', value: count });
broadcastAll({ type: 'broadcast', value: count });
}
}
port.start();
port.onmessageerror = () => ports.delete(port);
}
function broadcastAll(message) {
ports.forEach(port => {
port.postMessage(message);
});
}
function App() {
const [count, setCount] = useState(0);
const workerRef = useRef(null);
useEffect(() => {
// Shared Worker 생성
workerRef.current = new SharedWorker(
new URL('./workers/counter-worker.js', import.meta.url),
{ type: 'module' }
);
// 메시지 핸들러 설정
const messageHandler = (e) => {
const { type, value } = e.data;
switch(type) {
case 'init':
case 'count':
setCount(value);
break;
case 'broadcast':
console.log(`Broadcast received in tab ${document.title}:`, value);
break;
}
};
workerRef.current.port.onmessage = messageHandler;
workerRef.current.port.start();
// 클린업
return () => {
if (workerRef.current) {
workerRef.current.port.close();
}
};
}, []);
const handleIncrement = () => {
workerRef.current?.port.postMessage('increment');
};
// ... JSX 반환
}