TIL2024. 6. 26.
웹소켓, SSE 개념 및 적용
#개념
개요
사용자 요청에 대해 BE에서 바로 처리가 어려워 해당 작업이 끝나면 알려주기로 했음
그리하여 실시간 통신을 위한 방식들을 정리. 이들 중 나의 필요와 그나마 맞아 보이는 웹소켓 & SSE 비교를 겸함.
실시간 통신을 위한 방식 후보들
폴링(client pull)
- 실시간으로 하려면 요청-응답 반복 ⇒ 많은 HTTP 오버헤드로 부적합 등의 이유로 탈락 !
- 일정하게 갱신되는 서버 데이터의 경우에 일정 간격으로 요청하는 식으로 쓰면 굿
긴 폴링 (client pull)(Hanging GET / COMET)
- 서버가 바로 응답하지 않고, 새 데이터가 생길 때까지 요청을 보류했다가 응답.
- 여러 클라이언트가 동시에 서버와 연결 유지 ⇒ 서버 부하 큼 등의 이유로 탈락 !
웹소켓 (server push) (web socket)
- HTTP 요청으로 최초 연결 후 프로토콜을 웹 소켓으로 바꿔 양방향 통신 상태 유지
- 실시간 채팅, 주식 시세 스트리밍 등 고빈도 실시간 데이터에 적합
SSE (server push) (Server-Sent Events)
- 서버 → 클라이언트 단방향 통신. client 측에서의 구독(연결) 필요
- 전통적 HTTP. 특별한 프로토콜이나 서버 구현 불필요(웹소켓과 달리!) + 구현 단순
- 알림, 실시간 피드 등에 적합
웹소켓 vs SSE 비교 정리
| 웹소켓 | SSE | |
|---|---|---|
| 통신방향 | 양방향 | 단방향(서버 ⇒ 클라이언트) |
| 자동재접속 | 안함 | 함, 3초마다 재시도 |
| 최대 동시 접속수 | 브라우저 연결 한도 없으나 서버 셋업따라 다름 | HTTP(6) HTTP2(100) |
| 프로토콜 | 웹소켓 | HTTP |
| 배터리 소모량 | 큼(계속 서버랑 handshake) | 작음(열어두고 기다림) |
| Firewall | not 친화적 | 친화적 |
| 사용처 | 주로 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로 연결 시 네트워크 탭에서 아래와 같이 보임
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 UI | FE | BE | |
|---|---|---|---|
| 사용자 작업 대상(파일) 업로드 | 1. 작업 대상 POST | → | 2. 일단 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를-이용한-실시간-알림