====== ๐Ÿ”Œ Socket.IO ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ====== Phaser Baduk Metaverse ํ”„๋กœ์ ํŠธ์˜ ''Socket.IO''๋ฅผ ์‚ฌ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ๊ตฌํ˜„์— ๋Œ€ํ•ด ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. ---- ===== 1. Socket.IO๋ž€ ๋ฌด์—‡์ธ๊ฐ€์š”? ===== ''Socket.IO''๋Š” ์›น ๋ธŒ๋ผ์šฐ์ €์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ฃผ๋Š” ''JavaScript'' ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ฑ„ํŒ…, ๊ฒŒ์ž„, ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ๋“ฑ์— ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. **์ฃผ์š” ํŠน์ง•:** * ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ . * ์ž๋™ ์žฌ์—ฐ๊ฒฐ ๊ธฐ๋Šฅ. * ๋‹ค์–‘ํ•œ ์ „์†ก ๋ฐฉ์‹ ์ง€์› (''WebSocket'', ''HTTP Long Polling'' ๋“ฑ). * ๋ฐฉ(''Room'') ๊ธฐ๋Šฅ์œผ๋กœ ๊ทธ๋ฃน ํ†ต์‹  ๊ฐ€๋Šฅ. * ๋ธŒ๋ผ์šฐ์ € ํ˜ธํ™˜์„ฑ์ด ๋›ฐ์–ด๋‚จ. **์ผ๋ฐ˜์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€:** * ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… * ๋ฉ€ํ‹ฐํ”Œ๋ ˆ์ด์–ด ๊ฒŒ์ž„ * ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ * ํ˜‘์—… ๋„๊ตฌ * ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ ---- ===== 2. Socket.IO vs ์ผ๋ฐ˜ HTTP ===== **HTTP ํ†ต์‹ ์˜ ํ•œ๊ณ„:** * ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด์•ผ๋งŒ ์‘๋‹ต์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋จผ์ € ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. * ์‹ค์‹œ๊ฐ„ ํ†ต์‹ ์ด ์–ด๋ ต์Šต๋‹ˆ๋‹ค. **Socket.IO์˜ ์žฅ์ :** * ์„œ๋ฒ„๊ฐ€ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์–ธ์ œ๋“ ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ํด๋ผ์ด์–ธํŠธ๋„ ์„œ๋ฒ„์—๊ฒŒ ์–ธ์ œ๋“ ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. * ์‹ค์‹œ๊ฐ„ ์–‘๋ฐฉํ–ฅ ํ†ต์‹ ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ---- ===== 3. ์„œ๋ฒ„ ์ธก ๊ตฌํ˜„ ===== ==== 1) Socket.IO ์„œ๋ฒ„ ์„ค์ • ==== ๋จผ์ € ํ•„์š”ํ•œ ๋ชจ๋“ˆ๋“ค์„ ๋ถˆ๋Ÿฌ์˜ค๊ณ  ์„œ๋ฒ„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค: const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const path = require('path'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); **์„ค๋ช…:** * ''express'' - ์›น ์„œ๋ฒ„ ํ”„๋ ˆ์ž„์›Œํฌ * ''http'' - ''HTTP'' ์„œ๋ฒ„ ์ƒ์„ฑ * ''socket.io'' - ''Socket.IO'' ์„œ๋ฒ„ ์ƒ์„ฑ * ''cors'' - ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ์˜ ์ ‘๊ทผ ํ—ˆ์šฉ ==== 2) ์ •์  ํŒŒ์ผ ์„œ๋น™ ์„ค์ • ==== // ์ •์  ํŒŒ์ผ ์„œ๋น™ app.use(express.static(path.join(__dirname, 'public'))); **์„ค๋ช…:** * ''public'' ํด๋”์˜ ํŒŒ์ผ๋“ค์„ ์›น์—์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. * ''HTML'', ''CSS'', ''JavaScript'' ํŒŒ์ผ๋“ค์„ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ==== 3) ๊ฒŒ์ž„ ์ƒํƒœ ์ €์žฅ์†Œ ์„ค์ • ==== // ๊ฒŒ์ž„ ์ƒํƒœ ์ €์žฅ์†Œ const games = new Map(); const players = new Map(); **์„ค๋ช…:** * ''Map'' - ํ‚ค-๊ฐ’ ์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ์ž๋ฃŒ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. * ''games'' - ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ๊ฒŒ์ž„๋“ค์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. * ''players'' - ํ˜„์žฌ ์ ‘์† ์ค‘์ธ ํ”Œ๋ ˆ์ด์–ด๋“ค์„ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ==== 4) Socket.IO ์—ฐ๊ฒฐ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ==== // Socket.IO ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ io.on('connection', (socket) => { console.log('์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ:', socket.id); // ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด ์ €์žฅ players.set(socket.id, { id: socket.id, name: 'Anonymous', currentGame: null, color: null }); **์„ค๋ช…:** * ''io.on('connection')'' - ์ƒˆ๋กœ์šด ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฐ๊ฒฐ๋  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ''socket.id'' - ๊ฐ ํด๋ผ์ด์–ธํŠธ์˜ ๊ณ ์œ  ID์ž…๋‹ˆ๋‹ค. * ''players.set()'' - ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด๋ฅผ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค. ==== 5) ๊ฒŒ์ž„ ์ฐธ๊ฐ€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ==== // ๊ฒŒ์ž„ ์ฐธ๊ฐ€ socket.on('join-game', (data) => { const { gameId, playerName } = data; const player = players.get(socket.id); if (!games.has(gameId)) { games.set(gameId, { id: gameId, players: [], gameState: null, spectators: [], createdAt: Date.now() }); } const game = games.get(gameId); **์„ค๋ช…:** * ''socket.on('join-game')'' - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๊ฒŒ์ž„ ์ฐธ๊ฐ€ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ''data'' - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ (''gameId'', ''playerName'')์ž…๋‹ˆ๋‹ค. * ๊ฒŒ์ž„์ด ์—†์œผ๋ฉด ์ƒˆ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. * ๊ฒŒ์ž„ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค. ==== 6) ํ”Œ๋ ˆ์ด์–ด ์ถ”๊ฐ€ ๋ฐ ๊ฒŒ์ž„ ์‹œ์ž‘ ==== // ๊ฒŒ์ž„์— ํ”Œ๋ ˆ์ด์–ด ์ถ”๊ฐ€ if (game.players.length < 2) { const color = game.players.length === 0 ? 'black' : 'white'; player.currentGame = gameId; player.color = color; player.name = playerName || `Player ${color}`; game.players.push({ id: socket.id, name: player.name, color: color }); socket.join(gameId); // ๊ฒŒ์ž„ ์‹œ์ž‘ ์•Œ๋ฆผ if (game.players.length === 2) { io.to(gameId).emit('game-start', { gameId: gameId, players: game.players }); } else { socket.emit('waiting-for-player', { gameId: gameId, currentPlayers: game.players.length }); } console.log(`ํ”Œ๋ ˆ์ด์–ด ${player.name}์ด ๊ฒŒ์ž„ ${gameId}์— ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.`); } **์„ค๋ช…:** * ๊ฒŒ์ž„์— ํ”Œ๋ ˆ์ด์–ด๊ฐ€ 2๋ช… ๋ฏธ๋งŒ์ด๋ฉด ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. * ์ฒซ ๋ฒˆ์งธ ํ”Œ๋ ˆ์ด์–ด๋Š” ๊ฒ€์€ ๋Œ, ๋‘ ๋ฒˆ์งธ๋Š” ํฐ ๋Œ์ž…๋‹ˆ๋‹ค. * ''socket.join(gameId)'' - ํŠน์ • ๊ฒŒ์ž„๋ฐฉ์— ์†Œ์ผ“์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. * ''io.to(gameId).emit()'' - ํŠน์ • ๋ฐฉ์˜ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. * ''socket.emit()'' - ํ˜„์žฌ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ๋งŒ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ==== 7) ๊ด€์ „์ž ์ถ”๊ฐ€ ==== } else { // ๊ด€์ „์ž๋กœ ์ถ”๊ฐ€ game.spectators.push({ id: socket.id, name: playerName || 'Spectator' }); socket.join(gameId); socket.emit('spectator-joined', { gameId: gameId, message: '๊ด€์ „์ž๋กœ ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.' }); console.log(`๊ด€์ „์ž ${playerName}์ด ๊ฒŒ์ž„ ${gameId}์— ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.`); } }); **์„ค๋ช…:** * ๊ฒŒ์ž„์ด ๊ฐ€๋“ ์ฐฌ ๊ฒฝ์šฐ ๊ด€์ „์ž๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. * ๊ด€์ „์ž๋Š” ๊ฒŒ์ž„์„ ๋ณผ ์ˆ˜๋งŒ ์žˆ๊ณ  ์ฐธ์—ฌํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. * ๊ด€์ „์ž์—๊ฒŒ ํŠน๋ณ„ํ•œ ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ==== 8) ๊ฒŒ์ž„ ์ด๋™ ์ฒ˜๋ฆฌ ==== // ๊ฒŒ์ž„ ์ด๋™ socket.on('make-move', (data) => { const { gameId, x, y, color } = data; const game = games.get(gameId); if (!game) { socket.emit('error', { message: '๊ฒŒ์ž„์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.' }); return; } // ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด์˜ ์ฐจ๋ก€์ธ์ง€ ํ™•์ธ const currentPlayer = game.players.find(p => p.id === socket.id); if (!currentPlayer || currentPlayer.color !== color) { socket.emit('error', { message: '๋‹น์‹ ์˜ ์ฐจ๋ก€๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค.' }); return; } // ์ด๋™์„ ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ์ „์†ก io.to(gameId).emit('move-made', { x: x, y: y, color: color, player: currentPlayer.name }); console.log(`${currentPlayer.name}์ด (${x}, ${y})์— ${color} ๋Œ์„ ๋†“์•˜์Šต๋‹ˆ๋‹ค.`); }); **์„ค๋ช…:** * ''socket.on('make-move')'' - ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ด๋™ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ๊ฒŒ์ž„ ์กด์žฌ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. * ํ˜„์žฌ ํ”Œ๋ ˆ์ด์–ด์˜ ์ฐจ๋ก€์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. * ''io.to(gameId).emit()'' - ๋ชจ๋“  ํ”Œ๋ ˆ์ด์–ด์—๊ฒŒ ์ด๋™ ์ •๋ณด๋ฅผ ์ „์†กํ•ฉ๋‹ˆ๋‹ค. ==== 9) ์—ฐ๊ฒฐ ํ•ด์ œ ์ฒ˜๋ฆฌ ==== // ์—ฐ๊ฒฐ ํ•ด์ œ socket.on('disconnect', () => { console.log('ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ํ•ด์ œ:', socket.id); const player = players.get(socket.id); if (player && player.currentGame) { const game = games.get(player.currentGame); if (game) { // ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ฒŒ์ž„์—์„œ ์ œ๊ฑฐ game.players = game.players.filter(p => p.id !== socket.id); game.spectators = game.spectators.filter(s => s.id !== socket.id); // ๊ฒŒ์ž„์ด ๋น„์–ด์žˆ์œผ๋ฉด ์‚ญ์ œ if (game.players.length === 0 && game.spectators.length === 0) { games.delete(player.currentGame); console.log(`๊ฒŒ์ž„ ${player.currentGame}์ด ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`); } else { // ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๋“ค์—๊ฒŒ ์•Œ๋ฆผ io.to(player.currentGame).emit('player-left', { playerId: socket.id, playerName: player.name }); } } } // ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด ์‚ญ์ œ players.delete(socket.id); }); }); **์„ค๋ช…:** * ''socket.on('disconnect')'' - ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ์ด ๋Š์–ด์งˆ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ฒŒ์ž„์—์„œ ์ œ๊ฑฐํ•ฉ๋‹ˆ๋‹ค. * ๊ฒŒ์ž„์ด ๋น„์–ด์žˆ์œผ๋ฉด ๊ฒŒ์ž„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. * ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๋“ค์—๊ฒŒ ํ”Œ๋ ˆ์ด์–ด ํ‡ด์žฅ์„ ์•Œ๋ฆฝ๋‹ˆ๋‹ค. * ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด๋ฅผ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค. ---- ===== 4. ํด๋ผ์ด์–ธํŠธ ์ธก ๊ตฌํ˜„ ===== ==== 1) Socket.IO ํด๋ผ์ด์–ธํŠธ ์„ค์ • ==== ''HTML'' ํŒŒ์ผ์—์„œ ''Socket.IO'' ํด๋ผ์ด์–ธํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค: ๋ฐ”๋‘‘ ๊ฒŒ์ž„

๋ฐ”๋‘‘ ๊ฒŒ์ž„

**์„ค๋ช…:** * ''/socket.io/socket.io.js'' - ''Socket.IO'' ํด๋ผ์ด์–ธํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. * ์„œ๋ฒ„์—์„œ ์ž๋™์œผ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค. * ''game.js'' - ๊ฒŒ์ž„ ๋กœ์ง์„ ๋‹ด์€ ''JavaScript'' ํŒŒ์ผ์ž…๋‹ˆ๋‹ค. ==== 2) ํด๋ผ์ด์–ธํŠธ JavaScript ์„ค์ • ==== // Socket.IO ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ const socket = io(); // ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ socket.on('connect', () => { console.log('์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); document.getElementById('game-info').innerHTML = '์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋จ'; }); socket.on('disconnect', () => { console.log('์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์ด ๋Š์–ด์กŒ์Šต๋‹ˆ๋‹ค.'); document.getElementById('game-info').innerHTML = '์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์ด ๋Š์–ด์ง'; }); **์„ค๋ช…:** * ''io()'' - ''Socket.IO'' ํด๋ผ์ด์–ธํŠธ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. * ''socket.on('connect')'' - ์„œ๋ฒ„์— ์—ฐ๊ฒฐ๋  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ''socket.on('disconnect')'' - ์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์ด ๋Š์–ด์งˆ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ==== 3) ๊ฒŒ์ž„ ์ฐธ๊ฐ€ ๊ธฐ๋Šฅ ==== // ๊ฒŒ์ž„ ์ฐธ๊ฐ€ ํ•จ์ˆ˜ function joinGame(gameId, playerName) { socket.emit('join-game', { gameId: gameId, playerName: playerName }); } // ๊ฒŒ์ž„ ์ฐธ๊ฐ€ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ socket.on('waiting-for-player', (data) => { console.log('๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘...'); document.getElementById('game-info').innerHTML = `๊ฒŒ์ž„ ${data.gameId}์— ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ค‘...`; }); socket.on('game-start', (data) => { console.log('๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค!'); document.getElementById('game-info').innerHTML = `๊ฒŒ์ž„์ด ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ํ”Œ๋ ˆ์ด์–ด: ${data.players.map(p => p.name).join(', ')}`; // ๊ฒŒ์ž„ ๋ณด๋“œ ์ดˆ๊ธฐํ™” initializeGameBoard(); }); **์„ค๋ช…:** * ''socket.emit('join-game')'' - ์„œ๋ฒ„์— ๊ฒŒ์ž„ ์ฐธ๊ฐ€๋ฅผ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. * ''socket.on('waiting-for-player')'' - ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์ƒํƒœ์ž…๋‹ˆ๋‹ค. * ''socket.on('game-start')'' - ๊ฒŒ์ž„์ด ์‹œ์ž‘๋  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ==== 4) ๊ฒŒ์ž„ ์ด๋™ ์ฒ˜๋ฆฌ ==== // ์ด๋™ ์ „์†ก ํ•จ์ˆ˜ function makeMove(x, y, color) { socket.emit('make-move', { gameId: currentGameId, x: x, y: y, color: color }); } // ์ด๋™ ์ˆ˜์‹  ์ฒ˜๋ฆฌ socket.on('move-made', (data) => { console.log(`${data.player}์ด (${data.x}, ${data.y})์— ${data.color} ๋Œ์„ ๋†“์•˜์Šต๋‹ˆ๋‹ค.`); // ๊ฒŒ์ž„ ๋ณด๋“œ์— ๋Œ ํ‘œ์‹œ placeStone(data.x, data.y, data.color); // ํ„ด ๋ณ€๊ฒฝ updateTurn(data.color === 'black' ? 'white' : 'black'); }); // ์—๋Ÿฌ ์ฒ˜๋ฆฌ socket.on('error', (data) => { console.error('์—๋Ÿฌ:', data.message); alert(data.message); }); **์„ค๋ช…:** * ''socket.emit('make-move')'' - ์„œ๋ฒ„์— ์ด๋™์„ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค. * ''socket.on('move-made')'' - ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด์˜ ์ด๋™์„ ๋ฐ›์„ ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ''placeStone()'' - ๊ฒŒ์ž„ ๋ณด๋“œ์— ๋Œ์„ ํ‘œ์‹œํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. * ''updateTurn()'' - ํ„ด์„ ๋ณ€๊ฒฝํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ==== 5) ํ”Œ๋ ˆ์ด์–ด ํ‡ด์žฅ ์ฒ˜๋ฆฌ ==== // ํ”Œ๋ ˆ์ด์–ด ํ‡ด์žฅ ์ฒ˜๋ฆฌ socket.on('player-left', (data) => { console.log(`${data.playerName}์ด ๊ฒŒ์ž„์„ ๋– ๋‚ฌ์Šต๋‹ˆ๋‹ค.`); document.getElementById('game-info').innerHTML = `${data.playerName}์ด ๊ฒŒ์ž„์„ ๋– ๋‚ฌ์Šต๋‹ˆ๋‹ค.`; }); // ๊ด€์ „์ž ์ฐธ๊ฐ€ ์ฒ˜๋ฆฌ socket.on('spectator-joined', (data) => { console.log(data.message); document.getElementById('game-info').innerHTML = data.message; }); **์„ค๋ช…:** * ''socket.on('player-left')'' - ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ๊ฒŒ์ž„์„ ๋– ๋‚  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. * ''socket.on('spectator-joined')'' - ๊ด€์ „์ž๋กœ ์ฐธ๊ฐ€ํ•  ๋•Œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ---- ===== 5. ์‹ค์Šต ์˜ˆ์ œ ===== ==== 1) ๊ฐ„๋‹จํ•œ ์ฑ„ํŒ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ==== **์„œ๋ฒ„ ์ฝ”๋“œ (''chat-server.js''):** 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); // ์ •์  ํŒŒ์ผ ์„œ๋น™ app.use(express.static('public')); // Socket.IO ์—ฐ๊ฒฐ ์ฒ˜๋ฆฌ io.on('connection', (socket) => { console.log('์ƒˆ๋กœ์šด ์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ:', socket.id); // ์‚ฌ์šฉ์ž ์ž…์žฅ socket.on('join', (username) => { socket.username = username; io.emit('user-joined', username); console.log(`${username}์ด ์ž…์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.`); }); // ๋ฉ”์‹œ์ง€ ์ „์†ก socket.on('message', (message) => { io.emit('message', { username: socket.username, message: message, time: new Date().toLocaleTimeString() }); }); // ์—ฐ๊ฒฐ ํ•ด์ œ socket.on('disconnect', () => { if (socket.username) { io.emit('user-left', socket.username); console.log(`${socket.username}์ด ํ‡ด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.`); } }); }); const PORT = 3000; server.listen(PORT, () => { console.log(`์ฑ„ํŒ… ์„œ๋ฒ„๊ฐ€ ํฌํŠธ ${PORT}์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค.`); }); **ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ (''public/index.html''):** ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…

์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…



==== 2) ์‹คํ–‰ ๋ฐฉ๋ฒ• ==== # ํ•„์š”ํ•œ ํŒจํ‚ค์ง€ ์„ค์น˜ npm install express socket.io # ์„œ๋ฒ„ ์‹คํ–‰ node chat-server.js **ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•:** * ๋ธŒ๋ผ์šฐ์ €์—์„œ ''http://localhost:3000'' ์ ‘์† * ์‚ฌ์šฉ์ž๋ช… ์ž…๋ ฅ ํ›„ ์ž…์žฅ * ์—ฌ๋Ÿฌ ๋ธŒ๋ผ์šฐ์ € ์ฐฝ์„ ์—ด์–ด์„œ ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ํ…Œ์ŠคํŠธ ---- ===== 6. ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ===== ==== 1) ๋ฐฉ(Room) ๊ธฐ๋Šฅ ==== // ๋ฐฉ ์ƒ์„ฑ ๋ฐ ์ฐธ๊ฐ€ socket.on('join-room', (roomId) => { socket.join(roomId); socket.roomId = roomId; console.log(`๋ฐฉ ${roomId}์— ์ฐธ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.`); }); // ๋ฐฉ์—๋งŒ ๋ฉ”์‹œ์ง€ ์ „์†ก socket.on('room-message', (message) => { if (socket.roomId) { io.to(socket.roomId).emit('message', { username: socket.username, message: message }); } }); ==== 2) ๊ฐœ์ธ ๋ฉ”์‹œ์ง€ ==== // ํŠน์ • ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฉ”์‹œ์ง€ ์ „์†ก socket.on('private-message', (data) => { const targetSocket = io.sockets.sockets.get(data.to); if (targetSocket) { targetSocket.emit('private-message', { from: socket.username, message: data.message }); } }); ==== 3) ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ ==== // ์—ฐ๊ฒฐ ์ƒํƒœ ํ™•์ธ setInterval(() => { if (!socket.connected) { console.log('์„œ๋ฒ„์™€์˜ ์—ฐ๊ฒฐ์ด ๋Š์–ด์กŒ์Šต๋‹ˆ๋‹ค. ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ ์ค‘...'); } }, 5000); // ์žฌ์—ฐ๊ฒฐ ์‹œ๋„ socket.on('reconnect', () => { console.log('์„œ๋ฒ„์— ์žฌ์—ฐ๊ฒฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'); }); ---- ===== 7. ์„ฑ๋Šฅ ์ตœ์ ํ™” ===== ==== 1) ์ด๋ฒคํŠธ ์ œํ•œ ==== // ํด๋ผ์ด์–ธํŠธ๋‹น ์ด๋ฒคํŠธ ์ œํ•œ const rateLimit = require('socket.io-rate-limiter'); const limiter = rateLimit({ points: 10, // ์ตœ๋Œ€ ์ด๋ฒคํŠธ ์ˆ˜ duration: 1, // ์‹œ๊ฐ„ (์ดˆ) errorMessage: '๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์ด ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.' }); io.use(limiter); ==== 2) ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ==== // ์—ฐ๊ฒฐ ํ•ด์ œ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ socket.on('disconnect', () => { // ๊ฒŒ์ž„ ์ƒํƒœ ์ •๋ฆฌ if (socket.currentGame) { cleanupGame(socket.currentGame); } // ํ”Œ๋ ˆ์ด์–ด ์ •๋ณด ์ •๋ฆฌ delete players[socket.id]; }); ---- ===== 8. ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ ===== ==== 1) ์ž…๋ ฅ ๊ฒ€์ฆ ==== // ๋ฉ”์‹œ์ง€ ๊ธธ์ด ์ œํ•œ socket.on('message', (message) => { if (typeof message !== 'string' || message.length > 1000) { socket.emit('error', { message: '๋ฉ”์‹œ์ง€๊ฐ€ ๋„ˆ๋ฌด ๊น๋‹ˆ๋‹ค.' }); return; } // XSS ๋ฐฉ์ง€ const sanitizedMessage = message.replace(/