ํฐ์คํ ๋ฆฌ ๋ทฐ
์น ์ค์๊ฐ ํต์ ๊ธฐ์ ์ ํ ๊ฐ์ด๋: Polling, Long Polling, WebSocket, SSE ์ฌ์ธต ๋น๊ต
Code Brewer 2026. 1. 27. 22:37
๐ ํ๋กค๋ก๊ทธ: ํ๋ ์น ์๋น์ค, ์ค์๊ฐ ํต์ ์ ํ์์ ๋๋ค
์ค๋๋ ์น ์๋น์ค๋ ๋จ์ํ ์ ๋ณด๋ฅผ ๋์ดํ๋ ๊ฒ์ ๋์ด, ์ฌ์ฉ์์๊ฒ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ๊ณผ ๋๊น ์๋ ๊ฒฝํ์ ์ ๊ณตํ๋ฉฐ ์งํํ๊ณ ์์ต๋๋ค. ์ฑํ ์ฑ์์ ๋ฉ์์ง๊ฐ ์ค์๊ฐ์ผ๋ก ์ ๋ฌ๋๊ณ , ์ฃผ์ ์์ธ๊ฐ ์ด ๋จ์๋ก ์ ๋ฐ์ดํธ๋๋ฉฐ, ๋ผ์ด๋ธ ์คํฌ์ธ ์ค๊ณ์ ๋์ ์ํฉ์ด ์ง์ฐ ์์ด ๋ฐ์๋๋ ๊ฒ์ ์ด์ ๋น์ฐํ ๊ธฐ๋์น์ ๋๋ค. ์ด๋ฌํ ์ค์๊ฐ ๋ฐ์ดํฐ์ ํ๋ฆ ์์ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ง์ด ํ์ ํ ๋จ์ด์ง ์๋ฐ์ ์์ต๋๋ค.
ํ์ง๋ง ์น์ ๊ทผ๊ฐ์ ์ด๋ฃจ๋ HTTP(Hypertext Transfer Protocol)๋ ๋ณธ๋ '์์ฒญ-์๋ต(Request-Response)' ๋ฐฉ์์ ํต์ ํ๋กํ ์ฝ์ ๋๋ค. ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )๊ฐ ์๋ฒ์ ํน์ ๋ฐ์ดํฐ๋ฅผ '์์ฒญ'ํ๋ฉด, ์๋ฒ๋ ๊ทธ์ ๋ํ '์๋ต'์ ๋ณด๋ด๊ณ ์ฐ๊ฒฐ์ ๋๋ ๋ฐฉ์์ด์ฃ . ์ด๋ ๋ง์น ์์ ์์ ์ํ๋ ์ฑ ์ด ์๋์ง ๋ฌผ์ด๋ณด๊ณ , ์ง์์ด ์ฑ ์ ์ฐพ์์ฃผ๋ฉด ๊ณ์ฐํ๊ณ ๋์ค๋ ๊ณผ์ ๊ณผ ๋น์ทํฉ๋๋ค. ๋งค์ฐ ํจ์จ์ ์ด์ง๋ง, ์๋ฒ์์ ์๋ก์ด ์ ๋ณด๊ฐ ๋ฐ์ํ์ ๋ ์ด๋ฅผ ํด๋ผ์ด์ธํธ์ ๋จผ์ ์๋ ค์ค ์ ์๋ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ์์ด์ผ๋ง ์๋ตํ ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
์ด๋ฌํ HTTP์ ํ๊ณ๋ ์ค์๊ฐ์ฑ์ด ์ค์ํ ์๋น์ค์์ ํฐ ๋ฌธ์ ๋ก ๋ค๊ฐ์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์น๊ตฌ๊ฐ ๋์๊ฒ ๋ฉ์์ง๋ฅผ ๋ณด๋๋ค๊ณ ๊ฐ์ ํด ๋ด ์๋ค. ์ ํต์ ์ธ HTTP ๋ฐฉ์์ผ๋ก๋ ๋ด๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ฒ์ "์ ๋ฉ์์ง ์์ด์?"๋ผ๊ณ ๋ฌผ์ด๋ด์ผ๋ง ๋ฉ์์ง ์์ ์ฌ๋ถ๋ฅผ ์ ์ ์์ต๋๋ค. ๋๋ฌด ์์ฃผ ๋ฌผ์ด๋ณด๋ฉด ์๋ฒ์ ๋ถ๋ด์ด ๋๊ณ , ๋๋ฌด ๋ฆ๊ฒ ๋ฌผ์ด๋ณด๋ฉด ๋ฉ์์ง๋ฅผ ๋ฆ๊ฒ ํ์ธํ๊ฒ ๋๋ ๋๋ ๋ง์ ๋น ์ง๋๋ค.
์ฌ๊ธฐ์ ๋ฐ๋ก '์น ์ค์๊ฐ ํต์ ' ๊ธฐ์ ๋ค์ด ๋ฑ์ฅํฉ๋๋ค. ์ด ๊ธฐ์ ๋ค์ ์ ํต์ ์ธ HTTP์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ณ , ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฑฐ์ ์ง์ฐ ์์ด ์ฃผ๊ณ ๋ฐ์ ์ ์๋๋ก ๋ค์ํ ์๋ฃจ์ ์ ์ ๊ณตํฉ๋๋ค. ์ฐ๋ฆฌ๋ ์ด ๊ธ์ ํตํด ์น ์ค์๊ฐ ํต์ ์ ์ํ ๋ํ์ ์ธ ๋ค ๊ฐ์ง ๋ฐฉ๋ฒ, ์ฆ Polling, Long Polling, WebSocket, SSE๋ฅผ ๊น์ด ์๊ฒ ๋ค์ฌ๋ค๋ณด๊ณ , ๊ฐ ๋ฐฉ์์ ์๋ ์๋ฆฌ, ์ฅ๋จ์ , ๊ทธ๋ฆฌ๊ณ ์ด๋ค ์ํฉ์์ ๊ฐ์ฅ ์ ํฉํ์ง ์์ธํ ๋น๊ต ๋ถ์ํ ๊ฒ์ ๋๋ค. ์น ๊ฐ๋ฐ์๋ก์ ์ค์๊ฐ ์๋น์ค ๊ตฌํ์ ๋ํ ๊ณ ๋ฏผ์ ํด๊ฒฐํ๊ณ ์ต์ ์ ๊ธฐ์ ์ ์ ํํ๊ณ ์ถ์ ๋ถ๋ค์ด๋ผ๋ฉด, ์ด ๊ฐ์ด๋๊ฐ ๋ถ๋ช ํฐ ๋์์ด ๋ ๊ฒ์ ๋๋ค. ์ง๊ธ๋ถํฐ ์น์ ๊ฒฝ๊ณ๋ฅผ ํ๋ฌด๋ ์ค์๊ฐ ํต์ ์ ์ธ๊ณ๋ก ํจ๊ป ๋ ๋๋ด ์๋ค.
๐ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐ๋ณต ์์ฒญ: Polling (ํด๋ง)
Polling์ ๊ฐ๋ ๊ณผ ์๋ ๋ฐฉ์
์น ์ค์๊ฐ ํต์ ์ ์ธ๊ณ๋ก ๋ค์ด์๊ธฐ ์ ์, ๊ฐ์ฅ ๊ธฐ์ด์ ์ด์ง๋ง ์ฌ์ ํ ๋๋ฆฌ ์ฌ์ฉ๋๋ 'ํด๋ง(Polling)' ๋ฐฉ์๋ถํฐ ์ดํด๋ณด๊ฒ ์ต๋๋ค. ํด๋ง์ '์ฃผ๊ธฐ์ ์ผ๋ก ๋ฐ๋ณตํ์ฌ ์์ฒญํ๋ ๋ฐฉ์'์ ์๋ฏธํฉ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ํน์ ์๊ฐ ๊ฐ๊ฒฉ(์: 1์ด, 5์ด)์ ๋๊ณ ์๋ฒ์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๋์ง ์ง์์ ์ผ๋ก ๋ฌผ์ด๋ณด๋ ํํ์ ๋๋ค. ๋ง์น ์ด๋ฆฐ์์ด๊ฐ ์ฅ๊ฑฐ๋ฆฌ ์ฌํ ์ค ๋ถ๋ชจ๋์๊ฒ "์์ง ๋ฉ์์ด์? ๋ค ์์ด์?"๋ผ๊ณ ๊ณ์ ๋ฌผ์ด๋ณด๋ ๊ฒ๊ณผ ์ ์ฌํ์ฃ .
๊ธฐ์ ์ ์ผ๋ก๋ ํด๋ผ์ด์ธํธ(์น ๋ธ๋ผ์ฐ์ )๊ฐ setInterval๊ณผ ๊ฐ์ JavaScript ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฏธ๋ฆฌ ์ ํด์ง ์๊ฐ๋ง๋ค ์๋ฒ์ ํน์ API ์๋ํฌ์ธํธ๋ก HTTP ์์ฒญ(์ฃผ๋ก GET ์์ฒญ)์ ๋ณด๋
๋๋ค. ์๋ฒ๋ ์ด ์์ฒญ์ ๋ฐ์ผ๋ฉด, ํ์ฌ ์์ ์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๋์ง ํ์ธํ๊ณ , ์๋ค๋ฉด ํด๋น ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋๋ ค์ค๋๋ค. ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๋๋ผ๋ ์๋ฒ๋ "์๋ก์ด ๋ฐ์ดํฐ ์์"์ด๋ผ๋ ์๋ต์ ๋ณด๋ผ ์ ์์ต๋๋ค. ์ด ๊ณผ์ ์ ํด๋ผ์ด์ธํธ๊ฐ ํ์ด์ง๋ฅผ ๋ซ๊ฑฐ๋ ๋ช
์์ ์ผ๋ก ์ค์งํ ๋๊น์ง ๋ฌดํํ ๋ฐ๋ณต๋ฉ๋๋ค.
์๋ ๋ฐฉ์ ์์ฝ:
- ํด๋ผ์ด์ธํธ ์์ฒญ: ์ผ์ ์๊ฐ ๊ฐ๊ฒฉ์ผ๋ก ์๋ฒ์ HTTP ์์ฒญ์ ๋ณด๋ ๋๋ค.
- ์๋ฒ ์๋ต: ์์ฒญ์ ๋ฐ์ ์๋ฒ๋ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ํ์ธํ๊ณ , ์๋ค๋ฉด ๋ฐ์ดํฐ๋ฅผ ํฌํจํ์ฌ ์๋ตํฉ๋๋ค. ๋ฐ์ดํฐ๊ฐ ์๋๋ผ๋ ๋น ์๋ต์ ๋ณด๋ผ ์ ์์ต๋๋ค.
- ๋ฐ๋ณต: ํด๋ผ์ด์ธํธ๋ ์๋ต์ ๋ฐ์ผ๋ฉด ๋ค์ ์์ฒญ๊น์ง ๋๊ธฐํ๊ณ , ์ ํด์ง ์๊ฐ์ด ์ง๋๋ฉด ๋ค์ ์์ฒญ์ ๋ณด๋ ๋๋ค.
Polling์ ์ฅ์ ๊ณผ ๋จ์
์ฅ์ :
- ๊ตฌํ์ด ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค: ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ HTTP ์์ฒญ-์๋ต ๋ฐฉ์์ ๋ฐ๋ณต์ด๊ธฐ ๋๋ฌธ์, ํน๋ณํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์๋ฒ ์ค์ ์์ด๋ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค. ๋๋ถ๋ถ์ ์น ํ๋ ์์ํฌ์ ์ธ์ด์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด ๋ฉ๋๋ค.
- ๋ชจ๋ ๋ธ๋ผ์ฐ์ ์ ํ๊ฒฝ์์ ํธํ๋ฉ๋๋ค: HTTP๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ๋ฏ๋ก, ๊ตฌํ ๋ธ๋ผ์ฐ์ด๋ฅผ ํฌํจํ ๋ชจ๋ ์น ํ๊ฒฝ์์ ๋ฌธ์ ์์ด ์๋ํฉ๋๋ค. ๋คํธ์ํฌ ํ๋ก์๋ ๋ฐฉํ๋ฒฝ ๋ฌธ์ ์์๋ ๋น๊ต์ ์์ ๋กญ์ต๋๋ค.
- ์ํ ๋น์ ์ฅ(Stateless) ์ํคํ ์ฒ์ ์ ๋ง์ต๋๋ค: ์๋ฒ๋ ๊ฐ ์์ฒญ์ ๋ ๋ฆฝ์ ์ผ๋ก ์ฒ๋ฆฌํ๋ฏ๋ก, ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์งํ ํ์๊ฐ ์์ต๋๋ค. ์ด๋ ์๋ฒ ํ์ฅ์ ์ ๋ฆฌํฉ๋๋ค.
๋จ์ :
- ์ค์๊ฐ์ฑ ๋ถ์กฑ: ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญํ๋ ์ฃผ๊ธฐ์ ๋ฐ๋ผ ์ค์๊ฐ ๋ฐ์ดํฐ ์ ๋ฌ์ ์ง์ฐ์ด ๋ฐ์ํฉ๋๋ค. ์ฃผ๊ธฐ๋ฅผ ์งง๊ฒ ํ๋ฉด ์ค์๊ฐ์ฑ์ด ํฅ์๋์ง๋ง, ์ด๋ ๋ค์ ๋จ์ ์ผ๋ก ์ด์ด์ง๋๋ค.
- ๋ถํ์ํ ๋คํธ์ํฌ ํธ๋ํฝ ๋ฐ ์๋ฒ ๋ถํ: ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ด๋ ํด๋ผ์ด์ธํธ๋ ๊ณ์ํด์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ ๋๋ค. ์ด๋ ๋คํธ์ํฌ ์์(๋์ญํญ)์ ๋ญ๋นํ๊ณ , ์๋ฒ์ ๋ถํ์ํ ๋ถํ๋ฅผ ์ฃผ๊ฒ ๋ฉ๋๋ค. ํนํ ๋ง์ ํด๋ผ์ด์ธํธ๊ฐ ๋์์ ํด๋ง์ ์ํํ ๊ฒฝ์ฐ ์๋ฒ๋ ๊ณผ๋ํ ์์ฒญ ์ฒ๋ฆฌ๋ก ์ธํด ์ฑ๋ฅ ์ ํ๋ฅผ ๊ฒช์ ์ ์์ต๋๋ค.
- ๊ธด ์ง์ฐ ์๊ฐ: ํด๋ง ์ฃผ๊ธฐ๊ฐ ๊ธธ๋ฉด ๊ธธ์๋ก ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ์งํ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ์ด ๊ธธ์ด์ง๋๋ค. ์๋ฅผ ๋ค์ด, 5์ด ์ฃผ๊ธฐ๋ก ํด๋งํ๋ค๋ฉด, ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋๋ผ๋ ์ต๋ 5์ด ํ์์ผ ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ฅผ ์ธ์งํ ์ ์์ต๋๋ค.
- ๋ฐฐํฐ๋ฆฌ ์๋ชจ: ๋ชจ๋ฐ์ผ ํ๊ฒฝ์์๋ ๋น๋ฒํ ๋คํธ์ํฌ ์์ฒญ์ด ๋ฐฐํฐ๋ฆฌ ์๋ชจ๋ฅผ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.
Polling ์ฝ๋ ์์
๋ค์์ Polling ๋ฐฉ์์ ๊ฐ๋จํ ์์์ ๋๋ค. 1์ด๋ง๋ค ์๋ฒ์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋์ง ์์ฒญํ๊ณ , ์๋ค๋ฉด ์ฝ์์ ์ถ๋ ฅํ๋ ํด๋ผ์ด์ธํธ ์ฝ๋๋ฅผ Node.js ์๋ฒ์ ํจ๊ป ์ดํด๋ณด๊ฒ ์ต๋๋ค.
1. ์๋ฒ ์ฝ๋ (Node.js + Express)server.js
const express = require('express');
const app = express();
const port = 3000;
let messageCounter = 0;
let messages = []; // ์๋ก์ด ๋ฉ์์ง๋ฅผ ์ ์ฅํ ๋ฐฐ์ด
// ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ก์ด ๋ฉ์์ง ์์ฑ (์์)
setInterval(() => {
messageCounter++;
if (messageCounter % 5 === 0) { // 5์ด๋ง๋ค ์๋ก์ด ๋ฉ์์ง ์ถ๊ฐ
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${messageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค!`;
messages.push(newMessage);
console.log(`์๋ก์ด ๋ฉ์์ง ์์ฑ: ${newMessage}`);
}
}, 1000);
// ํด๋ผ์ด์ธํธ๊ฐ ๋ฉ์์ง๋ฅผ ์์ฒญํ๋ ์๋ํฌ์ธํธ
app.get('/messages', (req, res) => {
// ํด๋ผ์ด์ธํธ๊ฐ ๋ง์ง๋ง์ผ๋ก ๋ฐ์ ๋ฉ์์ง ์ธ๋ฑ์ค๋ฅผ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ๋ณด๋ผ ์ ์๋๋ก ๊ฐ์
// ์ฌ๊ธฐ์๋ ๊ฐ๋จํ๊ฒ ๋ชจ๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ง๋ง, ์ค์ ๋ก๋ ์ฐจ๋ฑ ์ ์ก ๋ก์ง ํ์
// ์ค์ ์๋น์ค์์๋ 'req.query.lastIndex' ๋ฑ์ ํ์ฉํ์ฌ
// ํด๋ผ์ด์ธํธ๊ฐ ์ด๋ฏธ ๋ฐ์ ๋ฉ์์ง๋ฅผ ์ ์ธํ๊ณ ์๋ก์ด ๋ฉ์์ง๋ง ๋ณด๋
๋๋ค.
// ์ฌ๊ธฐ์๋ ๊ฐ๋จํ๊ฒ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋ค๋ฉด ๋ชจ๋ ๋ณด๋
๋๋ค.
if (messages.length > 0) {
res.json({ newMessages: messages });
messages = []; // ๋ฉ์์ง๋ฅผ ๋ณด๋์ผ๋ ์ด๊ธฐํ (ํ๋ฒ ๋ณด๋ธ ๋ฉ์์ง๋ ๋ค์ ๋ณด๋ด์ง ์์)
} else {
res.json({ newMessages: [] }); // ์๋ก์ด ๋ฉ์์ง๊ฐ ์์์ ์๋ฆผ
}
});
// ํด๋ผ์ด์ธํธ HTML ํ์ผ์ ์๋นํ๊ธฐ ์ํ ์ ์ ํ์ผ ๊ฒฝ๋ก ์ค์
app.use(express.static('public'));
app.listen(port, () => {
console.log(`Polling ์๋ฒ๊ฐ http://localhost:${port} ์์ ์คํ ์ค์
๋๋ค.`);
console.log(`๋ธ๋ผ์ฐ์ ์์ http://localhost:${port}/index.html ์ ์ ์ํ์ธ์.`);
});
2. ํด๋ผ์ด์ธํธ ์ฝ๋ (HTML + JavaScript)public/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Polling ์์ </title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
h1 { color: #0056b3; }
#message-container {
border: 1px solid #ccc;
padding: 15px;
min-height: 150px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow-y: auto;
max-height: 400px;
margin-top: 20px;
}
.message-item {
padding: 8px 0;
border-bottom: 1px dashed #eee;
}
.message-item:last-child {
border-bottom: none;
}
button {
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
margin-top: 10px;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<h1>Polling ์ค์๊ฐ ํต์ ์์ </h1>
<p>์ด ํ์ด์ง๋ <strong>1์ด๋ง๋ค</strong> ์๋ฒ์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋์ง ์์ฒญํฉ๋๋ค.</p>
<button id="togglePolling">Polling ์์/์ค์ง</button>
<div id="message-container">
<p>์๋ฒ ๋ฉ์์ง:</p>
</div>
<script>
const messageContainer = document.getElementById('message-container');
const toggleButton = document.getElementById('togglePolling');
let pollingIntervalId = null;
let isPollingActive = false;
// ๋ฉ์์ง๋ฅผ ํ๋ฉด์ ์ถ๊ฐํ๋ ํจ์
function addMessageToDisplay(msg) {
const p = document.createElement('p');
p.className = 'message-item';
p.textContent = msg;
messageContainer.appendChild(p);
messageContainer.scrollTop = messageContainer.scrollHeight; // ์คํฌ๋กค์ ๋งจ ์๋๋ก
}
async function pollMessages() {
try {
const response = await fetch('/messages');
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling):', msg);
addMessageToDisplay(`[${new Date().toLocaleTimeString()}] ${msg}`);
});
} else {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling)');
addMessageToDisplay(`[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์์ต๋๋ค.`);
}
} catch (error) {
console.error('Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay(`[${new Date().toLocaleTimeString()}] Polling ์ค๋ฅ: ${error.message}`);
}
}
function startPolling() {
if (!isPollingActive) {
isPollingActive = true;
pollingIntervalId = setInterval(pollMessages, 1000); // 1์ด(1000ms)๋ง๋ค ์์ฒญ
toggleButton.textContent = 'Polling ์ค์ง';
addMessageToDisplay(`[${new Date().toLocaleTimeString()}] Polling ์์.`);
}
}
function stopPolling() {
if (isPollingActive) {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
toggleButton.textContent = 'Polling ์์';
addMessageToDisplay(`[${new Date().toLocaleTimeString()}] Polling ์ค์ง.`);
}
}
toggleButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
});
// ํ์ด์ง ๋ก๋ ์ ์๋์ผ๋ก Polling ์์
window.addEventListener('load', startPolling);
</script>
</body>
</html>
์คํ ๋ฐฉ๋ฒ:
- Node.js์ Express๋ฅผ ์ค์นํฉ๋๋ค:
npm init -yํnpm install express. server.jsํ์ผ์ ์์ฑํ๊ณ ์ ์ฝ๋๋ฅผ ๋ถ์ฌ๋ฃ์ต๋๋ค.public๋๋ ํ ๋ฆฌ๋ฅผ ์์ฑํ๊ณ ๊ทธ ์์index.htmlํ์ผ์ ์์ฑํ์ฌ ์ ์ฝ๋๋ฅผ ๋ถ์ฌ๋ฃ์ต๋๋ค.- ํฐ๋ฏธ๋์์
node server.js๋ฅผ ์คํํฉ๋๋ค. - ์น ๋ธ๋ผ์ฐ์ ์์
http://localhost:3000/index.html๋ก ์ ์ํ๋ฉด 1์ด๋ง๋ค ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๊ณ , 5์ด๋ง๋ค ์์ฑ๋๋ ์๋ก์ด ๋ฉ์์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
์ด ์์๋ฅผ ํตํด ํด๋ง์ ๋จ์ํจ๊ณผ ํจ๊ป, ๋ฉ์์ง๊ฐ ์๋๋ฐ๋ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๋ ๋นํจ์จ์ฑ์ ์ฒด๊ฐํ ์ ์์ ๊ฒ์ ๋๋ค. ์ด๋ฌํ ๋นํจ์จ์ฑ์ ๊ฐ์ ํ๊ธฐ ์ํ ๋ฐฉ๋ฒ ์ค ํ๋๊ฐ ๋ฐ๋ก ๋กฑ ํด๋ง์ ๋๋ค.
โณ ์๋ต ๋๊ธฐ ํ ์ฌ์์ฒญ: Long Polling (๋กฑ ํด๋ง)
Long Polling์ ๊ฐ๋ ๊ณผ ์๋ ๋ฐฉ์
ํด๋ง ๋ฐฉ์์ ๊ฐ์ฅ ํฐ ๋จ์ ์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ด๋ ํด๋ผ์ด์ธํธ๊ฐ ๊ณ์ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ธ๋ค๋ ์ ์ ๋๋ค. ์ด๋ ๋คํธ์ํฌ ๋์ญํญ๊ณผ ์๋ฒ ์์์ ๋ญ๋นํ๊ณ , ๋ถํ์ํ ๋ถํ๋ฅผ ์ ๋ฐํฉ๋๋ค. ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ๊ฐ์ ํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด ๋ฐ๋ก '๋กฑ ํด๋ง(Long Polling)'์ ๋๋ค. ๋กฑ ํด๋ง์ ํด๋ง์ ๋ณํ๋ ํํ๋ก, "์๋ฒ์ ๋ฐ์ดํฐ๊ฐ ์๊ธธ ๋๊น์ง ์๋ต์ ๋๊ธฐํ๊ณ , ๋ฐ์ดํฐ๊ฐ ์๊ธฐ๋ฉด ์๋ต์ ๋ณด๋ธ ํ ํด๋ผ์ด์ธํธ๊ฐ ์ฆ์ ์ฌ์์ฒญํ๋ ๋ฐฉ์"์ ๋๋ค.
๋กฑ ํด๋ง์ ๋ง์น ๋ด๊ฐ ์ฐํธํจ์ ํธ์ง๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ด ์๋๋ผ, ์ฐ์ฒด๊ตญ ์ง์์๊ฒ "์ ํธ์ง๊ฐ ์ค๋ฉด ๋ฐ๋ก ์๋ ค์ฃผ์ธ์. ํธ์ง๊ฐ ๋์ฐฉํ๊ธฐ ์ ๊น์ง๋ ์ ์๊ฒ ๋ค๋ฅธ ํธ์ง๊ฐ ์๋๊ณ ๋ฌป์ง ๋ง์ธ์."๋ผ๊ณ ๋งํ๊ณ ์ง์์ ํธ์ง๊ฐ ์ฌ ๋๊น์ง ๋์๊ฒ ์๋ฌด๊ฒ๋ ์๋ ค์ฃผ์ง ์๋ค๊ฐ, ํธ์ง๊ฐ ์ค๋ฉด ๊ทธ์ ์์ผ ๋์๊ฒ ํธ์ง๋ฅผ ์ ๋ฌํด์ฃผ๋ ๊ฒ๊ณผ ๋น์ทํฉ๋๋ค. ๋ด๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์ฐํธํจ์ ํ์ธํ๋ ๊ฒ์ด ์๋๋ผ, ํธ์ง๊ฐ ์ฌ ๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๊ฒ์ด์ฃ .
์๋ ๋ฐฉ์ ์์ฝ:
- ํด๋ผ์ด์ธํธ ์์ฒญ: ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ HTTP ์์ฒญ์ ๋ณด๋ ๋๋ค.
- ์๋ฒ ์๋ต ๋๊ธฐ: ์๋ฒ๋ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ ๋๊น์ง ์ด ์์ฒญ์ ๋ํ ์๋ต์ ๋ณด๋ด์ง ์๊ณ ๋๊ธฐํฉ๋๋ค. ์ด๋, ์๋ฒ๋ ๋ณดํต ์๋ต ๋๊ธฐ ์๊ฐ(Timeout)์ ์ค์ ํ์ฌ ๋ฌดํ์ ๊ธฐ๋ค๋ฆฌ์ง ์๋๋ก ํฉ๋๋ค.
- ๋ฐ์ดํฐ ๋ฐ์ ๋๋ ํ์์์:
- ๋ฐ์ดํฐ ๋ฐ์ ์: ์๋ฒ์์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋ฉด, ๋๊ธฐ ์ค์ด๋ ์์ฒญ์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ํฌํจํ์ฌ ์ฆ์ ์๋ตํฉ๋๋ค.
- ํ์์์ ์: ์ค์ ๋ ๋๊ธฐ ์๊ฐ์ ์ด๊ณผํด๋ ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด, ์๋ฒ๋ ๋น ์๋ต(๋๋ "๋ฐ์ดํฐ ์์" ์๋ต)์ ๋ณด๋ ๋๋ค.
- ํด๋ผ์ด์ธํธ ์ฌ์์ฒญ: ํด๋ผ์ด์ธํธ๋ ์๋ฒ๋ก๋ถํฐ ์๋ต์ ๋ฐ์๋ง์ (๋ฐ์ดํฐ ์ ๋ฌด์ ๊ด๊ณ์์ด) ์๋ก์ด ์์ฒญ์ ๋ค์ ๋ณด๋ ๋๋ค. ์ด๋ ๋ค์ ์๋ก์ด ๋ฐ์ดํฐ ๋๋ ํ์์์์ ๊ธฐ๋ค๋ฆฌ๊ธฐ ์ํจ์ ๋๋ค.
์ด๋ฌํ ๋ฐฉ์์ผ๋ก ๋กฑ ํด๋ง์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ผ๋ฉด ๋ถํ์ํ ์์ฒญ์ด ๋ฐ์ํ์ง ์๊ณ , ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋ฉด ์ฆ์ ํด๋ผ์ด์ธํธ์ ์ ๋ฌ๋๋ฏ๋ก ํด๋ง๋ณด๋ค ์ค์๊ฐ์ฑ์ด ํจ์ฌ ํฅ์๋ฉ๋๋ค.
Polling ๋๋น ๊ฐ์ ์ ๋ฐ ํ๊ณ
๊ฐ์ ์ (Polling ๋๋น ์ฅ์ ):
- ํฅ์๋ ์ค์๊ฐ์ฑ: ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋ ์ฆ์ ํด๋ผ์ด์ธํธ์ ์ ๋ฌ๋๋ฏ๋ก, ํด๋ง์ ๊ณ ์ ๋ ์ฃผ๊ธฐ๋ณด๋ค ํจ์ฌ ๋น ๋ฅด๊ฒ ์ ๋ฐ์ดํธ๋ฅผ ๊ฐ์งํ ์ ์์ต๋๋ค.
- ๋คํธ์ํฌ ํธ๋ํฝ ๋ฐ ์๋ฒ ๋ถํ ๊ฐ์: ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ ๋๋ ์์ฒญ์ด ๊ณ์ ์ ์ง๋๋ฏ๋ก, ํด๋ง์ฒ๋ผ ๋น๋ฒํ๊ฒ ์์ฒญ-์๋ต์ ๋ฐ๋ณตํ ํ์๊ฐ ์์ต๋๋ค. ์ด๋ ๋ถํ์ํ HTTP ํค๋ ๊ตํ์ ์ค์ฌ ๋คํธ์ํฌ ํธ๋ํฝ์ ์ ๊ฐํ๊ณ ์๋ฒ์ ์์ฒญ ์ฒ๋ฆฌ ๋ถํ๋ฅผ ์ค์ฌ์ค๋๋ค.
- ํจ์จ์ ์ธ ์์ ์ฌ์ฉ: ๋ฐ์ดํฐ๊ฐ ์์ ๋๋ง ์๋ต์ ๋ณด๋ด๋ฏ๋ก, ์๋ฒ์ ํด๋ผ์ด์ธํธ ๋ชจ๋ ๋ฐ์ดํฐ ์ ๋ฌ์ด ํ์ํ ์์ ์๋ง ํ์ฑํ๋ฉ๋๋ค.
ํ๊ณ (๋จ์ ):
- ์๋ฒ ์ฐ๊ฒฐ ์ ์ง ๋ถํ: ์๋ฒ๋ ์ฌ๋ฌ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๋์์ ๋๊ธฐ ์ํ๋ก ์ ์งํด์ผ ํฉ๋๋ค. ์ฐ๊ฒฐ๋ง๋ค ๋ฆฌ์์ค๋ฅผ ์๋ชจํ๋ฏ๋ก, ๋์ ์ ์์ ์๊ฐ ๋ง์์ง์๋ก ์๋ฒ์ ๋ฉ๋ชจ๋ฆฌ ๋ฐ ํ๋ก์ธ์ค ๋ถํ๊ฐ ์ปค์ง ์ ์์ต๋๋ค. ํนํ ์์ฒญ ๋๊ธฐ ์ค์ธ ์ฐ๊ฒฐ ์๊ฐ ๋ง์์ง๋ฉด ์๋ฒ์ ํ์ฅ์ฑ์ ์ํฅ์ ๋ฏธ์นฉ๋๋ค.
- ํ์์์ ์ฒ๋ฆฌ ๋ณต์ก์ฑ: ์๋ฒ๋ ๋ฌดํ์ ๊ธฐ๋ค๋ฆด ์ ์์ผ๋ฏ๋ก ์ ์ ํ ํ์์์์ ์ค์ ํด์ผ ํฉ๋๋ค. ํ์์์์ด ๋ฐ์ํ๋ฉด ํด๋ผ์ด์ธํธ๋ ๋น ์๋ต์ ๋ฐ๊ณ ์ฆ์ ์ฌ์์ฒญ์ ๋ณด๋ด์ผ ํฉ๋๋ค. ์ด ๊ณผ์ ์์ ์ฌ์์ฒญ ๋ก์ง์ด ๋ณต์กํด์ง ์ ์์ผ๋ฉฐ, ์งง์ ํ์์์์ ๋ค์ ํด๋ง๊ณผ ์ ์ฌํ ํํ๋ก ๋์๊ฐ ์ ์์ต๋๋ค.
- HTTP ์์ฒญ ์ค๋ฒํค๋: ๋งค๋ฒ ์๋ก์ด HTTP ์์ฒญ์ ์์ํด์ผ ํ๋ฏ๋ก, HTTP ํค๋ ์ ์ก์ผ๋ก ์ธํ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค. ์ด๋ WebSocket๊ณผ ๊ฐ์ ๋จ์ผ ์ง์ ์ฐ๊ฒฐ ๋ฐฉ์์ ๋นํด ๋นํจ์จ์ ์ ๋๋ค.
- ํน์ ํ๊ฒฝ์์์ ๋ฌธ์ : ์ผ๋ถ ๋ก๋ ๋ฐธ๋ฐ์๋ ํ๋ก์ ์๋ฒ๋ ์ค๋ซ๋์ ์๋ต์ด ์๋ ์ฐ๊ฒฐ์ ๋น์ ์์ผ๋ก ๊ฐ์ฃผํ์ฌ ๊ฐ์ ๋ก ๋์ ์ ์์ต๋๋ค. ์ด๋ ๋กฑ ํด๋ง์ ์์ ์ฑ์ ์ ํดํ ์ ์์ต๋๋ค.
Long Polling ์ฝ๋ ์์
๋ค์์ ๋กฑ ํด๋ง ๋ฐฉ์์ ๊ฐ๋จํ ์์์ ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด๋ฉด, ์๋ฒ๋ 5์ด๋ง๋ค ์๋ก์ด ๋ฉ์์ง๋ฅผ ์์ฑํ๋ค๊ฐ ๋ฉ์์ง๊ฐ ์๊ธฐ๋ฉด ์ฆ์ ์๋ต์ ๋ณด๋ด๊ณ , ํด๋ผ์ด์ธํธ๋ ๊ทธ ์๋ต์ ๋ฐ์๋ง์ ๋ค์ ์์ฒญ์ ๋ณด๋ ๋๋ค.
1. ์๋ฒ ์ฝ๋ (Node.js + Express)server.js
const express = require('express');
const app = express();
const port = 3000;
let messageCounter = 0;
let pendingRequests = []; // ๋กฑ ํด๋ง ์์ฒญ๋ค์ ์ ์ฅํ ๋ฐฐ์ด
// ์ฃผ๊ธฐ์ ์ผ๋ก ์๋ก์ด ๋ฉ์์ง ์์ฑ (์์)
setInterval(() => {
messageCounter++;
if (messageCounter % 5 === 0) { // 5์ด๋ง๋ค ์๋ก์ด ๋ฉ์์ง ์ถ๊ฐ
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${messageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค! (Long Polling)`;
console.log(`์๋ก์ด ๋ฉ์์ง ์์ฑ: ${newMessage}`);
// ๋๊ธฐ ์ค์ธ ๋ชจ๋ ์์ฒญ์ ๋ฉ์์ง ์ ์ก
while (pendingRequests.length > 0) {
const res = pendingRequests.shift(); // ํ์์ ์์ฒญ ํ๋๋ฅผ ๊บผ๋
res.json({ newMessages: [newMessage] }); // ๋ฉ์์ง์ ํจ๊ป ์๋ต
}
}
}, 1000);
// ํด๋ผ์ด์ธํธ๊ฐ ๋ฉ์์ง๋ฅผ ์์ฒญํ๋ ๋กฑ ํด๋ง ์๋ํฌ์ธํธ
app.get('/long-polling-messages', (req, res) => {
// ๋กฑ ํด๋ง ํ์์์ ์ค์ (์: 20์ด)
req.socket.setTimeout(20 * 1000);
// ์๋ก์ด ๋ฉ์์ง๊ฐ ๋ฐ์ํ ๋๊น์ง ์๋ต์ ๋ณด๋ฅ
pendingRequests.push(res);
console.log(`์๋ก์ด ๋กฑ ํด๋ง ์์ฒญ ๋๊ธฐ ์ค. ํ์ฌ ๋๊ธฐ ์์ฒญ ์: ${pendingRequests.length}`);
// ํ์์์ ์ฒ๋ฆฌ
req.on('timeout', () => {
console.log('๋กฑ ํด๋ง ์์ฒญ ํ์์์!');
const index = pendingRequests.indexOf(res);
if (index > -1) {
pendingRequests.splice(index, 1); // ๋ฐฐ์ด์์ ํด๋น ์์ฒญ ์ ๊ฑฐ
}
res.status(200).json({ newMessages: [] }); // ๋น ์๋ต ์ ์ก
});
});
// ํด๋ผ์ด์ธํธ HTML ํ์ผ์ ์๋นํ๊ธฐ ์ํ ์ ์ ํ์ผ ๊ฒฝ๋ก ์ค์
app.use(express.static('public'));
app.listen(port, () => {
console.log(`Long Polling ์๋ฒ๊ฐ http://localhost:${port} ์์ ์คํ ์ค์
๋๋ค.`);
console.log(`๋ธ๋ผ์ฐ์ ์์ http://localhost:${port}/index.html ์ ์ ์ํ์ธ์.`);
});
2. ํด๋ผ์ด์ธํธ ์ฝ๋ (HTML + JavaScript)public/index.html (๊ธฐ์กด index.html ํ์ผ์ Long Polling ์น์
์ถ๊ฐ)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>์น ์ค์๊ฐ ํต์ ์์ </title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
h1, h2 { color: #0056b3; margin-top: 30px;}
.section-container {
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 30px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
}
#message-container-polling, #message-container-long-polling, #message-container-sse, #message-container-websocket {
border: 1px solid #eee;
padding: 15px;
min-height: 150px;
background-color: #f9f9f9;
overflow-y: auto;
max-height: 400px;
margin-top: 15px;
border-radius: 5px;
}
.message-item {
padding: 8px 0;
border-bottom: 1px dashed #eee;
}
.message-item:last-child {
border-bottom: none;
}
button {
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
margin-top: 10px;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<h1>์น ์ค์๊ฐ ํต์ ์์ </h1>
<p>๋ค์ํ ์น ์ค์๊ฐ ํต์ ๋ฐฉ๋ฒ๋ค์ ๋์์ ๋น๊ตํด๋ด
๋๋ค.</p>
<!-- Polling Section -->
<div class="section-container">
<h2>Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ <strong>1์ด๋ง๋ค</strong> ์๋ฒ์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋์ง ์์ฒญํฉ๋๋ค.</p>
<button id="togglePolling">Polling ์์/์ค์ง</button>
<div id="message-container-polling">
<p>์๋ฒ ๋ฉ์์ง (Polling):</p>
</div>
</div>
<!-- Long Polling Section -->
<div class="section-container">
<h2>Long Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ๋ฉ์์ง๊ฐ ๋์ฐฉํ ๋๊น์ง ๋๊ธฐํ๊ณ , ๋์ฐฉํ๋ฉด ์ฆ์ ์๋ต์ ๋ฐ์ต๋๋ค. ์๋ต์ ๋ฐ์ผ๋ฉด ๋ฐ๋ก ๋ค์ ์์ฒญ์ ๋ณด๋
๋๋ค.</p>
<button id="toggleLongPolling">Long Polling ์์/์ค์ง</button>
<div id="message-container-long-polling">
<p>์๋ฒ ๋ฉ์์ง (Long Polling):</p>
</div>
</div>
<!-- SSE Section placeholder -->
<div class="section-container">
<h2>SSE (Server-Sent Events) ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ๋ก๋ถํฐ ๋จ๋ฐฉํฅ ์คํธ๋ฆผ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์์ ํฉ๋๋ค.</p>
<button id="toggleSSE">SSE ์์/์ค์ง</button>
<div id="message-container-sse">
<p>์๋ฒ ๋ฉ์์ง (SSE):</p>
</div>
</div>
<!-- WebSocket Section placeholder -->
<div class="section-container">
<h2>WebSocket ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ์๋ฐฉํฅ ์ ์ด์ค ํต์ ์ ์ค์ ํ๊ณ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ต๋๋ค.</p>
<input type="text" id="websocketInput" placeholder="์๋ฒ๋ก ๋ณด๋ผ ๋ฉ์์ง" style="padding: 8px; width: 250px; margin-right: 10px;">
<button id="sendWebSocketMessage">๋ฉ์์ง ์ ์ก</button>
<button id="toggleWebSocket">WebSocket ์ฐ๊ฒฐ/ํด์ </button>
<div id="message-container-websocket">
<p>์๋ฒ ๋ฉ์์ง (WebSocket):</p>
</div>
</div>
<script>
// --- ๊ณตํต ๋ฉ์์ง ์ถ๋ ฅ ํจ์ ---
function addMessageToDisplay(containerId, msg, type = 'info') {
const container = document.getElementById(containerId);
if (!container) return;
const p = document.createElement('p');
p.className = `message-item ${type}`;
p.textContent = msg;
container.appendChild(p);
container.scrollTop = container.scrollHeight;
}
// --- Polling Logic ---
const pollingMessageContainer = document.getElementById('message-container-polling');
const togglePollingButton = document.getElementById('togglePolling');
let pollingIntervalId = null;
let isPollingActive = false;
async function pollMessages() {
try {
const response = await fetch('/messages');
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling):', msg);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
// console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling)');
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling: ์๋ก์ด ๋ฉ์์ง๊ฐ ์์ต๋๋ค.`, 'info');
}
} catch (error) {
console.error('Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค๋ฅ: ${error.message}`, 'error');
}
}
function startPolling() {
if (!isPollingActive) {
isPollingActive = true;
pollingIntervalId = setInterval(pollMessages, 1000); // 1์ด(1000ms)๋ง๋ค ์์ฒญ
togglePollingButton.textContent = 'Polling ์ค์ง';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์์.`, 'info');
}
}
function stopPolling() {
if (isPollingActive) {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
togglePollingButton.textContent = 'Polling ์์';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค์ง.`, 'info');
}
}
togglePollingButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
});
// --- Long Polling Logic ---
const longPollingMessageContainer = document.getElementById('message-container-long-polling');
const toggleLongPollingButton = document.getElementById('toggleLongPolling');
let isLongPollingActive = false;
let longPollingAbortController = null; // ์์ฒญ ์ทจ์๋ฅผ ์ํ AbortController
async function requestLongPolling() {
if (!isLongPollingActive) return; // ํ์ฑํ๋์ด ์์ง ์์ผ๋ฉด ๋ ์ด์ ์์ฒญํ์ง ์์
longPollingAbortController = new AbortController();
const signal = longPollingAbortController.signal;
try {
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ์๋ฒ์ ๋กฑ ํด๋ง ์์ฒญ ์ค...`, 'info');
const response = await fetch('/long-polling-messages', { signal });
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Long Polling):', msg);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
// console.log('๋กฑ ํด๋ง ํ์์์ ๋๋ ๋ฐ์ดํฐ ์์.');
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๊ฑฐ๋ ํ์์์.`, 'info');
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('๋กฑ ํด๋ง ์์ฒญ์ด ์ทจ์๋์์ต๋๋ค.');
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์์ฒญ์ด ์ค์ง๋์์ต๋๋ค.`, 'info');
} else {
console.error('Long Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค๋ฅ: ${error.message}`, 'error');
}
} finally {
// ์๋ต์ ๋ฐ๊ฑฐ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด ์ฆ์ ๋ค์ ์์ฒญ์ ๋ณด๋
if (isLongPollingActive) {
requestLongPolling();
}
}
}
function startLongPolling() {
if (!isLongPollingActive) {
isLongPollingActive = true;
toggleLongPollingButton.textContent = 'Long Polling ์ค์ง';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์์.`, 'info');
requestLongPolling(); // ์ฒซ ์์ฒญ ์์
}
}
function stopLongPolling() {
if (isLongPollingActive) {
isLongPollingActive = false;
if (longPollingAbortController) {
longPollingAbortController.abort(); // ํ์ฌ ๋๊ธฐ ์ค์ธ ์์ฒญ ์ทจ์
}
toggleLongPollingButton.textContent = 'Long Polling ์์';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค์ง.`, 'info');
}
}
toggleLongPollingButton.addEventListener('click', () => {
if (isLongPollingActive) {
stopLongPolling();
} else {
startLongPolling();
}
});
// ํ์ด์ง ๋ก๋ ์ ์๋์ผ๋ก Polling ๋ฐ Long Polling ์์ (SSE, WebSocket์ ์๋ ์์)
window.addEventListener('load', () => {
startPolling();
startLongPolling();
});
</script>
</body>
</html>
์คํ ๋ฐฉ๋ฒ:
- ๊ธฐ์กด
server.js์public/index.htmlํ์ผ์ ์ ์ฝ๋๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. - ํฐ๋ฏธ๋์์
node server.js๋ฅผ ๋ค์ ์คํํฉ๋๋ค. - ์น ๋ธ๋ผ์ฐ์ ์์
http://localhost:3000/index.html๋ก ์ ์ํ๋ฉด Polling๊ณผ Long Polling์ ๋์ ๋ฐฉ์์ ๋๋ํ ๋น๊ตํ๋ฉฐ ํ์ธํ ์ ์์ต๋๋ค. Long Polling์ ์๋ฒ์์ ๋ฉ์์ง๊ฐ ์์ฑ๋ ๋๊น์ง ์๋ต์ด ์ค์ง ์๋ค๊ฐ, ๋ฉ์์ง๊ฐ ์์ฑ๋๋ฉด ์ฆ์ ๋ฉ์์ง๋ฅผ ๋ฐ๊ณ ๊ณง๋ฐ๋ก ๋ค์ ์์ฒญ์ ๋ณด๋ด๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๋กฑ ํด๋ง์ ํด๋ง์ ๋นํจ์จ์ฑ์ ํฌ๊ฒ ๊ฐ์ ํ์ง๋ง, ์ฌ์ ํ HTTP ์์ฒญ-์๋ต ๋ชจ๋ธ์ ํ๊ณ์ ๊ณผ ์๋ฒ์ ๋ถํ ๋ฌธ์ ๋ฅผ ์๊ณ ์์ต๋๋ค. ์ด์ ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์ผ๋ฐฉ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ํธ์ํ๋ ๋ฐฉ์์ผ๋ก ์ค์๊ฐ ํต์ ์ ๊ตฌํํ๋ SSE์ ๋ํด ์์๋ณด๊ฒ ์ต๋๋ค.
๐ ๋จ๋ฐฉํฅ ์คํธ๋ฆผ: SSE (Server-Sent Events)
SSE์ ๊ฐ๋ ๊ณผ ์๋ ๋ฐฉ์
ํด๋ง๊ณผ ๋กฑ ํด๋ง์ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ ๊ธฐ๋ฐํ ๋ฐฉ์์ ๋๋ค. ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ '๋น๊ฒจ์ค๋(pull)' ํํ์ฃ . ํ์ง๋ง ์ง์ ํ '์ค์๊ฐ'์ด๋ ์๋ฒ์์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ์ ๋, ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ๋ฐ์ดํฐ๋ฅผ '๋ฐ์ด์ฃผ๋(push)' ๊ฒ์ด ๋ ํจ์จ์ ์ ๋๋ค. ์ด๋ฌํ ์๋ฒ ํธ์ ๋ฐฉ์์ ํ๋๊ฐ ๋ฐ๋ก 'SSE(Server-Sent Events)'์ ๋๋ค.
SSE๋ ์ด๋ฆ ๊ทธ๋๋ก '์๋ฒ์์ ๋ณด๋ด๋ ์ด๋ฒคํธ'์ ๋๋ค. ์ด๋ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ํ ๋ฒ ์ฐ๊ฒฐ์ ์ค์ ํ๋ฉด, ์๋ฒ๊ฐ ์ด ์ฐ๊ฒฐ์ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ๋จ๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ง์์ ์ผ๋ก ์คํธ๋ฆฌ๋ฐํ๋ ๋ฐฉ์์ ๋๋ค. ๋ง์น ๋ผ๋์ค ๋ฐฉ์ก๊ณผ ์ ์ฌํฉ๋๋ค. ๋ผ๋์ค ์์ ๊ธฐ๊ฐ ๋ฐฉ์ก๊ตญ์ ํ ๋ฒ ์ฑ๋์ ๋ง์ถ๋ฉด, ๋ฐฉ์ก๊ตญ์ ๊ณ์ํด์ ์๋ก์ด ์ฝํ ์ธ ๋ฅผ ๋ด๋ณด๋ด๊ณ ์์ ๊ธฐ๋ ์ด๋ฅผ ๋์์์ด ๋ฐ์๋ฃ๊ธฐ๋ง ํ์ฃ . ์์ ๊ธฐ๊ฐ ๋ฐฉ์ก๊ตญ์ ๋ฌด์ธ๊ฐ๋ฅผ ๋ณด๋ผ ์๋ ์์ต๋๋ค.
์๋ ๋ฐฉ์ ์์ฝ:
- ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ: ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )๋ JavaScript์
EventSourceAPI๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฒ์ HTTP ์์ฒญ(GET)์ ๋ณด๋ ๋๋ค. ์๋ฒ๋ ์ด ์์ฒญ์ ๋ํดContent-Type: text/event-streamํค๋๋ฅผ ํฌํจํ ์๋ต์ ๋ณด๋ด๋ฉฐ ์ง์์ ์ธ ์ฐ๊ฒฐ์ ์ค์ ํฉ๋๋ค. - ์๋ฒ ์๋ต ์คํธ๋ฆผ ์์: ์๋ฒ๋ ์ด ์์ฒญ์ ๋ฐ์ผ๋ฉด, ์ผ๋ฐ์ ์ธ HTTP ์๋ต์ฒ๋ผ ์ฐ๊ฒฐ์ ๋ซ์ง ์๊ณ ๊ณ์ ์ด์ด๋ก๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ก์ด ๋ฐ์ดํฐ(์ด๋ฒคํธ)๊ฐ ๋ฐ์ํ ๋๋ง๋ค, ๋ฏธ๋ฆฌ ์ฝ์๋
event:,data:,id:์ ๊ฐ์ ํ์์ ๋ง์ถฐ ๋ฐ์ดํฐ๋ฅผ ํ ์คํธ ์คํธ๋ฆผ ํํ๋ก ํด๋ผ์ด์ธํธ์ 'ํธ์'ํฉ๋๋ค. - ๋ฐ์ดํฐ ์คํธ๋ฆฌ๋ฐ: ์๋ฒ๋ ํด๋ผ์ด์ธํธ๊ฐ ์ฐ๊ฒฐ์ ๋ซ๊ฑฐ๋, ์๋ฒ ์์ฒด์์ ์ฐ๊ฒฐ์ ์ข ๋ฃํ ๋๊น์ง ๋ฐ์ดํฐ๋ฅผ ๊ณ์ํด์ ์ ์กํ ์ ์์ต๋๋ค.
- ์๋ ์ฌ์ฐ๊ฒฐ: SSE์ ํฐ ์ฅ์ ์ค ํ๋๋ ๋คํธ์ํฌ ๋ฌธ์ ๋ฑ์ผ๋ก ์ฐ๊ฒฐ์ด ๋์ด์ก์ ๋, ํด๋ผ์ด์ธํธ๊ฐ ์๋์ผ๋ก ์๋ฒ์ ์ฌ์ฐ๊ฒฐ์ ์๋ํ๋ค๋ ์ ์ ๋๋ค. ์ด๋ ๊ฐ๋ฐ์๊ฐ ๋ณ๋๋ก ์ฌ์ฐ๊ฒฐ ๋ก์ง์ ๊ตฌํํ ํ์ ์์ด ์์ ์ฑ์ ๋์ฌ์ค๋๋ค.
์ฌ์ฉ ์๋๋ฆฌ์ค, ์ฅ๋จ์
์ฃผ์ ์ฌ์ฉ ์๋๋ฆฌ์ค:
SSE๋ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก์ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ํ๋ฆ์ด ํ์ํ ์ํฉ์ ๋งค์ฐ ์ ํฉํฉ๋๋ค.
- ์ค์๊ฐ ๋ด์ค ํผ๋/์๋ฆผ: ์๋ก์ด ๊ธฐ์ฌ, ํธ์ ์๋ฆผ ๋ฑ์ด ๋ฐ์ํ์ ๋ ์ฆ์ ์ฌ์ฉ์์๊ฒ ์ ๋ฌํฉ๋๋ค.
- ์ฃผ์ ์์ธ, ์คํฌ์ธ ์ค์ฝ์ด ์ ๋ฐ์ดํธ: ์ค์๊ฐ์ผ๋ก ๋ณ๋ํ๋ ์ ๋ณด๋ฅผ ์ง์์ ์ผ๋ก ํด๋ผ์ด์ธํธ์ ์ ์กํฉ๋๋ค.
- ๋์๋ณด๋ ์ ๋ฐ์ดํธ: ์๋ฒ ์ํ, ๋ก๊ทธ, ํต๊ณ ๋ฑ ๋ชจ๋ํฐ๋ง ์ ๋ณด๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ฐ์ํฉ๋๋ค.
- ์งํ ์ํ ํ์: ํ์ผ ์ ๋ก๋ ์งํ๋ฅ , ๋ฐฑ์๋ ์์ ์งํ๋ฅ ๋ฑ์ ํด๋ผ์ด์ธํธ์ ๋ณด๊ณ ํฉ๋๋ค.
- ์ค์๊ฐ ์ถ์ฒ: ์ฌ์ฉ์ ํ๋์ ๊ธฐ๋ฐํ ์ค์๊ฐ ์ถ์ฒ์ ์ ๊ณตํฉ๋๋ค.
์ฅ์ :
- ๊ตฌํ ์ฉ์ด์ฑ:
EventSourceAPI๋ ๋งค์ฐ ๊ฐ๋จํ์ฌ ํด๋ผ์ด์ธํธ ์ธก ์ฝ๋๊ฐ ๋ณต์กํ์ง ์์ต๋๋ค. ์๋ฒ ์ธก์์๋text/event-streamํ์๋ง ๋ง์ถฐ์ฃผ๋ฉด ๋๊ธฐ ๋๋ฌธ์, ์น์์ผ์ ๋นํด ์๋์ ์ผ๋ก ๊ตฌํ์ด ์ฝ์ต๋๋ค. - HTTP/2 ํธํ์ฑ: SSE๋ HTTP ํ๋กํ ์ฝ ์์ ๊ตฌ์ถ๋๋ฏ๋ก, HTTP/2์ ๋ฉํฐํ๋ ์ฑ(multiplexing) ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ํ๋์ TCP ์ฐ๊ฒฐ๋ก ์ฌ๋ฌ SSE ์คํธ๋ฆผ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
- ์๋ ์ฌ์ฐ๊ฒฐ: ๋ธ๋ผ์ฐ์ ๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํ๋
EventSourceAPI๋ ์ฐ๊ฒฐ์ด ๋์ด์ง๋ฉด ์๋์ผ๋ก ์ฌ์ฐ๊ฒฐ์ ์๋ํฉ๋๋ค. ์ด๋ ๋คํธ์ํฌ ๋ถ์์ ์ฑ์ ๊ฐํ๋ฉฐ ๊ฐ๋ฐ ํธ์์ฑ์ ๋์ ๋๋ค. - ๋ฐฉํ๋ฒฝ ์นํ์ : ํ์ค HTTP ํ๋กํ ์ฝ์ ์ฌ์ฉํ๋ฏ๋ก, ๋๋ถ๋ถ์ ๋ฐฉํ๋ฒฝ๊ณผ ํ๋ก์ ์๋ฒ์์ ๋ฌธ์ ์์ด ์๋ํฉ๋๋ค.
- ๋ฎ์ ๋ฉ์์ง ์ค๋ฒํค๋: ์ด๊ธฐ HTTP ์ฐ๊ฒฐ ์ค์ ํ, ๊ฐ ๋ฉ์์ง๋ ์ต์ํ์
data:์ ๋์ฌ์ ์ค๋ฐ๊ฟ ๋ฌธ์๋ก ์ ์ก๋๋ฏ๋ก, HTTP ํด๋ง ๋ฐฉ์์ ๋นํด ๋ฉ์์ง๋น ์ค๋ฒํค๋๊ฐ ๋ฎ์ต๋๋ค. - ๋ค์ํ ์ด๋ฒคํธ ํ์
:
event:ํ๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ์ข ๋ฅ์ ์ด๋ฒคํธ๋ฅผ ๊ตฌ๋ถํ์ฌ ๋ณด๋ผ ์ ์์ต๋๋ค.
๋จ์ :
- ๋จ๋ฐฉํฅ ํต์ : ๊ฐ์ฅ ํฐ ํ๊ณ์ ์ ํด๋ผ์ด์ธํธ์์ ์๋ฒ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์๋ค๋ ๊ฒ์ ๋๋ค. ๋ง์ฝ ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ผ ํ๋ ๊ฒฝ์ฐ(์: ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ ), SSE๋ ์ ํฉํ์ง ์์ผ๋ฉฐ, ์ด ๊ฒฝ์ฐ ๋ณ๋์ HTTP ์์ฒญ(POST, PUT ๋ฑ)์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
- ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ ์ ์ก ๋ถ๊ฐ: SSE๋ ํ ์คํธ ๊ธฐ๋ฐ์ ๋ฐ์ดํฐ๋ง ์ ์กํ ์ ์์ต๋๋ค. ์ด๋ฏธ์ง๋ ์ค๋์ค/๋น๋์ค์ ๊ฐ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ์ง์ ์ ์กํ ์๋ ์์ต๋๋ค.
- ๋์ ์ฐ๊ฒฐ ์ ํ: HTTP/1.1 ํ๊ฒฝ์์๋ ๋ธ๋ผ์ฐ์ ๋น ๋๋ฉ์ธ๋ณ๋ก 6~8๊ฐ ์ ๋์ ๋์ ์ฐ๊ฒฐ ์ ํ์ด ์์ ์ ์์ต๋๋ค. (HTTP/2์์๋ ๋ฉํฐํ๋ ์ฑ์ผ๋ก ํด๊ฒฐ ๊ฐ๋ฅ)
- ํด๋ฆฌํ ํ์: ๊ตฌํ Internet Explorer์์๋
EventSource๋ฅผ ์ง์ํ์ง ์์ผ๋ฏ๋ก ํด๋ฆฌํ(Polyfill)์ด ํ์ํ ์ ์์ต๋๋ค. (ํ๋ ๋ธ๋ผ์ฐ์ ์์๋ ๋๋ถ๋ถ ์ง์)
SSE ์ฝ๋ ์์
๋ค์์ SSE ๋ฐฉ์์ ๊ฐ๋จํ ์์์
๋๋ค. ์๋ฒ๋ 3์ด๋ง๋ค ์๋ก์ด ๋ฉ์์ง๋ฅผ ํด๋ผ์ด์ธํธ์ ํธ์ํ๊ณ , ํด๋ผ์ด์ธํธ๋ ์ด๋ฅผ EventSource๋ก ์์ ํฉ๋๋ค.
1. ์๋ฒ ์ฝ๋ (Node.js + Express)server.js (๊ธฐ์กด ํ์ผ์ SSE ๋ผ์ฐํธ ์ถ๊ฐ)
const express = require('express');
const app = express();
const port = 3000;
let messageCounter = 0;
let pendingRequests = []; // Long Polling ์์ฒญ๋ค์ ์ ์ฅํ ๋ฐฐ์ด
// Polling์ ์ํ ๋ฉ์์ง ์์ฑ
let pollingMessages = [];
setInterval(() => {
messageCounter++;
if (messageCounter % 5 === 0) {
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${messageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค! (Polling)`;
pollingMessages.push(newMessage);
// console.log(`์๋ก์ด Polling ๋ฉ์์ง ์์ฑ: ${newMessage}`);
}
}, 1000);
// Polling ์๋ํฌ์ธํธ
app.get('/messages', (req, res) => {
if (pollingMessages.length > 0) {
res.json({ newMessages: pollingMessages });
pollingMessages = [];
} else {
res.json({ newMessages: [] });
}
});
// Long Polling์ ์ํ ๋ฉ์์ง ์์ฑ ๋ฐ ์ฒ๋ฆฌ
let longPollingMessageCounter = 0;
let longPollingPendingRequests = [];
setInterval(() => {
longPollingMessageCounter++;
if (longPollingMessageCounter % 5 === 0) {
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${longPollingMessageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค! (Long Polling)`;
console.log(`์๋ก์ด Long Polling ๋ฉ์์ง ์์ฑ: ${newMessage}`);
while (longPollingPendingRequests.length > 0) {
const res = longPollingPendingRequests.shift();
res.json({ newMessages: [newMessage] });
}
}
}, 1000);
// Long Polling ์๋ํฌ์ธํธ
app.get('/long-polling-messages', (req, res) => {
req.socket.setTimeout(20 * 1000); // 20์ด ํ์์์
longPollingPendingRequests.push(res);
// console.log(`์๋ก์ด ๋กฑ ํด๋ง ์์ฒญ ๋๊ธฐ ์ค. ํ์ฌ ๋๊ธฐ ์์ฒญ ์: ${longPollingPendingRequests.length}`);
req.on('timeout', () => {
// console.log('๋กฑ ํด๋ง ์์ฒญ ํ์์์!');
const index = longPollingPendingRequests.indexOf(res);
if (index > -1) {
longPollingPendingRequests.splice(index, 1);
}
res.status(200).json({ newMessages: [] });
});
});
// SSE๋ฅผ ์ํ ๋ฉ์์ง ์์ฑ ๋ฐ ์ฒ๋ฆฌ
let sseClients = []; // SSE ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ๋ค์ ์ ์ฅํ ๋ฐฐ์ด
let sseMessageCounter = 0;
// ์ฃผ๊ธฐ์ ์ผ๋ก SSE ๋ฉ์์ง ์์ฑ ๋ฐ ์ ์ก
setInterval(() => {
sseMessageCounter++;
const message = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด SSE ๋ฉ์์ง #${sseMessageCounter}๊ฐ ๋์ฐฉํ์ต๋๋ค!`;
const eventData = `data: ${JSON.stringify({ content: message, timestamp: new Date() })}\n\n`; // SSE ๋ฐ์ดํฐ ํ์
console.log(`SSE ๋ฉ์์ง ์ ์ก: ${message}`);
sseClients.forEach(client => {
client.write(eventData); // ๊ฐ ํด๋ผ์ด์ธํธ์ ์ด๋ฒคํธ ๋ฐ์ดํฐ ์ ์ก
});
}, 3000); // 3์ด๋ง๋ค ๋ฉ์์ง ์์ฑ
// SSE ์๋ํฌ์ธํธ
app.get('/sse-events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*', // CORS ํ์ฉ (ํ์์ ๋ฐ๋ผ ์ค์ )
});
res.write('retry: 10000\n\n'); // ํด๋ผ์ด์ธํธ๊ฐ 10์ด ํ ์ฌ์ฐ๊ฒฐ ์๋ํ๋๋ก ์ค์
sseClients.push(res); // ํด๋ผ์ด์ธํธ ์๋ต ๊ฐ์ฒด๋ฅผ ์ ์ฅ
req.on('close', () => {
// ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ์ด ๋์ด์ง๋ฉด ๋ฐฐ์ด์์ ์ ๊ฑฐ
const index = sseClients.indexOf(res);
if (index > -1) {
sseClients.splice(index, 1);
console.log('SSE ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํด์ . ํ์ฌ ์ฐ๊ฒฐ ์:', sseClients.length);
}
});
console.log('์๋ก์ด SSE ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ. ํ์ฌ ์ฐ๊ฒฐ ์:', sseClients.length);
});
// ํด๋ผ์ด์ธํธ HTML ํ์ผ์ ์๋นํ๊ธฐ ์ํ ์ ์ ํ์ผ ๊ฒฝ๋ก ์ค์
app.use(express.static('public'));
app.listen(port, () => {
console.log(`์๋ฒ๊ฐ http://localhost:${port} ์์ ์คํ ์ค์
๋๋ค.`);
console.log(`๋ธ๋ผ์ฐ์ ์์ http://localhost:${port}/index.html ์ ์ ์ํ์ธ์.`);
});
2. ํด๋ผ์ด์ธํธ ์ฝ๋ (HTML + JavaScript)public/index.html (๊ธฐ์กด ํ์ผ์ SSE ์น์
์ถ๊ฐ)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>์น ์ค์๊ฐ ํต์ ์์ </title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
h1, h2 { color: #0056b3; margin-top: 30px;}
.section-container {
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 30px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
}
#message-container-polling, #message-container-long-polling, #message-container-sse, #message-container-websocket {
border: 1px solid #eee;
padding: 15px;
min-height: 150px;
background-color: #f9f9f9;
overflow-y: auto;
max-height: 400px;
margin-top: 15px;
border-radius: 5px;
}
.message-item {
padding: 8px 0;
border-bottom: 1px dashed #eee;
}
.message-item:last-child {
border-bottom: none;
}
.message-item.info { color: #555; font-style: italic; }
.message-item.received { color: #007bff; font-weight: bold; }
.message-item.sent { color: #28a745; }
.message-item.error { color: #dc3545; }
button {
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
margin-top: 10px;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
input[type="text"] {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
</style>
</head>
<body>
<h1>์น ์ค์๊ฐ ํต์ ์์ </h1>
<p>๋ค์ํ ์น ์ค์๊ฐ ํต์ ๋ฐฉ๋ฒ๋ค์ ๋์์ ๋น๊ตํด๋ด
๋๋ค.</p>
<!-- Polling Section -->
<div class="section-container">
<h2>Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ <strong>1์ด๋ง๋ค</strong> ์๋ฒ์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋์ง ์์ฒญํฉ๋๋ค.</p>
<button id="togglePolling">Polling ์์/์ค์ง</button>
<div id="message-container-polling">
<p>์๋ฒ ๋ฉ์์ง (Polling):</p>
</div>
</div>
<!-- Long Polling Section -->
<div class="section-container">
<h2>Long Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ๋ฉ์์ง๊ฐ ๋์ฐฉํ ๋๊น์ง ๋๊ธฐํ๊ณ , ๋์ฐฉํ๋ฉด ์ฆ์ ์๋ต์ ๋ฐ์ต๋๋ค. ์๋ต์ ๋ฐ์ผ๋ฉด ๋ฐ๋ก ๋ค์ ์์ฒญ์ ๋ณด๋
๋๋ค.</p>
<button id="toggleLongPolling">Long Polling ์์/์ค์ง</button>
<div id="message-container-long-polling">
<p>์๋ฒ ๋ฉ์์ง (Long Polling):</p>
</div>
</div>
<!-- SSE Section -->
<div class="section-container">
<h2>SSE (Server-Sent Events) ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ๋ก๋ถํฐ ๋จ๋ฐฉํฅ ์คํธ๋ฆผ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์์ ํฉ๋๋ค.</p>
<button id="toggleSSE">SSE ์์/์ค์ง</button>
<div id="message-container-sse">
<p>์๋ฒ ๋ฉ์์ง (SSE):</p>
</div>
</div>
<!-- WebSocket Section placeholder -->
<div class="section-container">
<h2>WebSocket ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ์๋ฐฉํฅ ์ ์ด์ค ํต์ ์ ์ค์ ํ๊ณ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ต๋๋ค.</p>
<input type="text" id="websocketInput" placeholder="์๋ฒ๋ก ๋ณด๋ผ ๋ฉ์์ง" style="padding: 8px; width: 250px; margin-right: 10px;">
<button id="sendWebSocketMessage">๋ฉ์์ง ์ ์ก</button>
<button id="toggleWebSocket">WebSocket ์ฐ๊ฒฐ/ํด์ </button>
<div id="message-container-websocket">
<p>์๋ฒ ๋ฉ์์ง (WebSocket):</p>
</div>
</div>
<script>
// --- ๊ณตํต ๋ฉ์์ง ์ถ๋ ฅ ํจ์ ---
function addMessageToDisplay(containerId, msg, type = 'info') {
const container = document.getElementById(containerId);
if (!container) return;
const p = document.createElement('p');
p.className = `message-item ${type}`;
p.textContent = msg;
container.appendChild(p);
container.scrollTop = container.scrollHeight;
}
// --- Polling Logic ---
const pollingMessageContainer = document.getElementById('message-container-polling');
const togglePollingButton = document.getElementById('togglePolling');
let pollingIntervalId = null;
let isPollingActive = false;
async function pollMessages() {
try {
const response = await fetch('/messages');
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling):', msg);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling: ์๋ก์ด ๋ฉ์์ง๊ฐ ์์ต๋๋ค.`, 'info');
}
} catch (error) {
console.error('Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค๋ฅ: ${error.message}`, 'error');
}
}
function startPolling() {
if (!isPollingActive) {
isPollingActive = true;
pollingIntervalId = setInterval(pollMessages, 1000);
togglePollingButton.textContent = 'Polling ์ค์ง';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์์.`, 'info');
}
}
function stopPolling() {
if (isPollingActive) {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
togglePollingButton.textContent = 'Polling ์์';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค์ง.`, 'info');
}
}
togglePollingButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
});
// --- Long Polling Logic ---
const longPollingMessageContainer = document.getElementById('message-container-long-polling');
const toggleLongPollingButton = document.getElementById('toggleLongPolling');
let isLongPollingActive = false;
let longPollingAbortController = null;
async function requestLongPolling() {
if (!isLongPollingActive) return;
longPollingAbortController = new AbortController();
const signal = longPollingAbortController.signal;
try {
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ์๋ฒ์ ๋กฑ ํด๋ง ์์ฒญ ์ค...`, 'info');
const response = await fetch('/long-polling-messages', { signal });
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Long Polling):', msg);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๊ฑฐ๋ ํ์์์.`, 'info');
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('๋กฑ ํด๋ง ์์ฒญ์ด ์ทจ์๋์์ต๋๋ค.');
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์์ฒญ์ด ์ค์ง๋์์ต๋๋ค.`, 'info');
} else {
console.error('Long Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค๋ฅ: ${error.message}`, 'error');
}
} finally {
if (isLongPollingActive) {
requestLongPolling();
}
}
}
function startLongPolling() {
if (!isLongPollingActive) {
isLongPollingActive = true;
toggleLongPollingButton.textContent = 'Long Polling ์ค์ง';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์์.`, 'info');
requestLongPolling();
}
}
function stopLongPolling() {
if (isLongPollingActive) {
isLongPollingActive = false;
if (longPollingAbortController) {
longPollingAbortController.abort();
}
toggleLongPollingButton.textContent = 'Long Polling ์์';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค์ง.`, 'info');
}
}
toggleLongPollingButton.addEventListener('click', () => {
if (isLongPollingActive) {
stopLongPolling();
} else {
startLongPolling();
}
});
// ํ์ด์ง ๋ก๋ ์ Polling ๋ฐ Long Polling์ ์๋์ผ๋ก ์์ (SSE, WebSocket์ ์๋ ์์)
window.addEventListener('load', () => {
startPolling();
startLongPolling();
// startSSE(); // SSE๋ ์๋ ์์์ผ๋ก ๋ณ๊ฒฝ
});
</script>
</body>
</html>
์คํ ๋ฐฉ๋ฒ:
- ๊ธฐ์กด
server.js์public/index.htmlํ์ผ์ ์ ์ฝ๋๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. - ํฐ๋ฏธ๋์์
node server.js๋ฅผ ๋ค์ ์คํํฉ๋๋ค. - ์น ๋ธ๋ผ์ฐ์ ์์
http://localhost:3000/index.html๋ก ์ ์ํ ํ, "SSE ์์/์ค์ง" ๋ฒํผ์ ๋๋ฌ SSE ์ฐ๊ฒฐ์ ์์ํฉ๋๋ค. 3์ด๋ง๋ค ์๋ฒ์์ ํธ์๋๋ ๋ฉ์์ง๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
SSE๋ ๋จ๋ฐฉํฅ ํธ์ ํต์ ์ ์์ด ๋งค์ฐ ํจ์จ์ ์ด๊ณ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ๋๋ค. ํ์ง๋ง ํด๋ผ์ด์ธํธ์ ์๋ฒ ์์ชฝ์์ ์์ ๋กญ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์์ผ ํ๋ ๊ฒฝ์ฐ, ์ฆ ์๋ฐฉํฅ ํต์ ์ด ํ์ํ ๊ฒฝ์ฐ์๋ ๋ค์์ผ๋ก ์๊ฐํ ์น์์ผ์ด ๊ฐ๋ ฅํ ๋์์ด ๋ฉ๋๋ค.
๐ค ์๋ฐฉํฅ ์ ์ด์ค ํต์ : WebSocket (์น์์ผ)
WebSocket์ ๊ฐ๋ , Handshake ๊ณผ์ , ์๋ฐฉํฅ ์ ์ด์ค ํต์
์์ ์ดํด๋ณธ ํด๋ง, ๋กฑ ํด๋ง, SSE๋ ๊ฐ๊ฐ ์ค์๊ฐ ํต์ ์ ํจ์จ์ฑ์ ๊ฐ์ ํ์ง๋ง, ๋ชจ๋ HTTP ๊ธฐ๋ฐ์ด๋ผ๋ ๊ทผ๋ณธ์ ์ธ ํ๊ณ์ ์ ์๊ณ ์์ต๋๋ค. HTTP๋ ์์ฒญ-์๋ต ๋ชจ๋ธ์ด๊ฑฐ๋(ํด๋ง, ๋กฑ ํด๋ง), ๋จ๋ฐฉํฅ ์คํธ๋ฆผ(SSE)์ผ๋ก๋ง ์๋ํฉ๋๋ค. ํ์ง๋ง ์ฑํ , ์จ๋ผ์ธ ๊ฒ์, ํ์ ํ์ ๋ฑ ์ง์ ํ ์๋ฐฉํฅ ์ํธ์์ฉ์ด ํ์ํ ์๋น์ค์์๋ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ์์ ๋กญ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋ '์ ์ด์ค(Full-duplex)' ํต์ ์ฑ๋์ด ํ์์ ์ ๋๋ค. ์ด ์๊ตฌ๋ฅผ ์ถฉ์กฑํ๊ธฐ ์ํด ๋ฑ์ฅํ ๊ฒ์ด ๋ฐ๋ก '์น์์ผ(WebSocket)'์ ๋๋ค.
์น์์ผ์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ํ๋์ ์๊ตฌ์ ์ธ TCP ์ฐ๊ฒฐ์ ์ค์ ํ๊ณ , ์ด ์ฐ๊ฒฐ์ ํตํด ์๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๊ฒ ํ๋ ํ๋กํ ์ฝ์ ๋๋ค. ์ด๋ ๋ง์น ์ ํ๋ฅผ ๊ฑธ์ด ํ ๋ฒ ์ฐ๊ฒฐ๋๋ฉด ์๋ก ๋๊ธฐ์ง ์๊ณ ๋ํํ ์ ์๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค. ๊ธฐ์กด HTTP ํต์ ์ด ๋งค๋ฒ ์๋ก์ด ํธ์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๊ฒ์ด์๋ค๋ฉด, ์น์์ผ์ ํ ๋ฒ ์ฐ๊ฒฐ๋ ์ ํ์ ์ด๋ผ๊ณ ๋น์ ํ ์ ์์ต๋๋ค.
Handshake ๊ณผ์ :
์น์์ผ ์ฐ๊ฒฐ์ ์ผ๋ฐ์ ์ธ HTTP ์์ฒญ์ผ๋ก ์์๋ฉ๋๋ค. ์ด ๊ณผ์ ์ '์น์์ผ ํธ๋์ ฐ์ดํฌ(WebSocket Handshake)'๋ผ๊ณ ๋ถ๋ฆ ๋๋ค.
- ํด๋ผ์ด์ธํธ ์์ฒญ (Upgrade Request): ํด๋ผ์ด์ธํธ(๋ธ๋ผ์ฐ์ )๋ ์๋ฒ์
ws://๋๋wss://(๋ณด์ ์ฐ๊ฒฐ) ์คํค๋ง๋ฅผ ์ฌ์ฉํ๋ HTTP ์์ฒญ์ ๋ณด๋ ๋๋ค. ์ด ์์ฒญ์๋Upgrade: websocket๋ฐConnection: Upgradeํค๋๊ฐ ํฌํจ๋์ด, "๋๋ ์ด HTTP ์ฐ๊ฒฐ์ ์น์์ผ ์ฐ๊ฒฐ๋ก ์ ๊ทธ๋ ์ด๋ํ๊ณ ์ถ๋ค"๋ ์์ฌ๋ฅผ ๋ฐํ๋๋ค. ๋ํSec-WebSocket-Key์ ๊ฐ์ ์ถ๊ฐ ํค๋๋ฅผ ํตํด ๋ณด์ ๋ฐ ์ ํจ์ฑ์ ๊ฒ์ฆํ ์ ์์ต๋๋ค. - ์๋ฒ ์๋ต (Upgrade Response): ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์ ์
๊ทธ๋ ์ด๋ ์์ฒญ์ ์๋ฝํ๋ฉด,
HTTP/1.1 101 Switching Protocols์ํ ์ฝ๋์ ํจ๊ปUpgrade: websocket,Connection: Upgrade,Sec-WebSocket-Accept๋ฑ์ ํค๋๋ฅผ ํฌํจํ ์๋ต์ ๋ณด๋ ๋๋ค. ์ด ์๋ต์ "HTTP ์ฐ๊ฒฐ์ ์น์์ผ ์ฐ๊ฒฐ๋ก ์ ํํ๊ฒ ๋ค"๋ ๋์๋ฅผ ์๋ฏธํฉ๋๋ค. - ์น์์ผ ์ฐ๊ฒฐ ์ค์ : ํธ๋์ ฐ์ดํฌ๊ฐ ์ฑ๊ณต์ ์ผ๋ก ์๋ฃ๋๋ฉด, ๊ธฐ์กด HTTP ์ฐ๊ฒฐ์ ์น์์ผ ์ฐ๊ฒฐ๋ก '์น๊ฒฉ(upgrade)'๋๊ณ , ๊ทธ ์ดํ๋ถํฐ๋ HTTP ํ๋กํ ์ฝ์ด ์๋ ์น์์ผ ํ๋กํ ์ฝ์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ฒ ๋ฉ๋๋ค. ์ด ์ฐ๊ฒฐ์ ํด๋ผ์ด์ธํธ๋ ์๋ฒ๊ฐ ๋ช ์์ ์ผ๋ก ๋ซ์ ๋๊น์ง ์ ์ง๋ฉ๋๋ค.
์๋ฐฉํฅ ์ ์ด์ค ํต์ ํน์ง:
์น์์ผ ์ฐ๊ฒฐ์ด ์ค์ ๋๋ฉด, ํด๋ผ์ด์ธํธ์ ์๋ฒ๋ ์๋ก ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค.
- ์๋ฐฉํฅ(Bidirectional): ํด๋ผ์ด์ธํธ์ ์๋ฒ ๋ชจ๋ ์ธ์ ๋ ์ง ์๋๋ฐฉ์๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์์ต๋๋ค.
- ์ ์ด์ค(Full-duplex): ๋ฐ์ดํฐ ์ ์ก์ด ๋์์ ์๋ฐฉํฅ์ผ๋ก ์ด๋ฃจ์ด์ง ์ ์์ต๋๋ค. ์ฆ, ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ด๋ ๋์์ ์๋ฒ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์์ต๋๋ค. ์ด๋ HTTP์ ๋จ๋ฐฉํฅ ์์ฒญ-์๋ต ๋ชจ๋ธ๊ณผ๋ ํ์ฐํ ๋ค๋ฆ ๋๋ค.
- ์ง์์ ์ธ ์ฐ๊ฒฐ(Persistent Connection): ํ ๋ฒ ์ฐ๊ฒฐ์ด ์ค์ ๋๋ฉด, ๋ฐ์ดํฐ ์ ์ก์ด ์์ด๋ ์ฐ๊ฒฐ์ด ์ ์ง๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๋งค๋ฒ ์ฐ๊ฒฐ์ ์๋ก ์๋ฆฝํ๋ ์ค๋ฒํค๋ ์์ด ๋ฎ์ ์ง์ฐ ์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ต๋๋ค.
- ํ๋ ์ ๊ธฐ๋ฐ(Frame-based): ์น์์ผ์ HTTP์ ๋ฌ๋ฆฌ 'ํ๋ ์'์ด๋ผ๋ ๋ ์์ ๋จ์๋ก ๋ฐ์ดํฐ๋ฅผ ๋ถํ ํ์ฌ ์ ์กํฉ๋๋ค. ์ด๋ ๋ฉ์์ง ๊ธฐ๋ฐ์ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ฉฐ, ํ ์คํธ ๋ฐ์ดํฐ(UTF-8)๋ฟ๋ง ์๋๋ผ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ(Blob, ArrayBuffer)๋ ํจ์จ์ ์ผ๋ก ์ ์กํ ์ ์์ต๋๋ค.
์ฃผ์ ์ฌ์ฉ ์ฌ๋ก, ์ฅ๋จ์
์ฃผ์ ์ฌ์ฉ ์ฌ๋ก:
์น์์ผ์ ์ค์๊ฐ ์๋ฐฉํฅ ์ํธ์์ฉ์ด ํต์ฌ์ธ ์๋น์ค์ ์ฃผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
- ์ค์๊ฐ ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ : ๊ฐ์ฅ ๋ํ์ ์ธ ์ฌ๋ก๋ก, ์ฌ์ฉ์๋ค์ด ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ๋ฐ ์ต์ ํ๋์ด ์์ต๋๋ค.
- ์จ๋ผ์ธ ๊ฒ์: ํ๋ ์ด์ด ๊ฐ์ ์ค์๊ฐ ์ํธ์์ฉ, ์์น ์ ๋ฐ์ดํธ, ๊ฒ์ ์ํ ๋๊ธฐํ ๋ฑ์ ํ์์ ์ ๋๋ค.
- ์ฃผ์ ๊ฑฐ๋ ํ๋ซํผ: ์ค์๊ฐ ์์ธ ๋ณ๋ ์๋ฆผ ๋ฐ ์ฌ์ฉ์ ๊ฑฐ๋ ์์ฒญ ์ฒ๋ฆฌ์ ์ฌ์ฉ๋ฉ๋๋ค.
- ํ์/์์ฑ ํตํ (WebRTC ๋ณด์กฐ): WebRTC์ ์๊ทธ๋๋ง(์ฐ๊ฒฐ ์ค์ ์ ๋ณด ๊ตํ) ๊ณผ์ ์์ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
- ํ์ ๋๊ตฌ (๊ตฌ๊ธ ๋ ์ค ๋ฑ): ์ฌ๋ฌ ์ฌ์ฉ์๊ฐ ๋์์ ๋ฌธ์๋ฅผ ํธ์งํ ๋ ์ค์๊ฐ์ผ๋ก ๋ณ๊ฒฝ ์ฌํญ์ ๋๊ธฐํํฉ๋๋ค.
- IoT ์ฅ์น ์ ์ด: ์ค๋งํธ ํ ์ฅ์น๋ ์ฐ์ ์ฉ ์ผ์ ๋ฐ์ดํฐ ์์ง ๋ฐ ์ ์ด์ ํ์ฉ๋ฉ๋๋ค.
์ฅ์ :
- ์ง์ ํ ์๋ฐฉํฅ ์ ์ด์ค ํต์ : ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์์ ๋กญ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์ด, ๋ณต์กํ ์ค์๊ฐ ์ํธ์์ฉ ๊ตฌํ์ ์ต์ ํ๋์ด ์์ต๋๋ค.
- ๋ฎ์ ์ง์ฐ ์๊ฐ (Low Latency): ํ ๋ฒ ์ฐ๊ฒฐ์ด ์ค์ ๋๋ฉด ์ถ๊ฐ์ ์ธ HTTP ํธ๋์ ฐ์ดํฌ ์์ด ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ์ ์กํ ์ ์์ด ๋งค์ฐ ๋ฎ์ ์ง์ฐ ์๊ฐ์ ๋ณด์ฅํฉ๋๋ค.
- ์ ์ ์ค๋ฒํค๋: HTTP ํค๋๊ฐ ๋งค๋ฒ ์ ์ก๋ ํ์ ์์ด, ์ต์ํ์ ์น์์ผ ํ๋ ์ ํค๋๋ง์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ฏ๋ก ๋คํธ์ํฌ ํธ๋ํฝ ์ค๋ฒํค๋๊ฐ ๋งค์ฐ ์ ์ต๋๋ค.
- ๋ค์ํ ๋ฐ์ดํฐ ํ์ ์ง์: ํ ์คํธ(UTF-8)์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ํจ์จ์ ์ผ๋ก ์ ์กํ ์ ์์ต๋๋ค.
- ํ์ค ํ๋กํ ์ฝ: IETF ํ์ค(RFC 6455)์ผ๋ก ์ ์๋์ด ์์ผ๋ฉฐ, ๋๋ถ๋ถ์ ํ๋ ์น ๋ธ๋ผ์ฐ์ ์ ์๋ฒ ํ๋ซํผ์์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํฉ๋๋ค.
๋จ์ :
- ์ด๊ธฐ ์ฐ๊ฒฐ ์ค์ ์ค๋ฒํค๋ (Handshake): ์ด๊ธฐ HTTP ํธ๋์ ฐ์ดํฌ ๊ณผ์ ์ด ํ์ํ์ฌ, ์ฒซ ์ฐ๊ฒฐ ์ ์ฝ๊ฐ์ ์ค๋ฒํค๋๊ฐ ๋ฐ์ํฉ๋๋ค.
- ์ค๋๋ ๋ธ๋ผ์ฐ์ ํธํ์ฑ: IE 9 ์ดํ ๋ฑ ๊ตฌํ ๋ธ๋ผ์ฐ์ ๋ ์น์์ผ์ ์ง์ํ์ง ์์ต๋๋ค. (ํ์ง๋ง ์์ฆ ๋๋ถ๋ถ์ ์๋น์ค๋ ๊ตฌํ ๋ธ๋ผ์ฐ์ ์ง์์ ์ค๋จํฉ๋๋ค)
- ๋ฐฉํ๋ฒฝ ๋ฐ ํ๋ก์ ๋ฌธ์ : ์ผ๋ถ ์๊ฒฉํ ๋ฐฉํ๋ฒฝ์ด๋ ํ๋ก์๋ ์น์์ผ ํ๋กํ ์ฝ์ ์ฐจ๋จํ ์ ์์ต๋๋ค.
wss://๋ฅผ ํตํด WebSocket over TLS๋ฅผ ์ฌ์ฉํ๋ฉด HTTP(S) ํฌํธ๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์ด๋ฌํ ๋ฌธ์ ๋ฅผ ์ฐํํ ์ ์์ต๋๋ค. - ์๋ฒ ๋ฆฌ์์ค ์ฌ์ฉ: ์ง์์ ์ธ ์ฐ๊ฒฐ์ ์ ์งํด์ผ ํ๋ฏ๋ก ์๋ฒ๋ ์ฐ๊ฒฐ๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ๊ด๋ฆฌํด์ผ ํฉ๋๋ค. ์ด๋ ๋ง์ ๋์ ์ฐ๊ฒฐ์ด ์์ ๋ ์๋ฒ ๋ฆฌ์์ค(๋ฉ๋ชจ๋ฆฌ, ํ์ผ ๋์คํฌ๋ฆฝํฐ)๋ฅผ ๋ง์ด ์๋ชจํ ์ ์์ต๋๋ค.
- ์ฌ์ฐ๊ฒฐ ๋ฐ ๋ฉ์์ง ์์ ๋ณด์ฅ:
EventSource์ ๋ฌ๋ฆฌ ์น์์ผ API๋ ์๋ ์ฌ์ฐ๊ฒฐ ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ์ผ๋ก ์ ๊ณตํ์ง ์์ต๋๋ค. ๋ํ ๋ฉ์์ง ์์ค์ด๋ ์์ ๋ณด์ฅ์ ์ํ ์ถ๊ฐ์ ์ธ ๋ก์ง(์: ์ํ์ค ๋ฒํธ)์ด ํ์ํ ์ ์์ต๋๋ค. ์ด๋ฅผ ์ํด Socket.IO์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ํ์ฉ๋ฉ๋๋ค.
WebSocket ์ฝ๋ ์์
๋ค์์ ์น์์ผ ๋ฐฉ์์ ๊ฐ๋จํ ์์์
๋๋ค. ์๋ฒ๋ ws ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ , ํด๋ผ์ด์ธํธ๋ ํ์ค WebSocket API๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฐฉํฅ ํต์ ์ ๊ตฌํํฉ๋๋ค.
1. ์๋ฒ ์ฝ๋ (Node.js + ws ๋ผ์ด๋ธ๋ฌ๋ฆฌ)server.js (๊ธฐ์กด ํ์ผ์ WebSocket ์๋ฒ ์ถ๊ฐ)
๋จผ์ ws ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํด์ผ ํฉ๋๋ค: npm install ws
const express = require('express');
const http = require('http'); // Express์ ํจ๊ป HTTP ์๋ฒ ์ฌ์ฉ
const WebSocket = require('ws'); // WebSocket ๋ผ์ด๋ธ๋ฌ๋ฆฌ
const app = express();
const port = 3000;
// --- Polling (์ด์ ์ฝ๋์ ๋์ผ) ---
let pollingMessages = [];
let pollingMessageCounter = 0;
setInterval(() => {
pollingMessageCounter++;
if (pollingMessageCounter % 5 === 0) {
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${pollingMessageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค! (Polling)`;
pollingMessages.push(newMessage);
}
}, 1000);
app.get('/messages', (req, res) => {
if (pollingMessages.length > 0) {
res.json({ newMessages: pollingMessages });
pollingMessages = [];
} else {
res.json({ newMessages: [] });
}
});
// --- Long Polling (์ด์ ์ฝ๋์ ๋์ผ) ---
let longPollingMessageCounter = 0;
let longPollingPendingRequests = [];
setInterval(() => {
longPollingMessageCounter++;
if (longPollingMessageCounter % 5 === 0) {
const newMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง #${longPollingMessageCounter / 5}๊ฐ ๋์ฐฉํ์ต๋๋ค! (Long Polling)`;
console.log(`์๋ก์ด Long Polling ๋ฉ์์ง ์์ฑ: ${newMessage}`);
while (longPollingPendingRequests.length > 0) {
const res = longPollingPendingRequests.shift();
res.json({ newMessages: [newMessage] });
}
}
}, 1000);
app.get('/long-polling-messages', (req, res) => {
req.socket.setTimeout(20 * 1000);
longPollingPendingRequests.push(res);
req.on('timeout', () => {
const index = longPollingPendingRequests.indexOf(res);
if (index > -1) {
longPollingPendingRequests.splice(index, 1);
}
res.status(200).json({ newMessages: [] });
});
});
// --- SSE (์ด์ ์ฝ๋์ ๋์ผ) ---
let sseClients = [];
let sseMessageCounter = 0;
setInterval(() => {
sseMessageCounter++;
const message = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ์๋ก์ด SSE ๋ฉ์์ง #${sseMessageCounter}๊ฐ ๋์ฐฉํ์ต๋๋ค!`;
const eventData = `data: ${JSON.stringify({ content: message, timestamp: new Date() })}\n\n`;
sseClients.forEach(client => {
client.write(eventData);
});
}, 3000);
app.get('/sse-events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*',
});
res.write('retry: 10000\n\n');
sseClients.push(res);
req.on('close', () => {
const index = sseClients.indexOf(res);
if (index > -1) {
sseClients.splice(index, 1);
}
});
});
// --- WebSocket ์๋ฒ ์ค์ ---
const server = http.createServer(app); // Express ์ฑ์ HTTP ์๋ฒ์ ์ฐ๊ฒฐ
const wss = new WebSocket.Server({ server }); // HTTP ์๋ฒ ์์ WebSocket ์๋ฒ ์์ฑ
let websocketClients = new Set(); // ์ฐ๊ฒฐ๋ WebSocket ํด๋ผ์ด์ธํธ ์ ์ฅ
wss.on('connection', ws => {
websocketClients.add(ws); // ์๋ก์ด ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ์ Set์ ์ถ๊ฐ
console.log('์๋ก์ด WebSocket ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ. ํ์ฌ ์ฐ๊ฒฐ ์:', websocketClients.size);
ws.on('message', message => {
// ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฉ์์ง ์์
const receivedMessage = message.toString(); // Buffer๋ฅผ ๋ฌธ์์ด๋ก ๋ณํ
console.log(`WebSocket ๋ฉ์์ง ์์ : ${receivedMessage}`);
// ๋ฐ์ ๋ฉ์์ง๋ฅผ ๋ชจ๋ ์ฐ๊ฒฐ๋ ํด๋ผ์ด์ธํธ์๊ฒ ๋ธ๋ก๋์บ์คํธ
websocketClients.forEach(client => {
if (client !== ws && client.readyState === WebSocket.OPEN) {
client.send(`[${new Date().toLocaleTimeString()}] ๋ค๋ฅธ ํด๋ผ์ด์ธํธ: ${receivedMessage}`);
} else if (client === ws) {
// ๋ฉ์์ง๋ฅผ ๋ณด๋ธ ํด๋ผ์ด์ธํธ์๊ฒ๋ ์์ ์ ๋ฉ์์ง์์ ์๋ฆผ
client.send(`[${new Date().toLocaleTimeString()}] ๋(${client.id || '์ ์ ์์'}): ${receivedMessage}`);
}
});
});
ws.on('close', () => {
websocketClients.delete(ws); // ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํด์ ์ Set์์ ์ ๊ฑฐ
console.log('WebSocket ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ ํด์ . ํ์ฌ ์ฐ๊ฒฐ ์:', websocketClients.size);
});
ws.on('error', error => {
console.error('WebSocket ์ค๋ฅ ๋ฐ์:', error);
});
// ์ฐ๊ฒฐ ์ ํด๋ผ์ด์ธํธ์๊ฒ ์ด๊ธฐ ๋ฉ์์ง ์ ์ก
ws.send(`[${new Date().toLocaleTimeString()}] WebSocket ์๋ฒ์ ์ฐ๊ฒฐ๋์์ต๋๋ค!`);
});
// ์ฃผ๊ธฐ์ ์ผ๋ก WebSocket ํด๋ผ์ด์ธํธ์๊ฒ ๋ฉ์์ง ์ ์ก (์๋ฒ ํธ์ ์์)
let wsMessageCounter = 0;
setInterval(() => {
wsMessageCounter++;
const serverPushMessage = `[${new Date().toLocaleTimeString()}] ์๋ฒ์์ ํธ์ํ ๋ฉ์์ง #${wsMessageCounter} (WebSocket)`;
websocketClients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(serverPushMessage);
}
});
}, 5000); // 5์ด๋ง๋ค ์๋ฒ์์ ๋ชจ๋ ํด๋ผ์ด์ธํธ์ ๋ฉ์์ง ํธ์
// ํด๋ผ์ด์ธํธ HTML ํ์ผ์ ์๋นํ๊ธฐ ์ํ ์ ์ ํ์ผ ๊ฒฝ๋ก ์ค์
app.use(express.static('public'));
// HTTP ์๋ฒ์ WebSocket ์๋ฒ๋ฅผ ํจ๊ป ์์
server.listen(port, () => {
console.log(`HTTP ๋ฐ WebSocket ์๋ฒ๊ฐ http://localhost:${port} ์์ ์คํ ์ค์
๋๋ค.`);
console.log(`๋ธ๋ผ์ฐ์ ์์ http://localhost:${port}/index.html ์ ์ ์ํ์ธ์.`);
});
2. ํด๋ผ์ด์ธํธ ์ฝ๋ (HTML + JavaScript)public/index.html (๊ธฐ์กด ํ์ผ์ WebSocket ์น์
์ถ๊ฐ)
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>์น ์ค์๊ฐ ํต์ ์์ </title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f4f4f4; color: #333; }
h1, h2 { color: #0056b3; margin-top: 30px;}
.section-container {
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 30px;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
border-radius: 8px;
}
#message-container-polling, #message-container-long-polling, #message-container-sse, #message-container-websocket {
border: 1px solid #eee;
padding: 15px;
min-height: 150px;
background-color: #f9f9f9;
overflow-y: auto;
max-height: 400px;
margin-top: 15px;
border-radius: 5px;
}
.message-item {
padding: 8px 0;
border-bottom: 1px dashed #eee;
}
.message-item:last-child {
border-bottom: none;
}
.message-item.info { color: #555; font-style: italic; }
.message-item.received { color: #007bff; font-weight: bold; }
.message-item.sent { color: #28a745; }
.message-item.error { color: #dc3545; }
button {
padding: 10px 15px;
font-size: 16px;
cursor: pointer;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
margin-top: 10px;
margin-right: 10px;
}
button:hover {
background-color: #0056b3;
}
input[type="text"] {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 16px;
}
</style>
</head>
<body>
<h1>์น ์ค์๊ฐ ํต์ ์์ </h1>
<p>๋ค์ํ ์น ์ค์๊ฐ ํต์ ๋ฐฉ๋ฒ๋ค์ ๋์์ ๋น๊ตํด๋ด
๋๋ค.</p>
<!-- Polling Section -->
<div class="section-container">
<h2>Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ <strong>1์ด๋ง๋ค</strong> ์๋ฒ์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๋์ง ์์ฒญํฉ๋๋ค.</p>
<button id="togglePolling">Polling ์์/์ค์ง</button>
<div id="message-container-polling">
<p>์๋ฒ ๋ฉ์์ง (Polling):</p>
</div>
</div>
<!-- Long Polling Section -->
<div class="section-container">
<h2>Long Polling ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ๋ฉ์์ง๊ฐ ๋์ฐฉํ ๋๊น์ง ๋๊ธฐํ๊ณ , ๋์ฐฉํ๋ฉด ์ฆ์ ์๋ต์ ๋ฐ์ต๋๋ค. ์๋ต์ ๋ฐ์ผ๋ฉด ๋ฐ๋ก ๋ค์ ์์ฒญ์ ๋ณด๋
๋๋ค.</p>
<button id="toggleLongPolling">Long Polling ์์/์ค์ง</button>
<div id="message-container-long-polling">
<p>์๋ฒ ๋ฉ์์ง (Long Polling):</p>
</div>
</div>
<!-- SSE Section -->
<div class="section-container">
<h2>SSE (Server-Sent Events) ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ๋ก๋ถํฐ ๋จ๋ฐฉํฅ ์คํธ๋ฆผ์ผ๋ก ์ด๋ฒคํธ๋ฅผ ์์ ํฉ๋๋ค.</p>
<button id="toggleSSE">SSE ์์/์ค์ง</button>
<div id="message-container-sse">
<p>์๋ฒ ๋ฉ์์ง (SSE):</p>
</div>
</div>
<!-- WebSocket Section -->
<div class="section-container">
<h2>WebSocket ์์ </h2>
<p>์ด ํ์ด์ง๋ ์๋ฒ์ ์๋ฐฉํฅ ์ ์ด์ค ํต์ ์ ์ค์ ํ๊ณ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ต๋๋ค.</p>
<input type="text" id="websocketInput" placeholder="์๋ฒ๋ก ๋ณด๋ผ ๋ฉ์์ง" style="padding: 8px; width: 250px; margin-right: 10px;">
<button id="sendWebSocketMessage">๋ฉ์์ง ์ ์ก</button>
<button id="toggleWebSocket">WebSocket ์ฐ๊ฒฐ/ํด์ </button>
<div id="message-container-websocket">
<p>์๋ฒ ๋ฉ์์ง (WebSocket):</p>
</div>
</div>
<script>
// --- ๊ณตํต ๋ฉ์์ง ์ถ๋ ฅ ํจ์ ---
function addMessageToDisplay(containerId, msg, type = 'info') {
const container = document.getElementById(containerId);
if (!container) return;
const p = document.createElement('p');
p.className = `message-item ${type}`;
p.textContent = msg;
container.appendChild(p);
container.scrollTop = container.scrollHeight;
}
// --- Polling Logic (์ด์ ๊ณผ ๋์ผ) ---
const pollingMessageContainer = document.getElementById('message-container-polling');
const togglePollingButton = document.getElementById('togglePolling');
let pollingIntervalId = null;
let isPollingActive = false;
async function pollMessages() {
try {
const response = await fetch('/messages');
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Polling):', msg);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling: ์๋ก์ด ๋ฉ์์ง๊ฐ ์์ต๋๋ค.`, 'info');
}
} catch (error) {
console.error('Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค๋ฅ: ${error.message}`, 'error');
}
}
function startPolling() {
if (!isPollingActive) {
isPollingActive = true;
pollingIntervalId = setInterval(pollMessages, 1000);
togglePollingButton.textContent = 'Polling ์ค์ง';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์์.`, 'info');
}
}
function stopPolling() {
if (isPollingActive) {
isPollingActive = false;
clearInterval(pollingIntervalId);
pollingIntervalId = null;
togglePollingButton.textContent = 'Polling ์์';
addMessageToDisplay('message-container-polling', `[${new Date().toLocaleTimeString()}] Polling ์ค์ง.`, 'info');
}
}
togglePollingButton.addEventListener('click', () => {
if (isPollingActive) {
stopPolling();
} else {
startPolling();
}
});
// --- Long Polling Logic (์ด์ ๊ณผ ๋์ผ) ---
const longPollingMessageContainer = document.getElementById('message-container-long-polling');
const toggleLongPollingButton = document.getElementById('toggleLongPolling');
let isLongPollingActive = false;
let longPollingAbortController = null;
async function requestLongPolling() {
if (!isLongPollingActive) return;
longPollingAbortController = new AbortController();
const signal = longPollingAbortController.signal;
try {
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ์๋ฒ์ ๋กฑ ํด๋ง ์์ฒญ ์ค...`, 'info');
const response = await fetch('/long-polling-messages', { signal });
const data = await response.json();
if (data.newMessages && data.newMessages.length > 0) {
data.newMessages.forEach(msg => {
console.log('์๋ก์ด ๋ฉ์์ง ์์ (Long Polling):', msg);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] ${msg}`, 'received');
});
} else {
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์๋ฒ์์ ์๋ก์ด ๋ฉ์์ง๊ฐ ์๊ฑฐ๋ ํ์์์.`, 'info');
}
} catch (error) {
if (error.name === 'AbortError') {
console.log('๋กฑ ํด๋ง ์์ฒญ์ด ์ทจ์๋์์ต๋๋ค.');
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling: ์์ฒญ์ด ์ค์ง๋์์ต๋๋ค.`, 'info');
} else {
console.error('Long Polling ์ค ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค๋ฅ: ${error.message}`, 'error');
}
} finally {
if (isLongPollingActive) {
requestLongPolling();
}
}
}
function startLongPolling() {
if (!isLongPollingActive) {
isLongPollingActive = true;
toggleLongPollingButton.textContent = 'Long Polling ์ค์ง';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์์.`, 'info');
requestLongPolling();
}
}
function stopLongPolling() {
if (isLongPollingActive) {
isLongPollingActive = false;
if (longPollingAbortController) {
longPollingAbortController.abort();
}
toggleLongPollingButton.textContent = 'Long Polling ์์';
addMessageToDisplay('message-container-long-polling', `[${new Date().toLocaleTimeString()}] Long Polling ์ค์ง.`, 'info');
}
}
toggleLongPollingButton.addEventListener('click', () => {
if (isLongPollingActive) {
stopLongPolling();
} else {
startLongPolling();
}
});
// --- SSE Logic (์ด์ ๊ณผ ๋์ผ) ---
const sseMessageContainer = document.getElementById('message-container-sse');
const toggleSSEButton = document.getElementById('toggleSSE');
let eventSource = null;
let isSSEActive = false;
function startSSE() {
if (!isSSEActive) {
isSSEActive = true;
eventSource = new EventSource('/sse-events');
eventSource.onopen = (event) => {
console.log('SSE ์ฐ๊ฒฐ์ด ์ด๋ ธ์ต๋๋ค.', event);
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ์ฐ๊ฒฐ์ด ์ด๋ ธ์ต๋๋ค.`, 'info');
};
eventSource.onmessage = (event) => {
try {
const messageData = JSON.parse(event.data);
console.log('์๋ก์ด ๋ฉ์์ง ์์ (SSE):', messageData.content);
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] ${messageData.content}`, 'received');
} catch (e) {
console.error('SSE ๋ฉ์์ง ํ์ฑ ์ค๋ฅ:', e, event.data);
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ๋ฉ์์ง ์ค๋ฅ: ${event.data}`, 'error');
}
};
eventSource.onerror = (event) => {
console.error('SSE ์ค๋ฅ ๋ฐ์:', event);
if (eventSource.readyState === EventSource.CLOSED) {
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ์ฐ๊ฒฐ์ด ๋ซํ์ต๋๋ค. ์๋์ผ๋ก ์ฌ์ฐ๊ฒฐ์ ์๋ํฉ๋๋ค.`, 'error');
} else {
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ์ค๋ฅ ๋ฐ์: ${event.message || '์ ์ ์์'}`, 'error');
}
};
toggleSSEButton.textContent = 'SSE ์ค์ง';
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ์์.`, 'info');
}
}
function stopSSE() {
if (isSSEActive && eventSource) {
isSSEActive = false;
eventSource.close();
eventSource = null;
toggleSSEButton.textContent = 'SSE ์์';
addMessageToDisplay('message-container-sse', `[${new Date().toLocaleTimeString()}] SSE ์ค์ง.`, 'info');
}
}
toggleSSEButton.addEventListener('click', () => {
if (isSSEActive) {
stopSSE();
} else {
startSSE();
}
});
// --- WebSocket Logic ---
const websocketMessageContainer = document.getElementById('message-container-websocket');
const websocketInput = document.getElementById('websocketInput');
const sendWebSocketMessageButton = document.getElementById('sendWebSocketMessage');
const toggleWebSocketButton = document.getElementById('toggleWebSocket');
let websocket = null;
let isWebSocketActive = false;
function connectWebSocket() {
if (!isWebSocketActive) {
isWebSocketActive = true;
// WebSocket ์ฐ๊ฒฐ
websocket = new WebSocket('ws://localhost:3000'); // ws:// ๋๋ wss:// ์ฌ์ฉ
websocket.onopen = (event) => {
console.log('WebSocket ์ฐ๊ฒฐ์ด ์ด๋ ธ์ต๋๋ค.', event);
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] WebSocket ์ฐ๊ฒฐ์ด ์ด๋ ธ์ต๋๋ค.`, 'info');
toggleWebSocketButton.textContent = 'WebSocket ์ฐ๊ฒฐ ํด์ ';
};
websocket.onmessage = (event) => {
// ์๋ฒ๋ก๋ถํฐ ๋ฉ์์ง ์์
console.log('์๋ก์ด ๋ฉ์์ง ์์ (WebSocket):', event.data);
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] ์๋ฒ๋ก๋ถํฐ: ${event.data}`, 'received');
};
websocket.onclose = (event) => {
console.log('WebSocket ์ฐ๊ฒฐ์ด ๋ซํ์ต๋๋ค.', event);
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] WebSocket ์ฐ๊ฒฐ์ด ๋ซํ์ต๋๋ค. (์ฝ๋: ${event.code}, ์ด์ : ${event.reason || '์์'})`, 'error');
isWebSocketActive = false;
toggleWebSocketButton.textContent = 'WebSocket ์ฐ๊ฒฐ';
};
websocket.onerror = (error) => {
console.error('WebSocket ์ค๋ฅ ๋ฐ์:', error);
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] WebSocket ์ค๋ฅ ๋ฐ์!`, 'error');
};
}
}
function disconnectWebSocket() {
if (isWebSocketActive && websocket) {
websocket.close(); // WebSocket ์ฐ๊ฒฐ ์ข
๋ฃ
websocket = null;
isWebSocketActive = false;
toggleWebSocketButton.textContent = 'WebSocket ์ฐ๊ฒฐ';
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] WebSocket ์ฐ๊ฒฐ ํด์ ์์ฒญ.`, 'info');
}
}
sendWebSocketMessageButton.addEventListener('click', () => {
if (websocket && websocket.readyState === WebSocket.OPEN) {
const message = websocketInput.value;
if (message) {
websocket.send(message); // ์๋ฒ๋ก ๋ฉ์์ง ์ ์ก
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] ๋: ${message}`, 'sent');
websocketInput.value = '';
}
} else {
addMessageToDisplay('message-container-websocket', `[${new Date().toLocaleTimeString()}] WebSocket์ด ์ฐ๊ฒฐ๋์ด ์์ง ์์ต๋๋ค.`, 'error');
}
});
toggleWebSocketButton.addEventListener('click', () => {
if (isWebSocketActive) {
disconnectWebSocket();
} else {
connectWebSocket();
}
});
// ํ์ด์ง ๋ก๋ ์ Polling ๋ฐ Long Polling์ ์๋์ผ๋ก ์์. SSE, WebSocket์ ์๋ ์์.
window.addEventListener('load', () => {
startPolling();
startLongPolling();
// connectWebSocket(); // WebSocket๋ ์๋ ์์์ผ๋ก ๋ณ๊ฒฝ
});
</script>
</body>
</html>
์คํ ๋ฐฉ๋ฒ:
ws๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ค์น๋์๋์ง ํ์ธํฉ๋๋ค:npm install ws.- ๊ธฐ์กด
server.js์public/index.htmlํ์ผ์ ์ ์ฝ๋๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. - ํฐ๋ฏธ๋์์
node server.js๋ฅผ ๋ค์ ์คํํฉ๋๋ค. - ์น ๋ธ๋ผ์ฐ์ ์์
http://localhost:3000/index.html๋ก ์ ์ํ ํ, "WebSocket ์ฐ๊ฒฐ/ํด์ " ๋ฒํผ์ ๋๋ฌ WebSocket ์ฐ๊ฒฐ์ ์์ํฉ๋๋ค. 5์ด๋ง๋ค ์๋ฒ์์ ํธ์๋๋ ๋ฉ์์ง๋ฅผ ๋ฐ์ ์ ์์ผ๋ฉฐ, ์ ๋ ฅ ํ๋์ ๋ฉ์์ง๋ฅผ ์ ๋ ฅํ๊ณ ์ ์ก ๋ฒํผ์ ๋๋ฅด๋ฉด ์๋ฒ์ ๋ค๋ฅธ ํด๋ผ์ด์ธํธ์๊ฒ ๋ฉ์์ง๊ฐ ์ ๋ฌ๋๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ ๊ฐ์ ๋ธ๋ผ์ฐ์ ํญ์ ์ด์ด ์๋ก ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ๋ ์ฑํ ์ฒ๋ผ ๋์ํ๋ ๊ฒ์ ์ง์ ๊ฒฝํํด ๋ณด์ธ์.
์น์์ผ์ ํ์ฌ๊น์ง ์น์์ ๊ฐ์ฅ ๊ฐ๋ ฅํ๊ณ ์ ์ฐํ ์ค์๊ฐ ์๋ฐฉํฅ ํต์ ์๋จ์ผ๋ก ์ธ์ ๋ฐ๊ณ ์์ต๋๋ค. ํ์ง๋ง ๋ชจ๋ ์ํฉ์ ์น์์ผ์ด ์ต์ ์ ์ ํ์ ์๋๋ฉฐ, ๊ฐ ๊ธฐ์ ์ ์ฅ๋จ์ ์ ๊ณ ๋ คํ์ฌ ์ ์ ํ ๋ฐฉ๋ฒ์ ์ ํํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๋ค์ ์น์ ์์๋ ์ด ๋ค ๊ฐ์ง ์ค์๊ฐ ํต์ ๋ฐฉ์์ ์ข ํฉ์ ์ผ๋ก ๋น๊ต ๋ถ์ํ์ฌ ์ด๋ค ์ํฉ์์ ์ด๋ค ๊ธฐ์ ์ ์ฌ์ฉํด์ผ ํ ์ง ๊ฐ์ด๋๋ฅผ ์ ์ํฉ๋๋ค.
โ๏ธ ์ด๋ค ๋ฐฉ๋ฒ์ ์ ํํด์ผ ํ ๊น์? 4๊ฐ์ง ์ค์๊ฐ ํต์ ๋ฐฉ์ ๋น๊ต ๋ฐ ์์ฝ
์ง๊ธ๊น์ง Polling, Long Polling, SSE, WebSocket์ด๋ผ๋ ๋ค ๊ฐ์ง ์น ์ค์๊ฐ ํต์ ๋ฐฉ์์ ์์ธํ ์ดํด๋ณด์์ต๋๋ค. ๊ฐ ๋ฐฉ์์ ๊ณ ์ ํ ์๋ ์๋ฆฌ์ ์ฅ๋จ์ ์ ๊ฐ์ง๋ฉฐ, ํน์ ์ฌ์ฉ ์๋๋ฆฌ์ค์ ๋ ์ ํฉํฉ๋๋ค. ์ฌ๋ฐ๋ฅธ ๊ธฐ์ ์ ํ์ ์๋น์ค์ ์ฑ๋ฅ, ํ์ฅ์ฑ, ๊ทธ๋ฆฌ๊ณ ๊ฐ๋ฐ ๋ฐ ์ ์ง๋ณด์ ๋น์ฉ์ ์ง์ ์ ์ธ ์ํฅ์ ๋ฏธ์น๋ฏ๋ก ๋งค์ฐ ์ค์ํฉ๋๋ค.
๋ค์ ํ๋ ๋ค ๊ฐ์ง ๋ฐฉ์์ ํต์ฌ์ ์ธ ์ธก๋ฉด์์ ๋น๊ตํ ๊ฒ์ ๋๋ค.
| ํน์ง/๋ฐฉ์ | Polling (ํด๋ง) | Long Polling (๋กฑ ํด๋ง) | SSE (Server-Sent Events) | WebSocket (์น์์ผ) |
|---|---|---|---|---|
| ํต์ ๋ฐฉํฅ | ๋จ๋ฐฉํฅ (ํด๋ผ์ด์ธํธ -> ์๋ฒ) | ๋จ๋ฐฉํฅ (ํด๋ผ์ด์ธํธ -> ์๋ฒ) | ๋จ๋ฐฉํฅ (์๋ฒ -> ํด๋ผ์ด์ธํธ) | ์๋ฐฉํฅ (ํด๋ผ์ด์ธํธ <-> ์๋ฒ) |
| ํ๋กํ ์ฝ | HTTP (๋ฐ๋ณต ์์ฒญ) | HTTP (์๋ต ๋๊ธฐ ํ ์ฌ์์ฒญ) | HTTP (text/event-stream) | ์น์์ผ ํ๋กํ ์ฝ (HTTP ํธ๋์ ฐ์ดํฌ ํ) |
| ์ฐ๊ฒฐ ๋ฐฉ์ | ๋งค ์์ฒญ๋ง๋ค ์๋ก์ด HTTP ์์ฒญ¹ | ์๋ต ๋๊ธฐ ๋์ HTTP ์์ฒญ ์ ์ง, ์๋ต ํ ์ ์์ฒญ | ๋จ์ผ ์ง์ ์ฐ๊ฒฐ (HTTP/1.1) | ๋จ์ผ ์๊ตฌ์ ์ธ TCP ์ฐ๊ฒฐ |
| ์ค์๊ฐ์ฑ | ๋ฎ์ (์์ฒญ ์ฃผ๊ธฐ์ ๋ฐ๋ผ ์ง์ฐ) | ์ค๊ฐ (๋ฐ์ดํฐ ๋ฐ์ ์ ์ฆ์ ์ ๋ฌ) | ๋์ (๋ฐ์ดํฐ ๋ฐ์ ์ ์ฆ์ ํธ์) | ๋งค์ฐ ๋์ (๊ฑฐ์ ์ค์๊ฐ) |
| ์ค๋ฒํค๋ | ๋์ (๋น๋ฒํ HTTP ํค๋) | ์ค๊ฐ (๋ฐ์ดํฐ ์์ ๋ ์์ฒญ ์ ์ง) | ๋ฎ์ (๊ฐ๋จํ ํ ์คํธ ์ด๋ฒคํธ ์ ์ก) | ๋งค์ฐ ๋ฎ์ (์ด๊ธฐ ํธ๋์ ฐ์ดํฌ ํ) |
| ์๋ฒ ๋ถํ | ๋์ (์ ๋ฐ์ดํฐ ์์ด๋ ์์ฒญ ์ฒ๋ฆฌ) | ์ค๊ฐ (๋ง์ ๋๊ธฐ ์ฐ๊ฒฐ ์ ๋ฆฌ์์ค ์๋ชจ) | ๋ฎ์ (๋๊ท๋ชจ ๋ธ๋ก๋์บ์คํ ์ ํจ์จ์ ) | ์ค๊ฐ~๋์ (๋ง์ ์ฐ๊ฒฐ ์ ์ง ๋ฐ ๊ด๋ฆฌ) |
| ๋ธ๋ผ์ฐ์ ์ง์ | ๋ชจ๋ ๋ธ๋ผ์ฐ์ | ๋ชจ๋ ๋ธ๋ผ์ฐ์ | ๋๋ถ๋ถ์ ํ๋ ๋ธ๋ผ์ฐ์ | ๋๋ถ๋ถ์ ํ๋ ๋ธ๋ผ์ฐ์ |
| ๊ตฌํ ๋์ด๋ | ๋งค์ฐ ์ฌ์ | ์ฌ์~์ค๊ฐ (ํ์์์, ์ฌ์ฐ๊ฒฐ ๊ด๋ฆฌ) | ์ฌ์ (EventSource API ์ฌ์ฉ) | ์ค๊ฐ~๋์ (์๋ฒ/ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ) |
| ๋ฐ์ดํฐ ํ์ | ํ ์คํธ (JSON, XML ๋ฑ) | ํ ์คํธ (JSON, XML ๋ฑ) | ํ ์คํธ (UTF-8) | ํ ์คํธ, ๋ฐ์ด๋๋ฆฌ |
| ์๋ ์ฌ์ฐ๊ฒฐ | ์์ | ์ง์ ๊ตฌํ ํ์ | ๊ธฐ๋ณธ ์ง์ | ์ง์ ๊ตฌํ ํ์ (๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ๊ถ์ฅ) |
¹ HTTP Keep-Alive ๋๋ถ์ TCP ์ฐ๊ฒฐ ์์ฒด๋ ์ฌ์ฌ์ฉ๋ ์ ์์ผ๋, ๋งค๋ฒ ์์ ํ HTTP ์์ฒญ/์๋ต ํค๋ ๊ตํ์ด ๋ฐ์ํฉ๋๋ค.
๊ฐ ๋ฐฉ์์ ํน์ง ๋ฐ ์ ํฉํ ์ฌ์ฉ ํ๊ฒฝ
1. Polling (ํด๋ง)
- ํต์ฌ: ํด๋ผ์ด์ธํธ๊ฐ ์ ํด์ง ์๊ฐ๋ง๋ค ์๋ฒ์ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๋์ง "๋ฌผ์ด๋ณด๋" ๋ฐฉ์.
- ์ฅ์ : ๊ตฌํ์ด ๊ฐ์ฅ ๊ฐ๋จํ๊ณ , ๋ชจ๋ ์น ํ๊ฒฝ์์ ํธํ๋ฉ๋๋ค.
- ๋จ์ : ์ค์๊ฐ์ฑ์ด ๋จ์ด์ง๊ณ , ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ด๋ ๊ณ์ ์์ฒญ์ ๋ณด๋ด๋ฏ๋ก ๋คํธ์ํฌ ํธ๋ํฝ๊ณผ ์๋ฒ ๋ถํ๊ฐ ๋์ต๋๋ค.
- ์ ํฉํ ์๋๋ฆฌ์ค:
- ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ ์ฃผ๊ธฐ๊ฐ ๊ธธ์ด๋ ๊ด์ฐฎ์ ๊ฒฝ์ฐ (์: ๋ ์จ ์ ๋ณด, ๋ด์ค ํค๋๋ผ์ธ ์ ๋ฐ์ดํธ)
- ๋งค์ฐ ์ ์ ํด๋ผ์ด์ธํธ ์ ๋๋ ๋ฎ์ ๋น๋๋ก ์ ๋ฐ์ดํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ
- ๊ตฌํ ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ด ๋งค์ฐ ์ค์ํ ๊ฒฝ์ฐ (ํ๋ ์๋น์ค์์๋ ๋๋ฌพ)
2. Long Polling (๋กฑ ํด๋ง)
- ํต์ฌ: ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ฉด, ์๋ฒ๋ ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์๊ธธ ๋๊น์ง ์๋ต์ '๋๊ธฐ'ํ๋ค๊ฐ ๋ฐ์ดํฐ๊ฐ ์๊ธฐ๋ฉด ์๋ตํ๊ณ , ํด๋ผ์ด์ธํธ๋ ์ฆ์ ์ฌ์์ฒญํ๋ ๋ฐฉ์.
- ์ฅ์ : ํด๋ง๋ณด๋ค ์ค์๊ฐ์ฑ์ด ๋ฐ์ด๋๊ณ , ์๋ก์ด ๋ฐ์ดํฐ๊ฐ ์์ ๋ ๋ถํ์ํ ์์ฒญ์ด ์ค์ด๋ญ๋๋ค.
- ๋จ์ : ์๋ฒ๊ฐ ๋๊ธฐ ์ค์ธ ์ฐ๊ฒฐ์ ๋ง์ด ์ ์งํด์ผ ํ๋ฏ๋ก ๋์ ์ ์์ ์๊ฐ ๋ง์์ง์๋ก ์๋ฒ ๋ถํ๊ฐ ์ปค์ง ์ ์์ต๋๋ค. ํ์์์ ๋ฐ ์ฌ์ฐ๊ฒฐ ๋ก์ง ๊ตฌํ์ด ๋ณต์กํ ์ ์์ต๋๋ค.
- ์ ํฉํ ์๋๋ฆฌ์ค:
- ์ฑํ , ์๋ฆผ ๋ฑ ์ค์๊ฐ์ฑ์ด ํ์ํ์ง๋ง, ๋ฉ์์ง ๋ฐ์ ๋น๋๊ฐ ์์ฃผ ๋์ง ์์ ๊ฒฝ์ฐ
- ์น์์ผ์ ์ฌ์ฉํ ์ ์๋ ์ ํ์ ์ธ ํ๊ฒฝ์์ ์๋ฒ ํธ์๋ฅผ ์๋ฎฌ๋ ์ดํธํด์ผ ํ ๋ (์ค๋๋ ๋ฐฉ์)
3. SSE (Server-Sent Events)
- ํต์ฌ: ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ํ ๋ฒ ์ฐ๊ฒฐํ๋ฉด, ์๋ฒ๊ฐ ์ด ์ฐ๊ฒฐ์ ํตํด ํด๋ผ์ด์ธํธ์๊ฒ ๋จ๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ง์์ ์ผ๋ก 'ํธ์'ํ๋ ๋ฐฉ์.
- ์ฅ์ : ์๋ฒ ํธ์ ๋ฐฉ์ ์ค ๊ตฌํ์ด ๋งค์ฐ ๊ฐ๋จํ๊ณ , ๋ธ๋ผ์ฐ์ ์์ ์๋ ์ฌ์ฐ๊ฒฐ์ ์ง์ํ์ฌ ์์ ์ ์ ๋๋ค. HTTP ํ๋กํ ์ฝ ๊ธฐ๋ฐ์ด๋ฏ๋ก ๋ฐฉํ๋ฒฝ ์นํ์ ์ ๋๋ค.
- ๋จ์ : ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก ๋ฐ์ดํฐ๋ฅผ ๋ณด๋ผ ์ ์๋ ๋จ๋ฐฉํฅ ํต์ ์ด๋ฉฐ, ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ ์ ์ก์ด ์ด๋ ต์ต๋๋ค.
- ์ ํฉํ ์๋๋ฆฌ์ค:
- ์ค์๊ฐ ๋ด์ค ํผ๋, ์ฃผ์ ์์ธ, ์คํฌ์ธ ์ค์ฝ์ด, ์๋ฆผ, ๋์๋ณด๋ ์ ๋ฐ์ดํธ ๋ฑ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก์ ์ผ๋ฐฉ์ ์ธ ๋ฐ์ดํฐ ์คํธ๋ฆผ์ด ํ์ํ ๊ฒฝ์ฐ.
- ์ฑํ ์ฑ์์ '๋๊ฐ ํ์ดํ ์ค์ด๋ค'์ ๊ฐ์ ์ํ ์ ๋ณด ์๋ฆผ (๋ฉ์์ง ์ ์ก์ ๋ณ๋์ HTTP POST ์ฌ์ฉ)
4. WebSocket (์น์์ผ)
- ํต์ฌ: HTTP ํธ๋์ ฐ์ดํฌ๋ฅผ ํตํด ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ํ๋์ ์๊ตฌ์ ์ธ TCP ์ฐ๊ฒฐ์ ์ค์ ํ๊ณ , ์ด ์ฐ๊ฒฐ์ ํตํด ์๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์์ ๋กญ๊ฒ ์ฃผ๊ณ ๋ฐ๋ ์ ์ด์ค ํต์ ๋ฐฉ์.
- ์ฅ์ : ์ง์ ํ ์๋ฐฉํฅ ์ ์ด์ค ํต์ ์ ์ ๊ณตํ๋ฉฐ, ๋งค์ฐ ๋ฎ์ ์ง์ฐ ์๊ฐ๊ณผ ์ ์ ์ค๋ฒํค๋๋ก ํจ์จ์ ์ธ ์ค์๊ฐ ์ํธ์์ฉ์ด ๊ฐ๋ฅํฉ๋๋ค. ํ ์คํธ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ง์ํฉ๋๋ค.
- ๋จ์ : ๊ตฌํ ๋ณต์ก๋๊ฐ ์๋์ ์ผ๋ก ๋๊ณ , ๋ง์ ๋์ ์ฐ๊ฒฐ ์ ์๋ฒ ๋ฆฌ์์ค ๊ด๋ฆฌ๊ฐ ์ค์ํฉ๋๋ค. ์๋ ์ฌ์ฐ๊ฒฐ ๋ฑ ์ถ๊ฐ ๋ก์ง ๊ตฌํ์ด ํ์ํ ์ ์์ต๋๋ค (Socket.IO ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ํด๊ฒฐ ๊ฐ๋ฅ).
- ์ ํฉํ ์๋๋ฆฌ์ค:
- ์ค์๊ฐ ์ฑํ , ์จ๋ผ์ธ ๋ฉํฐํ๋ ์ด์ด ๊ฒ์, ํ์ ๋ฌธ์ ํธ์ง, ํ์/์์ฑ ํตํ ์๊ทธ๋๋ง ๋ฑ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ํ๋ฐํ ์๋ฐฉํฅ ์ํธ์์ฉ์ด ํ์์ ์ธ ๊ฒฝ์ฐ.
- ๋ฎ์ ์ง์ฐ ์๊ฐ์ผ๋ก ๋ง์ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ์ฃผ๊ณ ๋ฐ์์ผ ํ๋ ๊ฒฝ์ฐ.
๊ฒฐ๋ก ์ ์ผ๋ก, ์ด๋ค ๊ธฐ์ ์ ์ ํํ ์ง๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ํต์ฌ ์๊ตฌ์ฌํญ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋๋ค.
- ๋จ์ํ ์๋ฆผ์ด๋ ์ ๋ณด์ฑ ์ ๋ฐ์ดํธ ๋ฑ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก์ ์ผ๋ฐฉํฅ ํธ์๋ง ํ์ํ๋ค๋ฉด: SSE๊ฐ ๊ฐ์ฅ ํจ์จ์ ์ด๊ณ ๊ตฌํ์ด ๊ฐ๋จํ ์ ํ์ ๋๋ค.
- ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ์๋ก ํ๋ฐํ๊ฒ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์์ผ ํ๋ ๋ณต์กํ ์ค์๊ฐ ์ํธ์์ฉ์ด ํ์ํ๋ค๋ฉด: WebSocket์ด ๊ฐ์ฅ ๊ฐ๋ ฅํ๊ณ ์ ํฉํ ์๋ฃจ์ ์ ๋๋ค.
- ์ค์๊ฐ์ฑ์ด ํฌ๊ฒ ์ค์ํ์ง ์๊ฑฐ๋ ๋งค์ฐ ์ ํ์ ์ธ ํ๊ฒฝ์ด๋ผ๋ฉด: Polling์ ๊ณ ๋ คํ ์ ์์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก ๊ถ์ฅ๋์ง๋ ์์ต๋๋ค.
- SSE๋ WebSocket์ ์ฌ์ฉํ ์ ์๋ ํ๊ฒฝ์ด๊ฑฐ๋, ๊ธฐ์กด ์์คํ ๊ณผ์ ํธํ์ฑ์ด ์ค์ํ๋ค๋ฉด: Long Polling์ด ๋์์ด ๋ ์ ์์ง๋ง, ์๋ฒ ๋ฆฌ์์ค ๊ด๋ฆฌ์ ์ฃผ์ํด์ผ ํฉ๋๋ค.
ํ๋์ ์น ๊ฐ๋ฐ์์๋ ์ผ๋ฐ์ ์ผ๋ก SSE ๋๋ WebSocket์ด ์ฃผ๋ก ์ฌ์ฉ๋๋ฉฐ, ๋ ๊ฐ์ง ๋ชจ๋ ์ง์์ด ํ์ํ ๊ฒฝ์ฐ Socket.IO์ ๊ฐ์ ์ถ์ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํตํด ํจ์จ์ ์ผ๋ก ๊ตฌํํ ์ ์์ต๋๋ค. ๊ฐ ๊ธฐ์ ์ ๊ฐ์ ์ ์ ํํ ์ดํดํ๊ณ ์๋น์ค์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ ํ๋ช ํ ์ ํ์ ํ์๊ธธ ๋ฐ๋๋๋ค.
๐ฎ ๋ง๋ฌด๋ฆฌํ๋ฉฐ: ์น ์ค์๊ฐ ํต์ ๊ธฐ์ ์ ๋ฏธ๋์ ์ฐ๋ฆฌ์ ์ญํ
์ฐ๋ฆฌ๋ ์ด ๊ธ์ ํตํด ์น ์๋น์ค์ ํต์ฌ์ ์ธ ์๊ตฌ์ฌํญ ์ค ํ๋์ธ '์ค์๊ฐ ํต์ '์ ๊ตฌํํ๊ธฐ ์ํ ๋ค ๊ฐ์ง ์ฃผ์ ๊ธฐ์ ์ธ Polling, Long Polling, SSE, ๊ทธ๋ฆฌ๊ณ WebSocket์ ์ฌ์ธต์ ์ผ๋ก ํ๊ตฌํ์ต๋๋ค. ๊ฐ ๊ธฐ์ ์ด ์น์ ์์ฒญ-์๋ต ํ๊ณ๋ฅผ ์ด๋ป๊ฒ ๊ทน๋ณตํ๊ณ , ์ด๋ค ๋ฐฉ์์ผ๋ก ์ค์๊ฐ์ฑ์ ์ ๊ณตํ๋ฉฐ, ๊ฐ๊ฐ์ ์ฅ๋จ์ ๊ณผ ์ต์ ์ ์ฌ์ฉ ์๋๋ฆฌ์ค๊ฐ ๋ฌด์์ธ์ง ์์ธํ ์ดํด๋ณด์์ต๋๋ค.
์น ๊ฐ๋ฐ์ ์ญ์ฌ๋ ๋ ๋น ๋ฅด๊ณ , ๋ ํจ์จ์ ์ด๋ฉฐ, ๋ ํ๋ถํ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํ ๊ธฐ์ ๋ฐ์ ์ ์ฐ์์ด์์ต๋๋ค. ์ ํต์ ์ธ HTTP ํต์ ๋ชจ๋ธ์ด ๊ฐ์ก๋ ์ ์ฝ์ฌํญ์ ์ธ์งํ๊ณ , ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Polling์์ Long Polling์ผ๋ก, ๊ทธ๋ฆฌ๊ณ Server-Sent Events์ WebSocket์ด๋ผ๋ ๋์ฑ ์ง๋ณด๋ ํ๋กํ ์ฝ๋ก ๋ฐ์ ํด ์์ต๋๋ค. ์ด ๊ธฐ์ ๋ค์ ๋จ์ํ ๋ฐ์ดํฐ๋ฅผ ๋น ๋ฅด๊ฒ ์ฃผ๊ณ ๋ฐ๋ ๊ฒ์ ๋์ด, ์ฌ์ฉ์๋ค์๊ฒ ๋๊น ์๋ ์ํธ์์ฉ๊ณผ ์ฆ๊ฐ์ ์ธ ํผ๋๋ฐฑ์ ์ ๊ณตํ์ฌ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฅ์ฑ์ ํ์ธต ๋ ํ์ฅ์์ผฐ์ต๋๋ค.
์น ์ค์๊ฐ ํต์ ๊ธฐ์ ์ ๋ฐ์ ๋ฐฉํฅ
ํ์ฌ ์น ์ค์๊ฐ ํต์ ์ WebSocket์ ์ค์ฌ์ผ๋ก ํ๋ฐํ๊ฒ ์ฌ์ฉ๋๊ณ ์์ผ๋ฉฐ, ๊ทธ ์์์ ๋ค์ํ ๊ณ ์์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ(์: Socket.IO, SignalR ๋ฑ)๊ฐ ๊ฐ๋ฐ์์ ํธ์์ฑ์ ๋์ฌ์ฃผ๊ณ ์์ต๋๋ค. ์ด์ ๋๋ถ์ด, ๋ช ๊ฐ์ง ํฅ๋ฏธ๋ก์ด ๊ธฐ์ ๋ฐ์ ๋ฐฉํฅ์ ์ง์ด๋ณผ ์ ์์ต๋๋ค.
- WebRTC (Web Real-Time Communication)์์ ์๋์ง: ์น์์ผ์ด ๋ฉ์์ง๊ณผ ๋ฐ์ดํฐ ๋๊ธฐํ์ ๊ฐ์ ์ ๊ฐ์ง๋ค๋ฉด, WebRTC๋ ๋ธ๋ผ์ฐ์ ๊ฐ์ ์ง์ ์ ์ธ ์ค์๊ฐ ์์ฑ, ์์ ํต์ ์ ํนํ๋์ด ์์ต๋๋ค. ๋ ๊ธฐ์ ์ ์ํธ ๋ณด์์ ์ผ๋ก ์ฌ์ฉ๋ ์ ์์ผ๋ฉฐ, ์น์์ผ์ WebRTC ์ฐ๊ฒฐ์ ์ํ ์๊ทธ๋๋ง(์ฐ๊ฒฐ ์ค์ ์ ๋ณด ๊ตํ) ์ฑ๋๋ก ํ์ฉ๋๊ธฐ๋ ํฉ๋๋ค. ๋ง์ ์ค์๊ฐ ๋ฏธ๋์ด ์๋น์ค์์ ๋ ๊ธฐ์ ์ ๊ฒฐํฉ์ด ์ค์ํ๊ฒ ํ์ฉ๋๊ณ ์์ต๋๋ค.
- Serverless & Edge Computing๊ณผ์ ํตํฉ: ํด๋ผ์ฐ๋ ์ปดํจํ ์ ๋ฐ์ ๊ณผ ํจ๊ป ์๋ฒ๋ฆฌ์ค(Serverless) ์ํคํ ์ฒ์ ์ฃ์ง ์ปดํจํ (Edge Computing)์ ์ค์์ฑ์ด ์ปค์ง๊ณ ์์ต๋๋ค. AWS Lambda, Azure Functions, Google Cloud Functions์ ๊ฐ์ ์๋ฒ๋ฆฌ์ค ํ๋ซํผ์์ WebSocket API Gateway๋ฅผ ํตํด ์ค์๊ฐ ํต์ ์ ๊ตฌํํ๋ ์ฌ๋ก๊ฐ ๋๊ณ ์์ต๋๋ค. ์ด๋ ๊ฐ๋ฐ์๊ฐ ์๋ฒ ์ธํ๋ผ ๊ด๋ฆฌ์ ๋ํ ๋ถ๋ด ์์ด ํ์ฅ ๊ฐ๋ฅํ ์ค์๊ฐ ์๋น์ค๋ฅผ ๊ตฌ์ถํ ์ ์๊ฒ ํด์ค๋๋ค. ์ฃ์ง ์ปดํจํ ์ ์ฌ์ฉ์์๊ฒ ๋ ๊ฐ๊น์ด ๊ณณ์์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํจ์ผ๋ก์จ ์ง์ฐ ์๊ฐ์ ๋์ฑ ๋จ์ถ์ํค๋ ์ ์ฌ๋ ฅ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
- GraphQL Subscriptions์ ๋ถ์: GraphQL์ API๋ฅผ ์ํ ์ฟผ๋ฆฌ ์ธ์ด๋ก, ์ต๊ทผ์๋
Subscriptions๊ธฐ๋ฅ์ ํตํด ์ค์๊ฐ ๋ฐ์ดํฐ ์ ๋ฐ์ดํธ๋ฅผ ์ง์ํฉ๋๋ค. ์ด๋ ์น์์ผ ์์์ ๊ตฌํ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ผ๋ฉฐ, ํด๋ผ์ด์ธํธ๊ฐ ํน์ ์ด๋ฒคํธ ๋ฐ์ ์ ์๋์ผ๋ก ์ ๋ฐ์ดํธ๋ฅผ ๋ฐ์ ์ ์๋๋ก ํฉ๋๋ค. REST API๊ฐ ๊ฐ์ง ์ฌ๋ฌ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๋ฉฐ, ์ ๊ตํ ๋ฐ์ดํฐ ํํฐ๋ง์ ํตํด ํ์ํ ๋ฐ์ดํฐ๋ง ์ค์๊ฐ์ผ๋ก ๋ฐ์๋ณผ ์ ์๋ค๋ ์ฅ์ ์ด ์์ต๋๋ค. - ๋ณด์ ๋ฐ ์ธ์ฆ ๊ฐํ: ์ค์๊ฐ ํต์ ์ ์
์์ ์ธ ๊ณต๊ฒฉ์ ์ทจ์ฝํ ์ ์์ผ๋ฏ๋ก, ๋ณด์๊ณผ ์ธ์ฆ์ ํญ์ ์ค์ํ ๊ณ ๋ ค์ฌํญ์
๋๋ค. TLS(Transport Layer Security)๋ฅผ ํตํ
wss://์ฌ์ฉ์ ๊ธฐ๋ณธ์ด๋ฉฐ, JWT(JSON Web Tokens)์ ๊ฐ์ ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ, ์ธ์ ๊ด๋ฆฌ ๋ฑ ๋ค์ํ ๋ณด์ ๋ฉ์ปค๋์ฆ์ด ์ค์ํ๊ฒ ์ ์ฉ๋์ด์ผ ํฉ๋๋ค.
์น ๊ฐ๋ฐ ํธ๋ ๋์ ๋ํ ์ธ์ฌ์ดํธ
์ค๋๋ ์ฌ์ฉ์๋ค์ ๋จ์ํ ๊ธฐ๋ฅ์ด ์๋ํ๋ ๊ฒ์ ๋์ด, '์ฆ๊ฐ์ ์ธ ๋ฐ์'๊ณผ '๋๊น ์๋ ๊ฒฝํ'์ ๊ธฐ๋ํฉ๋๋ค. ์ด๋ฌํ ๊ธฐ๋์น๋ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ณธ ์๊ตฌ์ฌํญ์ด ๋๊ณ ์์ผ๋ฉฐ, ์ค์๊ฐ ํต์ ๊ธฐ์ ์ ์ด๋ฌํ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ ํต์ฌ ์์์ ๋๋ค. ๊ฐ๋ฐ์๋ก์ ์ด ๊ธฐ์ ๋ค์ ์ดํดํ๊ณ ์ ์ฌ์ ์์ ํ์ฉํ๋ ๋ฅ๋ ฅ์ ํ๋ ์น ์๋น์ค๋ฅผ ๊ตฌ์ถํ๋ ๋ฐ ์์ด ํ์์ ์ธ ์ญ๋์ ๋๋ค.
์ด์ ์ฌ๋ฌ๋ถ์ Polling๋ถํฐ WebSocket์ ์ด๋ฅด๊ธฐ๊น์ง ๊ฐ ์ค์๊ฐ ํต์ ๋ฐฉ์์ ์ฅ๋จ์ ๊ณผ ํ์ฉ๋ฒ์ ๋ช ํํ๊ฒ ํ์ ํ์ จ์ ๊ฒ์ ๋๋ค. ์ด ์ง์์ ๋ฐํ์ผ๋ก ์ฌ๋ฌ๋ถ์ ์๋น์ค์ ๊ฐ์ฅ ์ ํฉํ ์ค์๊ฐ ํต์ ์๋ฃจ์ ์ ์ ํํ๊ณ , ์ฌ์ฉ์์๊ฒ ๋์ฑ ํ๋ถํ๊ณ ๋ชฐ์ ๊ฐ ์๋ ์น ๊ฒฝํ์ ์ ์ฌํ ์ ์๊ธฐ๋ฅผ ๋ฐ๋๋๋ค. ์น์ ๋ฏธ๋๋ ๋์ฑ ์ํธ์์ฉ์ ์ด๊ณ ์ค์๊ฐ์ ์ธ ๋ฐฉํฅ์ผ๋ก ๋์๊ฐ๊ณ ์์ผ๋ฉฐ, ์ด ๋ณํ์ ์ต์ ์ ์ ์ฌ๋ฌ๋ถ์ด ์ ์์ต๋๋ค. ๋์์์ด ๋ฐฐ์ฐ๊ณ ํ๊ตฌํ์ฌ ์น์ ๋ฌดํํ ๊ฐ๋ฅ์ฑ์ ํจ๊ป ์ด์ด๊ฐ์๋ค.
'DEV' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
- Total
- Today
- Yesterday
- ์น๊ฐ๋ฐ
- springai
- n8n
- ์ธ๊ณต์ง๋ฅ
- ์ ๋ฌด์๋ํ
- ๋ก๋๋ฐธ๋ฐ์ฑ
- ์น๋ณด์
- AI๋ฐ๋์ฒด
- ๊ฐ๋ฐ์์ฐ์ฑ
- ๋ฐ์ดํฐ๋ฒ ์ด์ค
- ํ๋กฌํํธ์์ง๋์ด๋ง
- ์๋ฐ๊ฐ๋ฐ
- ์ฑ๋ฅ์ต์ ํ
- ํด๋ผ์ฐ๋์ปดํจํ
- ์์ฑํAI
- restapi
- ๋ฏธ๋ai
- ๊ฐ๋ฐ์๊ฐ์ด๋
- LLM
- ๋ฐฑ์๋๊ฐ๋ฐ
- Java
- ํด๋ฆฐ์ฝ๋
- ๋ง์ดํฌ๋ก์๋น์ค
- AI
- ๊ฐ๋ฐ๊ฐ์ด๋
- ๋ฐฐ๋ฏผ
- SEO์ต์ ํ
- ํ๋ก ํธ์๋๊ฐ๋ฐ
- ๊ฐ๋ฐ์์ฑ์ฅ
- AI๊ธฐ์
| ์ผ | ์ | ํ | ์ | ๋ชฉ | ๊ธ | ํ |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
