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(); }; }, []);