목차

🔌 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'
    }
});

방 접근 제어:

io.on('connection', (socket) =
 {
    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();
    };
}, []);

📚 관련 문서