[번역] 브라우저 탭과 창 사이 WebSocket 커넥션 공유하기

Saetbyeol·2023년 6월 30일
8

translations.zip

목록 보기
7/14
post-thumbnail

원문: Sharing WebSocket Connections between Browser Tabs and Windows

원저자: Szymon Chmal

WebSocket 커넥션은 웹 브라우저와 서버가 실시간으로 서로 소통할 수 있는 특별한 통신 채널과 같습니다. 그러나 브라우저에서 여러 개의 탭이나 창을 띄우는 경우 각 탭과 창은 각자의 WebSocket 커넥션을 만들게 됩니다. 이는 자원을 낭비하며 웹 애플리케이션의 성능에 영향을 줄 수 있습니다.

하지만 걱정하지 마세요! 이 글에서 다른 탭과 창에서 애플리케이션을 열 때 WebSocket 사용을 최적화하는 방법에 대해 알아볼 것입니다. 탭과 창 간에 WebSocket 커넥션을 공유하면 웹 애플리케이션은 좀 더 효율적이고 원활하게 동작할 수 있습니다. 이제 브라우저 탭과 창 간에 WebSocket 커넥션을 공유하기 위해 몇 가지 유용한 전략들에 대해 자세히 살펴봅시다.

공유 워커(Shared Workers)에 대해서

브라우저 탭과 창 간에 WebSocket 커넥션을 공유하기 위해 웹 워커의 힘을 빌려볼 수 있습니다. 웹 워커는 본질적으로 별개의 스레드로, 다른 스크립트와 완전히 독립적이며 DOM에 접근할 수 없습니다. 백그라운드에서 작업을 실행할 수 있기 때문에 복잡한 연산을 수행하거나 웹 페이지가 멈추거나 응답하지 않는 것을 방지하는 데 특히 유용합니다.

공유 워커는 여러 개의 브라우저 탭과 창 사이에서 공유할 수 있는 특별한 유형의 웹 워커입니다. 단일 웹 페이지 전용으로 사용하는 일반적인 웹 워커와 다르게 공유 워커는 특정 웹 페이지나 사용자 인터페이스와 독립적으로 존재합니다. 탭이나 창과 같은 여러 개의 브라우저 컨텍스트들이 통신하며 자원을 공유할 수 있게 만듭니다. 공유 워커를 활용하면, WebSocket 커넥션을 관리하고 다양한 브라우저 컨텍스트 간에 공유를 편하게 만들어 주는 영구적인 엔터티를 만들 수 있습니다. 또한 커넥션을 닫는 것을 걱정할 필요가 없습니다. 더 이상 참조할 탭이나 창이 없으면 공유 워커 또한 존재하지 않으므로 커넥션이 자동으로 닫히기 때문입니다.

공유 워커로 WebSocket 커넥션 공유 구현하기

브라우저 탭과 창 간 WebSocket 커넥션 공유를 구현하기 위해서는 다음과 같은 단계를 따라야 합니다.

공유 워커 생성하기

Shared Worker API를 사용하여 공유 워커를 생성하는 것부터 시작해 보겠습니다. 이 워커는 WebSocket 커넥션을 관리하는 중앙 엔터티의 역할을 합니다. 메시지 핸들러를 추가하여 워커에서 데이터를 수신하고 원하는 작업을 수행할 수 있습니다.

// 번들러 없음
const worker = new SharedWorker("./worker.js");

// 웹팩 5
const worker = new SharedWorker(new URL("./worker.ts", import.meta.url));

worker.port.addEventListener("message", (event: MessageEvent): void => {
  // 데이터로 원하는 작업을 모두 수행합니다.
});
worker.port.start();

WebSocket 커넥션 맺기

공유 워커 내에서 WebSocket 객체를 생성하고 서버와 커넥션을 맺습니다. 이 커넥션은 모든 브라우저 탭과 창에서 공유됩니다. 커넥션 핸들러를 추가하고 포트들을 배열에 저장합니다. 또한 수신한 각 메시지를 MessagePort 객체에 전달하는 것 또한 중요합니다.

const ports: MessagePort[] = [];
const ws = new WebSocket("wss://some-url.com");

addEventListener("connect", (event: MessageEvent): void => {
  const port = event.ports[0];
  this.ports.push(port);
});

ws.addEventListener("message", (event: MessageEvent): void => {
  ports.forEach((port) => {
    port.postMessage(event.data);
  });
});

이게 전부입니다. 이제 커넥션을 공유하고 모든 탭과 창에서 동시에 메시지를 받을 수 있습니다.

과제와 해결책: 메모리 관리

공유 워커는 효율적인 해결책을 제공하지만 다뤄야 하는 과제가 하나 있습니다. 바로 메모리 관리입니다. 메시지 포트가 활성화되어 있는지 알 수 있는 내장된 메커니즘이 없기 때문에 참조 값들은 시간이 지나면서 계속 누적되고 이는 잠재적인 메모리 누수로 이어질 수 있습니다. 이 문제를 줄이기 위해 WeakRef를 활용할 수 있습니다. WeakRef를 통해 메시지 포트가 가비지 콜렉트 되었는지 확인할 수 있어 효율적으로 자원을 이용할 수 있게 됩니다.

export class BrowserPort {
  private readonly weakRef: WeakRef<MessagePort>;

  constructor(port: MessagePort) {
    this.weakRef = new WeakRef(port);
    port.start();
  }

  isAlive(): boolean {
    return !!this.weakRef.deref();
  }

  postMessage(message: unknown): void {
    this.weakRef.deref()?.postMessage(message);
  }

  addEventListener(event: string, handler: (event: Event) => void): void {
    this.weakRef.deref()?.addEventListener(event, handler);
  }

  removeEventListener(event: string, handler: (event: Event) => void): void {
    this.weakRef.deref()?.removeEventListener(event, handler);
  }

  close(): void {
    this.weakRef.deref()?.close();
  }
}

let ports: BrowserPort[] = [];

addEventListener("connect", (event: MessageEvent): void => {
  const port = event.ports[0];
  ports.push(new BrowserPort(port));
});

// 메시지를 보낼 때

if (port.isAlive()) {
  // 포트가 살아있습니다! 메시지를 보낼 수 있습니다.
  port.sendMessage("msg");
} else {
  // 포트가 죽었습니다! 배열에서 포트를 제거해야 합니다.
  ports = ports.filter((innerPort) => innerPort !== port);
}

또 다른 해결책은 onbeforeunload 이벤트 핸들러 내에 특별한 제어 메시지를 보내는 것입니다. 이 방법은 신뢰하기 어렵고, 브라우저가 이 메시지를 무시하여 다른 워커에 전혀 전달되지 않을 수도 있다는 점을 명심하세요.

// 브라우저
window.addEventListener("onbeforeunload", (): void => {
  worker.port.sendMessage("UNLOAD");
});

// 워커
let ports: MessagePort[] = [];

addEventListener("connect", (event: MessageEvent): void => {
  const port = event.ports[0];
  ports.push(port);

  port.addEventListener("message", (event: MessageEvent): void => {
    if (event.data === "UNLOAD") {
      ports = ports.filter((innerPort) => innerPort !== port);
    }
  });
});

마무리

이 글에서는 브라우저 탭과 창 간에 WebSocket 커넥션을 공유하는 개념을 살펴보았는데요. 공유 워커를 활용하면 리소스 사용을 최적화할 수 있고 여러 탭이나 여러 창을 띄운 경우 웹 애플리케이션의 성능을 향상할 수 있었습니다.

공유 워커는 WebSocket 커넥션을 관리하는 중앙 엔티티를 생성하고 서로 다른 브라우저 컨텍스트 간에 통신을 원활하게 하는 강력한 메커니즘을 제공합니다. 공유 워커를 생성하고 MessagePort API를 활용하면 브라우저 탭과 창 사이에 동일한 메시지를 전달하여 단일 WebSocket 커넥션을 공유할 수 있습니다.

메모리 관리와 같이 고려해야 하는 과제들도 존재하지만 WeakRef와 같은 적절한 기술을 잘 활용한다면 잠재적인 문제들을 줄이는 데 도움이 됩니다. 효율적으로 자원을 이용하고 메모리 누수를 방지하기 위해서는 적절한 전략으로 구현하는 것이 중요합니다.

WebSocket 커넥션 공유를 구현함으로써 웹 애플리케이션의 효율성, 응답성, 그리고 전반적인 사용자 경험을 향상할 수 있습니다. 사용자는 불필요하게 자원을 중복적으로 사용하지 않으면서 여러 탭과 창에서 원활하게 상호작용할 수 있습니다.

행코하세요!

0개의 댓글