Skip to content

실시간 통신 기술 정리

jjeonghak edited this page Dec 5, 2024 · 2 revisions

실시간 통신 기술 정리

t

Polling

Short Polling

주기적으로 서버에 요청을 보냄
일정 시간마다 데이터가 갱신되었는지 확인
서버가 요청에 대한 부담이 크지 않으며 실시간성이 중요하지 않은 경우

Long Polling

요청을 보낸 후 서버에서 변경이 일어날 때까지 대기
연결 요청을 보낸 후 변경된 값을 받으면 다시 연결 요청
실시간 메시지 전달이 중요하지만 서버 상태가 빈번하게 변경되지 않는 경우

// 자바스크립트 클라이언트의 롱 폴링
function longPoll() {
  fetch('http://example.com/poll')
    .then(response => response.json())
    .then(data => {
      console.log("Received data:", data);
      longPoll(); // 즉시 새로운 롱 폴링 요청을 설정
    })
    .catch(error => {
      /**
       * 에러는 정상적인 조건에서 연결 시간을 초과하거나
       * 클라이언트가 오프라인 상태가 될 때 발생할 수 있습니다.
       * 에러가 발생하면 약간의 지연 후에 다시 폴링을 시작합니다.
       */
      setTimeout(longPoll, 10000);
    });
}
longPoll(); // 롱 폴링을 시작합니다.

SSE(Server Sent Event)

한번 연결을 맺고 일정시간 동안 서버의 변경 사항을 전송 받음
long polling 방식에 비해 다시 연결 요청이 필요 없음
http persistent connections 기반
서버 업데이트를 클라이언트로 푸시하는 표준 방식
웹 소켓과 달리 서버에서 클라이언트로의 단방향 통신 전용으로 설계
서버 측에서 스크립트에 반드시 Content-Type: text/event-stream 설정 필요

// 서버 측 이벤트 스트림에 연결
const evtSource = new EventSource("https://example.com/events");

// 일반 메시지 이벤트 처리
evtSource.onmessage = event => {
  console.log('got message: ' + event.data);
};
import express from 'express';
const app = express();
const PORT = process.env.PORT || 3000;

app.get('/events', (req, res) => {
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
  });

  const sendEvent = (data) => {
    // 모든 메시지 줄은 'data: '로 시작해야 합니다.
    const formattedData = `data: ${JSON.stringify(data)}\n\n`;
    res.write(formattedData);
  };

  // 2초마다 이벤트를 보냅니다.
  const intervalId = setInterval(() => {
    const message = {
      time: new Date().toTimeString(),
      message: 'Hello from the server!',
    };
    sendEvent(message);
  }, 2000);
  
  // 연결이 종료되면 정리합니다.
  req.on('close', () => {
    clearInterval(intervalId);
    res.end();
  });
});
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));

WebSocket

클라이언트와 서버가 http 3 way handshaking 이후 웹 소켓 프로토콜을 이용한 상호간 통신
양방향 통신이 가능하며 웹소켓 포트에 접속한 모든 클라이언트에게 이벤트 방식으로 응답 가능
클라이언트와 서버가 http 요청-응답 주기 오버헤드 없이 데이터 통신 가능
소켓 연결이 여전히 사용 가능한지 여부를 ping-and-pong heartbeat를 추가해서 확인

// 클라이언트의 웹 소켓
const socket = new WebSocket('ws://example.com');
  socket.onopen = function(event) {
  console.log('Connection established');
  // 서버에 메시지 보내기
  socket.send('Hello Server!');
};

socket.onmessage = function(event) {
  console.log('Message from server:', event.data);
};

Web Transport API

클라이언트와 서버 간의 효율적이고 지연 시간이 짧은 통신을 위해 설계된 API
http/3 QUIC(Quick UDP Internet Connections) 프로토콜을 활용 가능
사파리 브라우저 사용 불가, node.js 기본적 지원 안함
여러 데이터 스트림을 단일 연결을 통해 전송 가능
여러 연결을 설정하는 오버헤드 제거
흐름 제어, 우선 순위 지정, 신뢰할 수 있는 전송 기능 지원

import { readFile } from "node:fs/promises";
import { createServer } from "node:https";
import { Server } from "socket.io";
import { Http3Server } from "@fails-components/webtransport";

// SSL/TLS 인증서를 읽어들입니다
const key = await readFile("./key.pem");
const cert = await readFile("./cert.pem");

// HTTPS Server를 생성합니다, 이 과정에서 인증서를 사용하며 구성되며 
// GET요청에 대해 index.html / 그 외 요청에는 404 오류를 return 하게 했습니다
const httpsServer = createServer({
  key,
  cert
}, async (req, res) => {
  if (req.method === "GET" && req.url === "/") {
    const content = await readFile("./index.html");
    res.writeHead(200, {
      "content-type": "text/html"
    });
    res.write(content);
    res.end();
  } else {
    res.writeHead(404).end();
  }
});

const port = process.env.PORT || 3000;

httpsServer.listen(port, () => {
  console.log(`server listening at https://localhost:${port}`);
});

// Server 클래스는 Socket.IO서버를 생성합니다. 
// HTTPS 서버 위에서 실행되며, 다양한 트랜스포트 방식을 지원합니다. 
const io = new Server(httpsServer, {
  transports: ["polling", "websocket", "webtransport"]
});

// 클라이언트가 서버에 연결될 때의 이벤트 리스너.
// 연결, 업그레이드, 연결해제시 로그를 출력하도록 하였습니다
io.on("connection", (socket) => {
  console.log(`connected with transport ${socket.conn.transport.name}`);

  socket.conn.on("upgrade", (transport) => {
    console.log(`transport upgraded to ${transport.name}`);
  });

  socket.on("disconnect", (reason) => {
    console.log(`disconnected due to ${reason}`);
  });
});

// Http3Server는 WebTransport프로토콜을 사용하는 서버를 생성합니다. 
const h3Server = new Http3Server({
  port,
  host: "0.0.0.0",
  secret: "changeit",
  cert,
  privKey: key,
});

// Http3Server를 시작하여 WebTransport연결을 수용합니다
h3Server.startServer();

// sessionStream 메소드를 통해 socket.io와의 통합이 이루어집니다
(async () => {
  const stream = await h3Server.sessionStream("/socket.io/");
  const sessionReader = stream.getReader();

  while (true) {
    const { done, value } = await sessionReader.read();
    if (done) {
      break;
    }
    io.engine.onWebTransportSession(value);
  }
})();
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Socket.IO WebTransport example</title>
  </head>
  <body>
    <p>Status: <span id="status">Disconnected</span></p>
    <p>Transport: <span id="transport">N/A</span></p>

    <script src="/socket.io/socket.io.js"></script>
    <script>
      const $status = document.getElementById("status");
      const $transport = document.getElementById("transport");

      // io함수를 이용해 socket.io 클라이언트 인스턴스 생성한다
      const socket = io({
        transportOptions: {
          webtransport: {
            hostname: "127.0.0.1"
          }
        }
      });

      // 이벤트 리스너: connect, disconnect, connect_error, upgrade 이벤트에 대해서 
      // 리스너를 설정하여 연결상태와 트랜스포트 방식의 변경을 콘솔과 웹 페이지에 로그합니다
      // connect
      socket.on("connect", () => {
        console.log(`connected with transport ${socket.io.engine.transport.name}`);

        $status.innerText = "Connected";
        $transport.innerText = socket.io.engine.transport.name;

        // upgrade
        socket.io.engine.on("upgrade", (transport) => {
          console.log(`transport upgraded to ${transport.name}`);

          $transport.innerText = transport.name;
        });
      });

      // connect_error
      socket.on("connect_error", (err) => {
        console.log(`connect_error due to ${err.message}`);
      });

      // disconnect
      socket.on("disconnect", (reason) => {
        console.log(`disconnect due to ${reason}`);

        $status.innerText = "Disconnected";
        $transport.innerText = "N/A";
      });
    </script>
  </body>
</html>

WebRTC(Web Real-Time Communication)

웹 브라우저와 모바일 애플리케이션에서 실시간 통신(RTC) 기능을 제공
복잡한 서버 인프라나 추가 플러그인 설치 없이 실시간 통신 가능
peer-to-peer, UDP 연결을 지원
클라이언트-클라이언트 간의 상호작용을 위해 설계
서버가 클라이언트처럼 동작하도록 구현 가능
WebRTC가 작동하려면 신호 서버가 필요하며 결국 웹 소켓, SSE 등으로 운영
따라서 WebRTC를 이러한 기술들의 대체로 사용하는 것은 목적에 맞지 않음


Clone this wiki locally