์น์์ผ ํ๋ก๊ทธ๋๋ฐ
1. HTTP ๋ฐ TCP/IP
1.1 TCP์ IP์ ์ญํ
-
IP (Internet Protocol): ๋ฐ์ดํฐ๋ฅผ ๋ชฉ์ ์ง๊น์ง ์ ๋ฌํ๋ ์ญํ .
-
TCP๋ IP ์์์ ์๋ํ๋ฉฐ, ๋ฐ์ดํฐ๋ฅผ ํจํท์ผ๋ก ๋๋์ด ์ ์กํ ํ ์์ ์ธก์์ ์ด๋ฅผ ์ฌ์กฐ๋ฆฝ.
-
TCP (Transmission Control Protocol): ๋ฐ์ดํฐ ์ ์ก์ ์ ๋ขฐ์ฑ์ ๋ณด์ฅ.
1.2 HTTP ํต์ ์ ํน์ง
-
HTTP (Hypertext Transfer Protocol): ์์ฒญ-์๋ต ๊ธฐ๋ฐ์ ๋จ๋ฐฉํฅ ํต์ ํ๋กํ ์ฝ.
-
ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ์๋ตํ๋ ๊ตฌ์กฐ๋ก, ์ ์ ์ฝํ ์ธ ๋ก๋, ํผ ๋ฐ์ดํฐ ์ ์ก, API ํธ์ถ ๋ฑ ์ผํ์ฑ ์์ฒญ์ ์ ํฉ.
-
์์ฒญ-์๋ต ๊ตฌ์กฐ: ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๊ณ ์๋ฒ๊ฐ ์๋ตํ๋ ๋ฐฉ์์ผ๋ก, ์์ฒญ๋ง๋ค ์ ์ฐ๊ฒฐ์ ์ค์ ํ๊ณ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์.
-
๋ฌด์ํ์ฑ: HTTP๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๊ฐ ์์ฒญ์ด ๋ ๋ฆฝ์ . ์ํ๋ฅผ ์ ์งํ๋ ค๋ฉด ์ธ์ ์ด๋ ์ฟ ํค์ ๊ฐ์ ์ถ๊ฐ ์์ ์ด ํ์.
2. ์น์์ผ
2.1 ์น์์ผ์ ํน์ง
-
์ค์๊ฐ ํต์ ์ ํ์์ฑ์ด ์ปค์ง๋ฉด์ 2011๋ ์ WebSocket์ด ํ์คํ.
-
WebSocket์ ์ด TCP๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์๋ํ๋ฉฐ, ๋ฐ์ดํฐ๊ฐ ์์๋๋ก ์ ์ก๋๋๋ก ํ๊ณ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๊ฒฝ์ฐ ์๋์ผ๋ก ๋ณต๊ตฌ.
- HTTP ํธ๋์ ฐ์ดํฌ๋ก ์ฐ๊ฒฐ์ ์์ํ๊ณ , ์ดํ TCP ์์ผ์ ํตํด ์ฐ๊ฒฐ์ ์ง์ํ๋ฉฐ ์๋ฐฉํฅ ๋ฐ์ดํฐ ์ ์ก.
-
์ค์๊ฐ ๋ฐ์ดํฐ ๊ตํ์ด ์ค์ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ํฉ.

2.2 ์ ์ ํ ์ฌ์ฉ ์์?
-
์ค์๊ฐ ์ฑํ ์ ํ๋ฆฌ์ผ์ด์
-
์ค์๊ฐ ๊ฒ์
-
์ค์๊ฐ ํ์ ๋๊ตฌ (์: ๊ณต๋ ๋ฌธ์ ํธ์ง)
-
์ค์๊ฐ ๋ฐ์ดํฐ ์คํธ๋ฆฌ๋ฐ (์: ๋ผ์ด๋ธ ์คํฌ์ธ ์ค๊ณ)
2.3 ์ฅ๋จ์
-
์ฅ์
-
๋ฎ์ ์ง์ฐ ์๊ฐ, ์ค์๊ฐ ๋ฐ์ดํฐ ๊ตํ์ ์ต์ ํ๋จ
-
ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ ์ก, ํค๋ ์ค๋ฒํค๋ ๊ฐ์
-
ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๋์์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์
-
-
๋จ์
-
์ง์์ ์ธ ์ฐ๊ฒฐ๋ก ์ธํ ์๋ฒ ์์ ์๋ชจ
-
๊ตฌํ ๋ณต์ก์ฑ ์ฆ๊ฐ
-
์ผ๋ถ ๋ฐฉํ๋ฒฝ์ด๋ ํ๋ก์์์ ์ฐจ๋จ๋ ์ ์์
-
2.4 ๋ธ๋ผ์ฐ์ ์์ผ ์ด๊ธฐํ
const socket = new WebSocket('ws://192.168.55.28:8080');
2.5 ์๋ฒ๋ก ๋ฐ์ดํฐ ์ ์กํ๊ธฐ
form.addEventListener('submit', function (e) {
e.preventDefault();
if (input.value) {
const message = {
clientId: clientId,
message: input.value
}
socket.send(JSON.stringify(message));
input.value = '';
}
});
2.6 ๋คํธ์ํฌ ๋ค์ฌ๋ค๋ณด๊ธฐ

ํธ๋์์ดํฌ ๊ณผ์
-
ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก ์์ฒญ ์ ์ก
- Sec-WebSocket-Key์ ํจ๊ป WebSocket ์ฐ๊ฒฐ ์์ฒญ์ ๋ณด๋.
-
์๋ฒ๊ฐ Sec-WebSocket-Accept ์์ฑ ํ ์๋ต
- ์๋ฒ๋ ํด๋ผ์ด์ธํธ๊ฐ ๋ณด๋ธ ํค๋ฅผ ์ด์ฉํด Sec-WebSocket-Accept ํค๋ ๊ฐ์ ์์ฑํ๊ณ ์๋ต
-
ํด๋ผ์ด์ธํธ์ ๊ฒ์ฆ: ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ ์๋ต ๊ฐ์ ํ์ธํ๊ณ ์ผ์นํ๋ฉด WebSocket ์ฐ๊ฒฐ์ด ์ฑ๋ฆฝ
์ฐธ๊ณ . Request URL <socket.io ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์>
-
EIO=4: Socket.IO ํ๋กํ ์ฝ ๋ฒ์ ( 4.x ํ๋กํ ์ฝ ์ฌ์ฉ)
-
transport=websocket: ํ์ฌ ์ ์ก ๋ฐฉ์์ ์ง์ .
-
sid=1kbDBngrZ9P44RmsAAAl: ์ธ์ ID๋ก, ์๋ฒ์์ ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณํ๊ธฐ ์ํด ๋ถ์ฌํ๋ ๊ณ ์ ID
2.7 ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ ์์
socket.onmessage = function (event) {
const item = document.createElement('li');
event.data.text().then((text) => {
let {clientId, message}= JSON.parse(text)
if(clientId === myId) {
item.classList.add('mine');
} else {
message = `${clientId}: ${message}`;
}
item.textContent = message;
messages.appendChild(item);
window.scrollTo(0, document.body.scrollHeight);
});
};
2.8 ์๋ฒ ์ฝ๋ ์์
const WebSocket = require('ws');
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const wsServer = new WebSocket.Server({ server });
wsServer.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
// ๋ฐ์ ๋ฉ์์ง๋ฅผ ๋ชจ๋ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก
console.log('received: %s', message);
wsServer.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});
app.use(express.static('public'));
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => console.log(`Server is running on http://localhost:${PORT}`));
2.9 ํด๋ผ์ด์ธํธ ์ฝ๋ (React)
import React, { useEffect, useRef } from 'react';
const Canvas = () => {
const canvasRef = useRef(null);
const socketRef = useRef(null);
useEffect(() => {
socketRef.current = new WebSocket('ws://localhost:8080');
socketRef.current.onmessage = (event) => {
const { x, y } = JSON.parse(event.data);
draw(x, y);
};
return () => socketRef.current.close();
}, []);
const draw = (x, y) => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(x, y, 2, 2);
};
const handleMouseMove = (event) => {
const x = event.clientX;
const y = event.clientY;
draw(x, y);
socketRef.current.send(JSON.stringify({ x, y }));
};
return <canvas ref={canvasRef} width={800} height={600} onMouseMove={handleMouseMove} />;
};
export default Canvas;
3. ๊ฐ๋ฐ ํ
3.1 ๋๋ฒ๊น
-
๋คํธ์ํฌ ํญ ํ์ฉ: ๋ธ๋ผ์ฐ์ ๊ฐ๋ฐ์ ๋๊ตฌ์ ๋คํธ์ํฌ ํญ์์ WebSocket ์ฐ๊ฒฐ ์ํ์ ์ ์ก๋๋ ๋ฐ์ดํฐ๋ฅผ ์ค์๊ฐ์ผ๋ก ๋ชจ๋ํฐ๋งํ ์ ์์.
-
WebSocket ํ๋ ์(Frame) ํญ์์ ์ฃผ๊ณ ๋ฐ๋ ๋ฉ์์ง๋ฅผ ํ์ธํ์ฌ ๋ฐ์ดํฐ ํ๋ฆ์ ํ์
-
์ฝ์ ๋ก๊ทธ: ํด๋ผ์ด์ธํธ์ ์๋ฒ์ ์์ผ ์ฐ๊ฒฐ, ์ก์์ ์ด๋ฒคํธ๋ฅผ ์ฝ์์ ์ถ๋ ฅํด ํต์ ์ด ์ ์์ ์ผ๋ก ์ด๋ฃจ์ด์ง๋์ง ํ์ธ.

3.2 ์ค๋ฅ - CORS
-
WebSocket ํต์ ์์๋ CORS ์ค๋ฅ๊ฐ ๋ฐ์ํ ์ ์์.
-
ํนํ ํด๋ผ์ด์ธํธ์ ์๋ฒ๊ฐ ๋ค๋ฅธ ๋๋ฉ์ธ์ ์์ ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ์์ WebSocket ์ฐ๊ฒฐ์ ์ฐจ๋จํ ์ ์์.
-
socket.io์์๋ ์๋ฒ์์ CORS ์ต์ ์ ์ถ๊ฐํ์ฌ ํด๊ฒฐํ ์ ์์.
const io = require('socket.io')(3000, {
cors: {
origin: "http://localhost:3001",
methods: ["GET", "POST"]
}
});
3.3 WebSocket ๋ณด์์ ์ํย wss://ย ์ค์
๋ณด์์ด ํ์ํ WebSocket ํต์ ์์๋ย wss://ย ํ๋กํ ์ฝ์ ์ฌ์ฉํด TLS ์ํธํ๋ฅผ ์ ์ฉํ ์ ์์.
- ์ ํ๋ฆฌ์ผ์ด์
์ฝ๋์์ TLS ์ค์ :ย
wss://๋ฅผ ์ง์ํ๋ ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ HTTPS ์๋ฒ๋ฅผ ์ค์ ํ๊ณ WebSocket ์๋ฒ๋ฅผ ๊ทธ ์์ ๊ตฌ์ฑ.
const https = require('https');
const WebSocket = require('ws');
const server = https.createServer({
cert: fs.readFileSync('path/to/cert.pem'),
key: fs.readFileSync('path/to/key.pem')
});
const wss = new WebSocket.Server({ server });
-
ํ๋ก์ ์๋ฒ ์ด์ฉ (๋์)
-
Nginx์ ๊ฐ์ ์น ์๋ฒ์์ TLS๋ฅผ ์ค์ ํด WebSocket์ย
wss://๋ก ๋ณดํธํ ์๋ ์์.
-
3.4 JWT๋ฅผ ์ฌ์ฉํ WebSocket ์ธ์ฆ ๊ณผ์
3.4.1 ์ด๊ธฐ ์ฐ๊ฒฐ ์ ํ ํฐ ์ ์ก
-
WebSocket์ HTTP ํธ๋์ ฐ์ดํฌ๋ฅผ ํตํด ์ฐ๊ฒฐ์ ์ค์ ํ๋ฏ๋ก, ์ด ๊ณผ์ ์์ JWT ํ ํฐ์ ํจ๊ป ์ ์กํ์ฌ ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ณ.
-
WebSocket์ย URL ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ย ํค๋์ JWT ํ ํฐ์ ํฌํจํ์ฌ ์ ์ก.
3.4.2 ์๋ฒ์์ JWT ์ธ์ฆ ๋ฐ ์๋ณ
-
์๋ฒ๋ ํธ๋์ ฐ์ดํฌ ์์ฒญ์ ํฌํจ๋ JWT ํ ํฐ์ ํ์ธํ๊ณ , ์ ํจํ ๊ฒฝ์ฐ WebSocket ์ฐ๊ฒฐ์ ํ์ฉ.
-
์๋ฒ๋ JWT ํ ํฐ์ ๊ฒ์ฆํ์ฌ ํด๋น ์ฌ์ฉ์์ **์๋ณ ์ ๋ณด(์: user ID)**๋ฅผ ์ถ์ถํ๊ณ , ์ดํ ํด๋ผ์ด์ธํธ์์ ํต์ ์ ์ฌ์ฉ.
์์ ์ฝ๋
const jwtToken = "your-jwt-token"; //๋์ถฉ ํด๋ผ์์ ์ ์ฅ์ค์ธ ํ ํฐ
// WebSocket ์ฐ๊ฒฐ ์ ํ ํฐ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ํฌํจํ์ฌ ์ ์ก
const socket = new WebSocket(`ws://localhost:8080/chat?token=${jwtToken}`);
-
ํด๋ผ์ด์ธํธ๋ WebSocket ์ฐ๊ฒฐ ์ JWT ํ ํฐ์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ ์ก.
-
์๋ฒ๋
-
WebSocket ํธ๋์ ฐ์ดํฌ ์์ฒญ์์ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ์ ๋ฌ๋ JWT ํ ํฐ์ ์ถ์ถ & ๊ฒ์ฆ
-
ํ ํฐ์ด ์ ํจํ๋ฉด ํด๋น ์ฌ์ฉ์ ์ ๋ณด(
userId)๋ฅผ ํ์ฉํ์ฌ ๋ฉ์์ง๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์.
-
3.5 ์ฐ๊ฒฐ ์ฒดํฌ & ์ฌ์ฐ๊ฒฐ ๋ฐฉ์
3.5.1 Ping/Pong ๋ฉ์์ง๋ฅผ ํตํ ์ฐ๊ฒฐ ์ํ ํ์ธ
WebSocket ํ๋กํ ์ฝ์ ์ฐ๊ฒฐ ์ํ๋ฅผ ์ ์งํ๊ธฐ ์ํดย Ping/Pongย ํ๋ ์์ ์ฃผ๊ณ ๋ฐ๊ธฐ.
-
Ping: ํด๋ผ์ด์ธํธ๋ ์๋ฒ์์ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณด๋ด๋ ์ฐ๊ฒฐ ์ฒดํฌ ๋ฉ์์ง
-
Pong: Ping์ ๋ํ ์๋ต์ผ๋ก, ์ฐ๊ฒฐ์ด ์ฌ์ ํ ์ ํจํ๋ค๋ ๊ฒ์ ํ์ธํ๋ ๋ฐ ์ฌ์ฉ.
-
์ฌ์ฉ ์์ : Ping ๋ฉ์์ง๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ๋ณด๋ด๊ณ , ์ผ์ ์๊ฐ ๋ด์ Pong ์๋ต์ ๋ฐ์ง ๋ชปํ๋ฉด ์ฐ๊ฒฐ์ด ๋์ด์ง ๊ฒ์ผ๋ก ๊ฐ์ฃผ.
3.5.2 ์๋ ์ฌ์ฐ๊ฒฐ ๋ฐฉ์
-
WebSocket ์ฐ๊ฒฐ์ด ๋์ด์ก์ ๋,ย ํด๋ผ์ด์ธํธ๊ฐ ์ผ์ ์๊ฐ ๊ฐ๊ฒฉ์ผ๋ก ์๋์ผ๋ก ์ฌ์ฐ๊ฒฐ์ ์๋ํ์ฌ ๋ณต๊ตฌ.
-
๋ณดํต ์ฌ์ฐ๊ฒฐ ๊ฐ๊ฒฉ์ ์ ์ฐจ ๋๋ ค๊ฐ๋ ๋ฐฉ์์ ์ฌ์ฉํด ์๋ฒ์ ๊ณผ๋ํ ์ฌ์ฐ๊ฒฐ ์์ฒญ์ด ๊ฐ์ง ์๋๋ก ๊ตฌํ
3.6 Web Workers๋ฅผ ํ์ฉํ ์์ผ๊ฐ์ฒด ๊ณต์
3.6.1 ๋ฌธ์ ์
-
๋ธ๋ผ์ฐ์ ์ฌ๋ฌ๊ฐ์ ํญ์์ ์๋ฒ์ ํต์ ํ๋ ค๊ณ ํ ๋๋ ์ฌ๋ฌ๊ฐ์ ์ค๋ณต ์์ผ์ฐ๊ฒฐ์ด ํ์ํจ.
-
๋จ์ผ ์์ผํต์ ์ฐ๊ฒฐ์ ์ ์งํ๋ ๋ฐฉ๋ฒ์?
3.6.2 ๋ฐฉ์
-
Web Workers์ Shared Worker๋ฅผ ์ฌ์ฉํด์ ๊ตฌํ ๊ฐ๋ฅ.
- Worker Thread์์ ํต์ ์ ๋ด๋นํ๊ณ ๋ธ๋ผ์ฐ์ ํญ๊ณผ ํต์ ์ ํ๋ฉด ๋ฐ์ดํฐ(๋ฉ์์ง) ๊ณต์

์ฐธ๊ณ :ย https://pike96.com/posts/websocket-sharedworker-broadcastchannel/
3.6.3 ์์
Shared Worker์ฝ๋
const socket = new WebSocket('wss://example.com/socket');
const clients = []; //๋ธ๋ผ์ฐ์ ํญ๋ค
// WebSocket ๋ฉ์์ง ์์ ์ ์ฐ๊ฒฐ๋ ๋ชจ๋ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ก
socket.onmessage = (event) => {
clients.forEach((port) => port.postMessage(event.data));
};
// ์ ํด๋ผ์ด์ธํธ(ํญ) ์ฐ๊ฒฐ ์
onconnect = (event) => {
const port = event.ports[0];
clients.push(port);
// ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฉ์์ง๋ฅผ ์์ ํด WebSocket์ผ๋ก ์ ์ก
port.onmessage = (e) => {
socket.send(e.data);
};
// ํด๋ผ์ด์ธํธ ์ฐ๊ฒฐ์ด ์ข
๋ฃ๋๋ฉด ๋ฐฐ์ด์์ ์ ๊ฑฐ
port.onclose = () => {
clients.splice(clients.indexOf(port), 1);
};
};
๋ฉ์ธ์ฐ๋ ๋(๋ธ๋ผ์ฐ์ ํญ)
// SharedWorker์ ์ฐ๊ฒฐ
const worker = new SharedWorker('sharedWorker.js');
worker.port.start(); // ํฌํธ ํ์ฑํ
// SharedWorker๋ก๋ถํฐ ๋ฉ์์ง๋ฅผ ์์ ํด ์ถ๋ ฅ
worker.port.onmessage = (event) => {
console.log('์๋ฒ๋ก๋ถํฐ ๋ฐ์ ๋ฉ์์ง:', event.data);
};
// ๋ฉ์์ง๋ฅผ SharedWorker๋ก ์ ์ก (SharedWorker๊ฐ WebSocket์ ํตํด ์๋ฒ๋ก ์ ๋ฌ)
function sendMessage(message) {
worker.port.postMessage(message);
}
3.6.4 ์ฃผ์์ฌํญ
-
๋ธ๋ผ์ฐ์ ํธํ์ฑ ์ด์ ๋์ ํ์
-
ํต์ ์๊ฐ ์ธก์ ๋น๊ต ํ์
-
ํญ์ด ํ๋์ผ๋ ๋ ์ผ๋ฐ ์์ผ์ฐ๊ฒฐ, ํญ์ด ์ฌ๋ฌ๊ฐ์ผ๋๋ Shared Worker๋ก ๊ตฌํ?
4. socket.io ์ฌ์ฉ ์์
-
socket.io๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ๋๋ผ WebSocket ์ฝ๋๋ณด๋ค ๊ฐ๊ฒฐํ๊ฒ ๊ตฌํํ ์ ์์. -
๋ค์์ ๋์ผํ ๊ธฐ๋ฅ์ย
socket.io๋ก ๊ตฌํํ ์.
4.1 ์๋ฒ ์ฝ๋ (socket.io)
const io = require('socket.io')(3000, {
cors: {
origin: "http://localhost:3001",
methods: ["GET", "POST"]
}
});
io.on('connection', (socket) => {
socket.on('draw', (data) => {
socket.broadcast.emit('draw', data);
});
});
4.2 ํด๋ผ์ด์ธํธ ์ฝ๋ (React)
import React, { useEffect, useRef } from 'react';
import io from 'socket.io-client';
const Canvas = () => {
const canvasRef = useRef(null);
const socketRef = useRef(null);
useEffect(() => {
socketRef.current = io('http://localhost:3000');
socketRef.current.on('draw', ({ x, y }) => {
draw(x, y);
});
return () => socketRef.current.disconnect();
}, []);
const draw = (x, y) => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.fillRect(x, y, 2, 2);
};
const handleMouseMove = (event) => {
const x = event.clientX;
const y = event.clientY;
draw(x, y);
socketRef.current.emit('draw', { x, y });
};
return <canvas ref={canvasRef} width={800} height={600} onMouseMove={handleMouseMove} />;
};
export default Canvas;