📚 TUẦN 3: WEBSOCKET & GATEWAY BASICS
🎯 Mục tiêu tuần này: Hiểu sâu WebSocket protocol, Socket.io, và NestJS Gateway pattern
1. CHỨC NĂNG CẦN HOÀN THÀNH
| STT | Chức năng | Mô tả | Output |
|---|---|---|---|
| 3.1 | Setup Socket.io | Cài đặt và config WebSocket Gateway | Connection works |
| 3.2 | GameGateway | Tạo gateway cho game module | Events có thể emit/receive |
| 3.3 | Room Concept | Join/Leave room logic | Client vào được room |
| 3.4 | Event Broadcasting | Gửi message cho room members | Real-time message delivery |
| 3.5 | Simple Chat Demo | Chat room để verify concepts | 2 clients chat được |
2. KIẾN THỨC CẦN NẮM VỮNG
2.1 HTTP vs WebSocket
📖 So sánh Protocol
| Đặc điểm | HTTP | WebSocket |
|---|---|---|
| Connection | Request → Response → Close | Persistent (giữ mở) |
| Communication | Half-duplex (1 chiều tại 1 thời điểm) | Full-duplex (2 chiều đồng thời) |
| Initiation | Client luôn là bên bắt đầu | Cả 2 bên có thể gửi bất kỳ lúc nào |
| Overhead | Header lớn mỗi request | Handshake 1 lần, sau đó minimal |
| Use case | REST APIs, web pages | Real-time: chat, games, notifications |
Flow comparison:
HTTP:
Client ──Request──> Server
Client <──Response── Server
(Connection closed)
WebSocket:
Client ══════════════════════════ Server
←── Message ───→
←── Message ───→
(Connection stays open)
Tại sao Quiz App cần WebSocket?
- Host gửi câu hỏi → Tất cả Players nhận ngay lập tức
- Players trả lời → Host thấy real-time
- Leaderboard update → Mọi người thấy đồng thời
- HTTP polling = lag + waste resources
2.2 Socket.io Concepts
📖 Core Terms
| Term | Mô tả | Ví dụ |
|---|---|---|
| Socket | 1 connection từ client | Mỗi browser tab = 1 socket |
| Server | Socket.io server instance | NestJS Gateway |
| Room | Nhóm sockets | 1 phòng quiz |
| Namespace | Isolated channel | /admin, /game |
| Event | Message type | 'joinRoom', 'newQuestion' |
📖 Room Concept
Khái niệm:
- Room = logical grouping của sockets
- 1 socket có thể join nhiều rooms
- Gửi message cho cả room:
server.to('room').emit()
// Join room
socket.join('room:123456'); // PIN = 123456
// Leave room
socket.leave('room:123456');
// Rooms của 1 socket
console.log(socket.rooms); // Set { socket.id, 'room:123456' }
📖 Namespace vs Room
Namespace (/game)
├── Room A (quiz:123)
│ ├── Socket 1
│ ├── Socket 2
│ └── Socket 3
└── Room B (quiz:456)
├── Socket 4
└── Socket 5
Khi nào dùng?
- Namespace: Tách biệt logic hoàn toàn (admin panel vs game)
- Room: Group sockets trong cùng namespace (players trong 1 quiz)
2.3 NestJS Gateway Pattern
📖 Gateway
Khái niệm:
- Gateway = WebSocket equivalent của Controller
- Handle WebSocket connections và events
- Decorate với
@WebSocketGateway()
Core Component:
import {
WebSocketGateway,
WebSocketServer,
SubscribeMessage,
OnGatewayConnection,
OnGatewayDisconnect,
ConnectedSocket,
MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
@WebSocketGateway({
cors: { origin: '*' }, // CORS config
namespace: '/game', // Optional namespace
})
export class GameGateway implements OnGatewayConnection, OnGatewayDisconnect {
@WebSocketServer()
server: Server; // Socket.io server instance
// Called when client connects
handleConnection(client: Socket) {
console.log(`Client connected: ${client.id}`);
}
// Called when client disconnects
handleDisconnect(client: Socket) {
console.log(`Client disconnected: ${client.id}`);
}
// Handle custom event
@SubscribeMessage('joinRoom')
handleJoinRoom(
@ConnectedSocket() client: Socket,
@MessageBody() data: { pin: string; nickname: string },
) {
const roomId = `room:${data.pin}`;
client.join(roomId);
// Notify others in room
client.to(roomId).emit('playerJoined', {
nickname: data.nickname,
});
return { success: true, roomId };
}
}
2.4 Event Patterns
📖 Emit Types
// 1. Emit to sender only
client.emit('event', data);
// 2. Broadcast to everyone EXCEPT sender
client.broadcast.emit('event', data);
// 3. Emit to everyone in a room
this.server.to('room:123').emit('event', data);
// 4. Emit to everyone (including sender)
this.server.emit('event', data);
// 5. Emit to specific socket
this.server.to(socketId).emit('event', data);
Visual:
Room: quiz:123
┌─────────────────────────────────────────┐
│ Socket A (sender) Socket B Socket C │
└─────────────────────────────────────────┘
client.emit() → A only
client.broadcast → B, C
server.to('quiz:123') → A, B, C
client.to('quiz:123') → B, C (not A)
📖 Acknowledgement
// Server side
@SubscribeMessage('checkAnswer')
handleCheckAnswer(@MessageBody() data: any) {
const isCorrect = this.checkAnswer(data);
return { correct: isCorrect, score: 100 }; // Automatically acknowledged
}
// Client side (JS)
socket.emit('checkAnswer', { questionId: 1, answer: 2 }, (response) => {
console.log(response); // { correct: true, score: 100 }
});