TIL2024. 6. 26.

웹소켓, SSE 개념 및 적용

#개념

개요

사용자 요청에 대해 BE에서 바로 처리가 어려워 해당 작업이 끝나면 알려주기로 했음

그리하여 실시간 통신을 위한 방식들을 정리. 이들 중 나의 필요와 그나마 맞아 보이는 웹소켓 & SSE 비교를 겸함.

실시간 통신을 위한 방식 후보들

폴링(client pull)

  • 실시간으로 하려면 요청-응답 반복 ⇒ 많은 HTTP 오버헤드로 부적합 등의 이유로 탈락 !
  • 일정하게 갱신되는 서버 데이터의 경우에 일정 간격으로 요청하는 식으로 쓰면 굿

긴 폴링 (client pull)(Hanging GET / COMET)

  • 서버가 바로 응답하지 않고, 새 데이터가 생길 때까지 요청을 보류했다가 응답.
  • 여러 클라이언트가 동시에 서버와 연결 유지 ⇒ 서버 부하 큼 등의 이유로 탈락 !

웹소켓 (server push) (web socket)

  • HTTP 요청으로 최초 연결 후 프로토콜을 웹 소켓으로 바꿔 양방향 통신 상태 유지
  • 실시간 채팅, 주식 시세 스트리밍 등 고빈도 실시간 데이터에 적합

Untitled.png

SSE (server push) (Server-Sent Events)

  • 서버 → 클라이언트 단방향 통신. client 측에서의 구독(연결) 필요
  • 전통적 HTTP. 특별한 프로토콜이나 서버 구현 불필요(웹소켓과 달리!) + 구현 단순
  • 알림, 실시간 피드 등에 적합

Untitled.png

웹소켓 vs SSE 비교 정리

웹소켓SSE
통신방향양방향단방향(서버 ⇒ 클라이언트)
자동재접속안함함, 3초마다 재시도
최대 동시 접속수브라우저 연결 한도 없으나 서버 셋업따라 다름HTTP(6) HTTP2(100)
프로토콜웹소켓HTTP
배터리 소모량큼(계속 서버랑 handshake)작음(열어두고 기다림)
Firewallnot 친화적친화적
사용처주로 real time 이 필요한 곳
예시 ) 카카오톡 채팅(SSE와 HTTP로도 만들수는 있음), 주식 트레이팅 데이터, 실시간 차트알림을 많이 줄 때. client쪽에서 데이터를 받기만 하면 되고, 완전히 실시간일 필요는 없는 곳
예시) 트위터 피드, 페북 친추 요청

그래서 최종 합격자는

SSE 씨,

당신은 구현도 쉽고 <작업 완료 알림 주기>라는 제 니즈에 딱 맞는 방식이네요.

SSE 클라이언트 코드

useEffect(() => { // 너와 나의 연결고리 만들기 ("구독 할게요.") const sseEvents = new EventSource('http://localhost:3000/sse') sseEvents.onopen = function() { // 연결 됐을 때 } sseEvents.onerror = function (error) { // 에러 났을 때 } sseEvents.onmessage = function (stream) { // 메세지 왔을 때 const parsedData = JSON.parse(stream.data) } }, [])

이렇게 클라이언트에서 EventSource로 연결 시 네트워크 탭에서 아래와 같이 보임

Untitled.png

SSE 서버 코드

/* client측에서 eventSource 만들어서 구독 시작(GET request)시, 이에 대한 response로 아래와 같은 헤더를 먼저 보내야함 */ const headers = { 'Content-Type': 'text/event-stream', // “이건 이벤트 스트림이야~" 'Connection': 'keep-alive', // 연결을 끊지 않고 유지 'Cache-Control': 'no-cache', // 캐싱 방지, 항상 최신 이벤트 }
res.writeHead(200, headers) const clientId = request.query.clientId clients[clientId] = { response, } request.on('close', () => { delete clients[clientId] }) // 알릴 이벤트 발생 (나의 경우: 서버에서 작업 완료) 알림 const notifyUser = (req, res) => { const payload = {...} clients.forEach(client => client.response.write(`data: ${JSON.stringify(payload)}\n\n`) }

눈여겨봐야 할 점

  • 데이터를 보낼 때 맨 끝에 \n\n을 써줘야 하는 것 => ”\n\n”이 이벤트 스트림에 이라고 알려주는 방법
  • 버퍼 없애기(버퍼가 있으면 클라이언트에게 바로 안 보내고 잠시 기다림)
    • HTTP header - X-Accel-Buffering: no
    • ngnix config - proxy_buffering off
    • 노드로는 10000 커넥션까지 가능

이벤트 스트림 format

  • data 값으로 메시지가오는데, 스트림 뒤에 \n\n은 끝.
  • \n을 구분하려는 메시지마다 추가해 분할해서 받을수있는데,
data: hello\n data: world\n data: final messsage\n\n

⇒ client 측에서는 split(’\n’)을 통해 여러 메시지를 구분함

이를 통해 JSON 을 바로 보내는 법

data: {\n data: "msg": "hello world",\n data: "id": 12345\n data: }\n\n
// 받은 메시지를 JSON 으로 만드는 Client 측 코드 source.addEventListener('message', function(e) { var data = JSON.parse(e.data); console.log(data.id, data.msg); }, false);

각각의 이벤트를 구분(unique하게)하려면

  • “id :” 를 첫줄에 포함시키면 됨
id: 101 // 이벤트 고유 번호가 됨 event: chat // 이벤트 이름 (없으면 message 이벤트로 처리됨) data: { "user": "dolchimdae", "msg": "안녕" }\n\n // 실제 데이터(JSON, 텍스트 등)
  • 클라이언트(브라우저)는 마지막으로 받은 id를 내부적으로 저장
  • 연결이 끊기면, 재연결 시 자동으로 Last-Event-ID 헤더를 서버에 보냄
  • 서버는 Last-Event-ID 이후의 이벤트부터 전송 가능(이벤트 로그 있어야겠지)

이벤트 이름 설정 예시 (단일 이벤트 소스 내 여러 유형의 이벤트 생성하는 법)

data: {"msg": "First message"}\n\n // message 이벤트(얘는 이벤트명 없이 감) event: userlogin\n data: {"username": "dolchimdae"}\n\n // userlogin 이벤트 event: update\n // data: {"username": "dolchimdae", "emotion": "sleepy"}\n\n // update 이벤트
// Client 측의 이벤트 리스너 source.addEventListener('message', function(e) { var data = JSON.parse(e.data); console.log(data.msg); }, false); source.addEventListener('userlogin', function(e) { var data = JSON.parse(e.data); console.log('User login:' + data.username); }, false); source.addEventListener('update', function(e) { var data = JSON.parse(e.data); console.log(data.username + ' is now ' + data.emotion); }, false);

나의 경우 실제 구현 설계

FE UIFEBE
사용자 작업 대상(파일) 업로드1. 작업 대상 POST2. 일단 DB 에 INSERT. status : processing
get 결과, 좀전 업로드한 새 작업 대상이 “processing”임을 확인 가능3. 우선 잘 받았다고 response
4. 서버 내 작업 대상 process 완료.
DB status UPDATE
get 결과, 작업 완료됨 확인5. notify client “작업 완료되었으니 확인해~”

참고 출처

https://surviveasdev.tistory.com/entry/웹소켓-과-SSEServer-Sent-Event-차이점-알아보고-사용해보기

https://hamait.tistory.com/792

https://velog.io/@max9106/Spring-SSE-Server-Sent-Events를-이용한-실시간-알림

요즘 작업송