์‚ฌ์šฉ์ž ๋„๊ตฌ

์‚ฌ์ดํŠธ ๋„๊ตฌ


wiki:it:dream_of_enc:metaverse:socketio

๋ชฉ์ฐจ

๐Ÿ”Œ 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 ํด๋ผ์ด์–ธํŠธ๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:

<!DOCTYPE html>
<html>
<head>
    <title>๋ฐ”๋‘‘ ๊ฒŒ์ž„</title>
    <script src="/socket.io/socket.io.js"></script>
</head>
<body>
    <div id="game-container">
        <h1>๋ฐ”๋‘‘ ๊ฒŒ์ž„</h1>
        <div id="game-info"></div>
        <div id="game-board"></div>
    </div>
 
    <script src="js/game.js"></script>
</body>
</html>

์„ค๋ช…:

  • /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):

<!DOCTYPE html>
<html>
<head>
    <title>์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…</title>
    <script src="/socket.io/socket.io.js"></script>
    <style>
        #chat-container {
            width: 400px;
            height: 300px;
            border: 1px solid #ccc;
            overflow-y: scroll;
            padding: 10px;
        }
        #message-input {
            width: 300px;
            margin-top: 10px;
        }
    </style>
</head>
<body>
    <h1>์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…</h1>
    <input type="text" id="username" placeholder="์‚ฌ์šฉ์ž๋ช…" />
    <button onclick="join()">์ž…์žฅ</button>
    <br><br>
    <div id="chat-container"></div>
    <input type="text" id="message-input" placeholder="๋ฉ”์‹œ์ง€๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" />
    <button onclick="sendMessage()">์ „์†ก</button>
 
    <script>
        const socket = io();
        const chatContainer = document.getElementById('chat-container');
        const messageInput = document.getElementById('message-input');
 
        function join() {
            const username = document.getElementById('username').value;
            if (username.trim()) {
                socket.emit('join', username);
                document.getElementById('username').disabled = true;
            }
        }
 
        function sendMessage() {
            const message = messageInput.value;
            if (message.trim()) {
                socket.emit('message', message);
                messageInput.value = '';
            }
        }
 
        socket.on('user-joined', (username) => {
            addMessage(`์‹œ์Šคํ…œ: ${username}์ด ์ž…์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.`);
        });
 
        socket.on('user-left', (username) => {
            addMessage(`์‹œ์Šคํ…œ: ${username}์ด ํ‡ด์žฅํ–ˆ์Šต๋‹ˆ๋‹ค.`);
        });
 
        socket.on('message', (data) => {
            addMessage(`${data.username}: ${data.message} (${data.time})`);
        });
 
        function addMessage(message) {
            const div = document.createElement('div');
            div.textContent = message;
            chatContainer.appendChild(div);
            chatContainer.scrollTop = chatContainer.scrollHeight;
        }
 
        messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</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(/<script>/gi, '');
 
    io.emit('message', {
        username: socket.username,
        message: sanitizedMessage
    });
});

2) ์ธ์ฆ

// ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ
socket.on('authenticate', (token) => {
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        socket.userId = decoded.userId;
        socket.emit('authenticated');
    } catch (error) {
        socket.emit('error', { message: '์ธ์ฆ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.' });
    }
});

9. ๋””๋ฒ„๊น… ๋ฐ ๋กœ๊น…

1) ๋””๋ฒ„๊ทธ ๋ชจ๋“œ ํ™œ์„ฑํ™”

// ์„œ๋ฒ„ ์ธก ๋””๋ฒ„๊ทธ ํ™œ์„ฑํ™”
const io = socketIo(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"]
    },
    debug: true
});

2) ๋กœ๊น…

// ์—ฐ๊ฒฐ ๋กœ๊น…
io.on('connection', (socket) => {
    console.log(`์ƒˆ๋กœ์šด ์—ฐ๊ฒฐ: ${socket.id}`);
 
    socket.on('disconnect', () => {
        console.log(`์—ฐ๊ฒฐ ํ•ด์ œ: ${socket.id}`);
    });
 
    // ๋ชจ๋“  ์ด๋ฒคํŠธ ๋กœ๊น…
    socket.onAny((eventName, ...args) => {
        console.log(`์ด๋ฒคํŠธ ${eventName}:`, args);
    });
});

10. ๋‹ค์Œ ๋‹จ๊ณ„

Socket.IO ๊ธฐ๋ณธ์„ ๋ฐฐ์› ๋‹ค๋ฉด ๋‹ค์Œ์„ ํ•™์Šตํ•ด๋ณด์„ธ์š”:

  • ์‹ค์‹œ๊ฐ„ ๊ฒŒ์ž„ ๊ฐœ๋ฐœ - ๋ฐ”๋‘‘, ์ฒด์Šค, ํ‹ฑํƒํ†  ๋“ฑ
  • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์‹œ์Šคํ…œ - ๊ฐœ์ธ ๋ฉ”์‹œ์ง€, ๊ทธ๋ฃน ์ฑ„ํŒ…
  • ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ - ํ‘ธ์‹œ ์•Œ๋ฆผ, ์ด๋ฉ”์ผ ์•Œ๋ฆผ
  • ์‹ค์‹œ๊ฐ„ ๋Œ€์‹œ๋ณด๋“œ - ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”, ๋ชจ๋‹ˆํ„ฐ๋ง
  • ํ˜‘์—… ๋„๊ตฌ - ์‹ค์‹œ๊ฐ„ ๋ฌธ์„œ ํŽธ์ง‘, ํ™”๋ฉด ๊ณต์œ 

์ถ”์ฒœ ํ•™์Šต ์ˆœ์„œ:

โ€” ์ด ํŽ˜์ด์ง€๋Š” ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

wiki/it/dream_of_enc/metaverse/socketio.txt ยท ๋งˆ์ง€๋ง‰์œผ๋กœ ์ˆ˜์ •๋จ: (๋ฐ”๊นฅ ํŽธ์ง‘)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki