====== ๐Ÿ”Œ Socket.IO ====== Socket.IO๋Š” ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ์œ„ํ•œ JavaScript ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ===== ๐Ÿ“‹ ์ •์˜ ===== **Socket.IO**๋Š” ์›น์†Œ์ผ“์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ž๋™ ์žฌ์—ฐ๊ฒฐ, ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ํ†ต์‹ , ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ ๋“ฑ์„ ์ œ๊ณตํ•˜์—ฌ ์‹ค์‹œ๊ฐ„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. ===== ๐ŸŽฏ ์ฃผ์š” ํŠน์ง• ===== **1. ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ** - ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ตํ™˜ - ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜ ๋ฉ”์‹œ์ง€ ์ „์†ก - ์ž๋™ ์žฌ์—ฐ๊ฒฐ ๊ธฐ๋Šฅ **2. ํฌ๋กœ์Šค ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ** - ๋ชจ๋“  ์ฃผ์š” ๋ธŒ๋ผ์šฐ์ € ์ง€์› - ํด๋ฐฑ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ HTTP ํด๋ง ์ง€์› - ์ž๋™ ์ „์†ก ๋ฐฉ์‹ ์„ ํƒ **3. ๋ฐฉ(Room) ์‹œ์Šคํ…œ** - ํŠน์ • ๊ทธ๋ฃน์—๋งŒ ๋ฉ”์‹œ์ง€ ์ „์†ก - ๋„ค์ž„์ŠคํŽ˜์ด์Šค ์ง€์› - ์‚ฌ์šฉ์ž๋ณ„ ๊ฐœ์ธ ๋ฉ”์‹œ์ง€ ===== ๐Ÿ—๏ธ ์„œ๋ฒ„ ์ธก ๊ตฌํ˜„ ===== **๊ธฐ๋ณธ ์„œ๋ฒ„ ์„ค์ •:** const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); // ์—ฐ๊ฒฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ io.on('connection', (socket) => { console.log('์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ:', socket.id); // ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  socket.on('chat-message', (data) => { console.log('๋ฐ›์€ ๋ฉ”์‹œ์ง€:', data); // ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์‹œ์ง€ ์ „์†ก io.emit('chat-message', { user: data.user, message: data.message, timestamp: new Date() }); }); // ์—ฐ๊ฒฐ ํ•ด์ œ ์ฒ˜๋ฆฌ socket.on('disconnect', () => { console.log('ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ:', socket.id); }); }); server.listen(3000, () => { console.log('Socket.IO ์„œ๋ฒ„๊ฐ€ ํฌํŠธ 3000์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.'); }); **๋ฐฉ(Room) ๊ด€๋ฆฌ:** io.on('connection', (socket) => { // ๋ฐฉ ์ฐธ๊ฐ€ socket.on('join-room', (roomId) => { socket.join(roomId); console.log(`์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ ${roomId}์— ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.`); // ๋ฐฉ์˜ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์•Œ๋ฆผ socket.to(roomId).emit('user-joined', { userId: socket.id, message: '์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž๊ฐ€ ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.' }); }); // ๋ฐฉ์—์„œ ๋ฉ”์‹œ์ง€ ์ „์†ก socket.on('room-message', (data) => { io.to(data.roomId).emit('message', { user: data.user, message: data.message, timestamp: new Date() }); }); // ๋ฐฉ ๋‚˜๊ฐ€๊ธฐ socket.on('leave-room', (roomId) => { socket.leave(roomId); socket.to(roomId).emit('user-left', { userId: socket.id, message: '์‚ฌ์šฉ์ž๊ฐ€ ๋ฐฉ์„ ๋‚˜๊ฐ”์Šต๋‹ˆ๋‹ค.' }); }); }); ===== ๐ŸŽฎ ํด๋ผ์ด์–ธํŠธ ์ธก ๊ตฌํ˜„ ===== **ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ:** import io from 'socket.io-client'; // ์„œ๋ฒ„์— ์—ฐ๊ฒฐ const socket = io('http://localhost:3000'); // ์—ฐ๊ฒฐ ์ด๋ฒคํŠธ socket.on('connect', () => { console.log('์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); console.log('์†Œ์ผ“ ID:', socket.id); }); socket.on('disconnect', () => { console.log('์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์ด ๋Š์–ด์กŒ์Šต๋‹ˆ๋‹ค.'); }); // ๋ฉ”์‹œ์ง€ ์ˆ˜์‹  socket.on('chat-message', (data) => { console.log('์ƒˆ ๋ฉ”์‹œ์ง€:', data); displayMessage(data); }); // ๋ฉ”์‹œ์ง€ ์ „์†ก function sendMessage(message) { socket.emit('chat-message', { user: '์‚ฌ์šฉ์ž๋ช…', message: message }); } **๋ฐฉ ๊ด€๋ฆฌ (ํด๋ผ์ด์–ธํŠธ):** // ๋ฐฉ ์ฐธ๊ฐ€ function joinRoom(roomId) { socket.emit('join-room', roomId); } // ๋ฐฉ์—์„œ ๋ฉ”์‹œ์ง€ ์ „์†ก function sendRoomMessage(roomId, message) { socket.emit('room-message', { roomId: roomId, user: '์‚ฌ์šฉ์ž๋ช…', message: message }); } // ๋ฐฉ ์ด๋ฒคํŠธ ์ˆ˜์‹  socket.on('user-joined', (data) => { console.log('์ƒˆ ์‚ฌ์šฉ์ž ์ฐธ๊ฐ€:', data); }); socket.on('user-left', (data) => { console.log('์‚ฌ์šฉ์ž ํ‡ด์žฅ:', data); }); socket.on('message', (data) => { displayMessage(data); }); ===== ๐ŸŽฏ ์ด๋ฒคํŠธ ์‹œ์Šคํ…œ ===== **๊ธฐ๋ณธ ์ด๋ฒคํŠธ:** // ์„œ๋ฒ„ ์ธก io.on('connection', (socket) => { // ์ปค์Šคํ…€ ์ด๋ฒคํŠธ socket.on('user-login', (userData) => { console.log('์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ:', userData); socket.broadcast.emit('user-online', userData); }); socket.on('typing', (data) => { socket.broadcast.emit('user-typing', data); }); socket.on('stop-typing', (data) => { socket.broadcast.emit('user-stop-typing', data); }); }); // ํด๋ผ์ด์–ธํŠธ ์ธก socket.on('user-online', (userData) => { console.log('์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ์˜จ๋ผ์ธ:', userData); }); socket.on('user-typing', (data) => { showTypingIndicator(data.user); }); socket.on('user-stop-typing', (data) => { hideTypingIndicator(data.user); }); ===== ๐Ÿ” ์ธ์ฆ ๋ฐ ๋ณด์•ˆ ===== **๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•œ ์ธ์ฆ:** // ์„œ๋ฒ„ ์ธก io.use((socket, next) => { const token = socket.handshake.auth.token; if (token) { // ํ† ํฐ ๊ฒ€์ฆ verifyToken(token) .then(user => { socket.user = user; next(); }) .catch(err => { next(new Error('์ธ์ฆ ์‹คํŒจ')); }); } else { next(new Error('ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค')); } }); // ํด๋ผ์ด์–ธํŠธ ์ธก const socket = io('http://localhost:3000', { auth: { token: 'your-jwt-token' } }); **๋ฐฉ ์ ‘๊ทผ ์ œ์–ด:** { socket.on('join-private-room', (roomId) => { // ์‚ฌ์šฉ์ž ๊ถŒํ•œ ํ™•์ธ if (hasRoomAccess(socket.user.id, roomId)) { socket.join(roomId); socket.emit('room-joined', { roomId }); } else { socket.emit('access-denied', { message: '์ด ๋ฐฉ์— ์ ‘๊ทผํ•  ๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.' }); } }); }); ===== ๐Ÿ“Š ์‹ค์‹œ๊ฐ„ ํ†ต๊ณ„ ===== **์—ฐ๊ฒฐ ํ†ต๊ณ„ ์ˆ˜์ง‘:** // ์„œ๋ฒ„ ์ธก let connectionStats = { totalConnections: 0, activeConnections: 0, peakConnections: 0 }; io.on('connection', (socket) => { connectionStats.totalConnections++; connectionStats.activeConnections++; if (connectionStats.activeConnections > connectionStats.peakConnections) { connectionStats.peakConnections = connectionStats.activeConnections; } socket.on('disconnect', () => { connectionStats.activeConnections--; }); }); // ํ†ต๊ณ„ API ์—”๋“œํฌ์ธํŠธ app.get('/socket-stats', (req, res) => { res.json(connectionStats); }); **์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง:** // ํด๋ผ์ด์–ธํŠธ ์ธก socket.on('connect', () => { updateConnectionStatus('์—ฐ๊ฒฐ๋จ'); }); socket.on('disconnect', () => { updateConnectionStatus('์—ฐ๊ฒฐ ๋Š๊น€'); }); socket.on('reconnect', (attemptNumber) => { console.log(`์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ${attemptNumber}๋ฒˆ์งธ`); }); socket.on('reconnect_error', (error) => { console.log('์žฌ์—ฐ๊ฒฐ ์˜ค๋ฅ˜:', error); }); ===== ๐ŸŽฎ ๊ฒŒ์ž„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ===== **์‹ค์‹œ๊ฐ„ ๊ฒŒ์ž„ ๊ตฌํ˜„:** // ์„œ๋ฒ„ ์ธก io.on('connection', (socket) => { socket.on('join-game', (gameId) => { socket.join(gameId); // ๊ฒŒ์ž„ ์ƒํƒœ ์ „์†ก const gameState = getGameState(gameId); socket.emit('game-state', gameState); }); socket.on('game-move', (data) => { const { gameId, move } = data; // ๊ฒŒ์ž„ ๋กœ์ง ์ฒ˜๋ฆฌ const result = processGameMove(gameId, move, socket.id); if (result.valid) { // ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ์—…๋ฐ์ดํŠธ ์ „์†ก io.to(gameId).emit('game-update', { move: move, gameState: result.gameState }); } else { socket.emit('invalid-move', { reason: result.reason }); } }); }); // ํด๋ผ์ด์–ธํŠธ ์ธก socket.on('game-state', (gameState) => { updateGameDisplay(gameState); }); socket.on('game-update', (data) => { updateGameDisplay(data.gameState); animateMove(data.move); }); function makeMove(move) { socket.emit('game-move', { gameId: currentGameId, move: move }); } ===== ๐Ÿ› ๏ธ ๋ฌธ์ œ ํ•ด๊ฒฐ ===== **์—ฐ๊ฒฐ ๋ฌธ์ œ:** // ์žฌ์—ฐ๊ฒฐ ์„ค์ • const socket = io('http://localhost:3000', { reconnection: true, reconnectionAttempts: 5, reconnectionDelay: 1000, timeout: 20000 }); // ์—ฐ๊ฒฐ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง socket.on('connect_error', (error) => { console.log('์—ฐ๊ฒฐ ์˜ค๋ฅ˜:', error); }); socket.on('reconnect_failed', () => { console.log('์žฌ์—ฐ๊ฒฐ ์‹คํŒจ'); }); **๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€:** // ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ ์ •๋ฆฌ function cleanup() { socket.off('chat-message'); socket.off('user-joined'); socket.disconnect(); } // ์ปดํฌ๋„ŒํŠธ ์–ธ๋งˆ์šดํŠธ ์‹œ ์ •๋ฆฌ useEffect(() => { return () => { cleanup(); }; }, []); ===== ๐Ÿ“š ๊ด€๋ จ ๋ฌธ์„œ ===== * [[wiki:it:dream_of_enc:metaverse:socketio|Socket.IO]] - ๋ฐ”๋‘‘ ๋ฉ”ํƒ€๋ฒ„์Šค ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ํ”„๋กœํ† ์ฝœ * [[wiki:it:dream_of_enc:metaverse:nodejs|Node.js]] - ๋ฐ”๋‘‘ ๋ฉ”ํƒ€๋ฒ„์Šค JavaScript ๋Ÿฐํƒ€์ž„ * [[wiki:it:dream_of_enc:metaverse:express|Express.js]] - ๋ฐ”๋‘‘ ๋ฉ”ํƒ€๋ฒ„์Šค ์›น ํ”„๋ ˆ์ž„์›Œํฌ