Введение
WebSocket — это протокол для создания постоянного двустороннего соединения между клиентом и сервером, который позволяет обмениваться данными в реальном времени. В отличие от REST API, который работает по модели "запрос-ответ", WebSocket обеспечивает постоянное соединение для real-time коммуникации.
В этом туториале мы создадим WebSocket Mock сервер за 15 минут, настроим Socket.io для клиент-серверной коммуникации, реализуем обработку сообщений и научимся тестировать WebSocket соединения.
✅ Что вы узнаете:
- ✅ Различия между WebSocket и REST API
- ✅ Как создать Mock WebSocket сервер
- ✅ Интеграция Socket.io для real-time коммуникации
- ✅ Обработка входящих и исходящих сообщений
- ✅ Тестирование WebSocket соединений
- ✅ Обработка ошибок и переподключение
- ✅ Best practices для WebSocket API
💡 Что такое WebSocket?
WebSocket — это протокол связи, который обеспечивает полноценное двустороннее соединение между клиентом и сервером через одно TCP соединение. Это позволяет серверу отправлять данные клиенту без необходимости запроса от клиента.
📋 Содержание
WebSocket vs REST: когда что использовать? 🤔
WebSocket и REST API решают разные задачи. Вот основные различия:
| Критерий | REST API | WebSocket |
|---|---|---|
| Протокол | HTTP/HTTPS | WS/WSS (WebSocket) |
| Соединение | Временное (запрос-ответ) | Постоянное (двустороннее) |
| Коммуникация | Односторонняя (клиент → сервер) | Двусторонняя (клиент ↔ сервер) |
| Overhead | Высокий (заголовки HTTP) | Низкий (минимальные заголовки) |
| Real-time | Нет (нужны запросы) | Да (сервер может отправлять данные) |
| Использование | CRUD операции, статические данные | Чат, уведомления, real-time обновления |
| Сложность | Простая | Средняя |
✅ Используйте REST API когда:
- Нужны стандартные CRUD операции
- Данные запрашиваются по требованию
- Не нужна real-time коммуникация
- Простота важнее производительности
✅ Используйте WebSocket когда:
- Нужна real-time коммуникация (чат, уведомления)
- Сервер должен отправлять данные клиенту
- Частые обновления данных
- Низкая задержка критична
Шаг 1: Настройка проекта 📦
Создадим простой Node.js проект для WebSocket Mock сервера.
Инициализация проекта:
# Создайте новую директорию
mkdir websocket-mock-server
cd websocket-mock-server
# Инициализируйте npm проект
npm init -y
# Установите зависимости
npm install express socket.io ws cors
npm install -D @types/node @types/ws nodemon typescript
Структура проекта:
websocket-mock-server/
├── src/
│ ├── server.ts # WebSocket сервер
│ ├── handlers/ # Обработчики сообщений
│ └── types.ts # TypeScript типы
├── package.json
└── tsconfig.json
Шаг 2: Создание Mock WebSocket сервера 🚀
Создадим простой WebSocket сервер используя нативный WebSocket API Node.js.
// src/server.ts
import WebSocket from 'ws';
import http from 'http';
const server = http.createServer();
const wss = new WebSocket.Server({ server });
// Хранилище подключений
const clients = new Map();
// Обработка подключения
wss.on('connection', (ws: WebSocket, req: http.IncomingMessage) => {
const clientId = generateClientId();
clients.set(clientId, ws);
console.log(`Client connected: ${clientId}`);
// Отправка приветственного сообщения
ws.send(JSON.stringify({
type: 'welcome',
message: 'Connected to WebSocket Mock Server',
clientId
}));
// Обработка входящих сообщений
ws.on('message', (data: WebSocket.Data) => {
try {
const message = JSON.parse(data.toString());
handleMessage(clientId, message, ws);
} catch (error) {
ws.send(JSON.stringify({
type: 'error',
message: 'Invalid JSON format'
}));
}
});
// Обработка отключения
ws.on('close', () => {
clients.delete(clientId);
console.log(`Client disconnected: ${clientId}`);
});
// Обработка ошибок
ws.on('error', (error) => {
console.error(`WebSocket error: ${error}`);
clients.delete(clientId);
});
});
function generateClientId(): string {
return `client-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
function handleMessage(clientId: string, message: any, ws: WebSocket) {
switch (message.type) {
case 'ping':
ws.send(JSON.stringify({ type: 'pong', timestamp: Date.now() }));
break;
case 'echo':
ws.send(JSON.stringify({
type: 'echo',
data: message.data,
timestamp: Date.now()
}));
break;
case 'broadcast':
// Отправка всем подключенным клиентам
broadcast(message.data, clientId);
break;
default:
ws.send(JSON.stringify({
type: 'error',
message: 'Unknown message type'
}));
}
}
function broadcast(data: any, senderId: string) {
const message = JSON.stringify({
type: 'broadcast',
data,
senderId,
timestamp: Date.now()
});
clients.forEach((ws, clientId) => {
if (clientId !== senderId) {
ws.send(message);
}
});
}
const PORT = process.env.PORT || 3001;
server.listen(PORT, () => {
console.log(`WebSocket Mock Server running on ws://localhost:${PORT}`);
});
Шаг 3: Интеграция Socket.io 🔌
Socket.io — популярная библиотека для real-time коммуникации. Она предоставляет удобный API и автоматически обрабатывает переподключение.
// src/server-socketio.ts
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
import cors from 'cors';
const app = express();
app.use(cors());
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: '*',
methods: ['GET', 'POST']
}
});
// Обработка подключения
io.on('connection', (socket) => {
console.log(`Client connected: ${socket.id}`);
// Отправка приветственного сообщения
socket.emit('welcome', {
message: 'Connected to Socket.io Mock Server',
clientId: socket.id
});
// Обработка входящих сообщений
socket.on('message', (data) => {
console.log(`Message from ${socket.id}:`, data);
// Echo сообщения обратно
socket.emit('message-response', {
...data,
timestamp: Date.now(),
serverId: socket.id
});
});
// Обработка ping
socket.on('ping', () => {
socket.emit('pong', { timestamp: Date.now() });
});
// Broadcast сообщения
socket.on('broadcast', (data) => {
socket.broadcast.emit('broadcast', {
...data,
senderId: socket.id,
timestamp: Date.now()
});
});
// Присоединение к комнате
socket.on('join-room', (roomId: string) => {
socket.join(roomId);
socket.emit('joined-room', { roomId });
socket.to(roomId).emit('user-joined', { userId: socket.id });
});
// Отправка в комнату
socket.on('room-message', ({ roomId, message }) => {
socket.to(roomId).emit('room-message', {
message,
senderId: socket.id,
timestamp: Date.now()
});
});
// Обработка отключения
socket.on('disconnect', () => {
console.log(`Client disconnected: ${socket.id}`);
});
});
const PORT = process.env.PORT || 3001;
httpServer.listen(PORT, () => {
console.log(`Socket.io Mock Server running on http://localhost:${PORT}`);
});
Шаг 4: Обработка сообщений 💬
Реализуем различные типы сообщений для разных сценариев использования.
// src/handlers/messageHandler.ts
import { Socket } from 'socket.io';
interface Message {
type: string;
data?: any;
timestamp?: number;
}
export class MessageHandler {
private socket: Socket;
constructor(socket: Socket) {
this.socket = socket;
}
// Обработка текстовых сообщений
handleTextMessage(message: Message) {
this.socket.emit('text-response', {
...message,
timestamp: Date.now(),
status: 'delivered'
});
}
// Обработка JSON данных
handleJsonMessage(message: Message) {
this.socket.emit('json-response', {
original: message.data,
processed: this.processData(message.data),
timestamp: Date.now()
});
}
// Обработка событий
handleEvent(event: Message) {
switch (event.type) {
case 'user-typing':
this.socket.broadcast.emit('user-typing', {
userId: this.socket.id,
...event.data
});
break;
case 'user-online':
this.socket.broadcast.emit('user-online', {
userId: this.socket.id,
...event.data
});
break;
default:
this.socket.emit('error', {
message: 'Unknown event type',
type: event.type
});
}
}
private processData(data: any): any {
// Пример обработки данных
if (typeof data === 'object') {
return {
...data,
processed: true,
processedAt: Date.now()
};
}
return data;
}
}
// Использование в сервере
io.on('connection', (socket) => {
const handler = new MessageHandler(socket);
socket.on('text-message', (message) => {
handler.handleTextMessage(message);
});
socket.on('json-message', (message) => {
handler.handleJsonMessage(message);
});
socket.on('event', (event) => {
handler.handleEvent(event);
});
});
Шаг 5: Тестирование WebSocket 🧪
Напишем тесты для проверки WebSocket соединения и обработки сообщений.
// tests/websocket.test.ts
import { io as Client } from 'socket.io-client';
import { Server } from 'socket.io';
import { createServer } from 'http';
describe('WebSocket Mock Server', () => {
let server: Server;
let httpServer: any;
beforeAll((done) => {
httpServer = createServer();
server = new Server(httpServer);
server.on('connection', (socket) => {
socket.on('ping', () => {
socket.emit('pong');
});
socket.on('echo', (data) => {
socket.emit('echo-response', data);
});
});
httpServer.listen(() => {
done();
});
});
afterAll(() => {
server.close();
httpServer.close();
});
test('should connect to server', (done) => {
const client = Client('http://localhost:3001');
client.on('connect', () => {
expect(client.connected).toBe(true);
client.disconnect();
done();
});
});
test('should receive pong on ping', (done) => {
const client = Client('http://localhost:3001');
client.on('connect', () => {
client.emit('ping');
client.on('pong', () => {
expect(true).toBe(true);
client.disconnect();
done();
});
});
});
test('should echo message', (done) => {
const client = Client('http://localhost:3001');
const testMessage = { text: 'Hello, WebSocket!' };
client.on('connect', () => {
client.emit('echo', testMessage);
client.on('echo-response', (data) => {
expect(data).toEqual(testMessage);
client.disconnect();
done();
});
});
});
});
Шаг 6: Интеграция с фронтендом 🌐
Подключим WebSocket клиент к фронтенд приложению (React пример).
// src/hooks/useWebSocket.ts
import { useEffect, useState, useRef } from 'react';
import { io, Socket } from 'socket.io-client';
interface UseWebSocketReturn {
socket: Socket | null;
connected: boolean;
messages: any[];
sendMessage: (message: any) => void;
}
export function useWebSocket(url: string): UseWebSocketReturn {
const [socket, setSocket] = useState(null);
const [connected, setConnected] = useState(false);
const [messages, setMessages] = useState([]);
const socketRef = useRef(null);
useEffect(() => {
// Создание подключения
const newSocket = io(url, {
reconnection: true,
reconnectionDelay: 1000,
reconnectionAttempts: 5
});
socketRef.current = newSocket;
setSocket(newSocket);
// Обработка подключения
newSocket.on('connect', () => {
setConnected(true);
console.log('Connected to WebSocket server');
});
// Обработка отключения
newSocket.on('disconnect', () => {
setConnected(false);
console.log('Disconnected from WebSocket server');
});
// Обработка сообщений
newSocket.on('message', (data) => {
setMessages(prev => [...prev, data]);
});
// Обработка ошибок
newSocket.on('error', (error) => {
console.error('WebSocket error:', error);
});
// Cleanup при размонтировании
return () => {
newSocket.close();
};
}, [url]);
const sendMessage = (message: any) => {
if (socketRef.current && connected) {
socketRef.current.emit('message', message);
}
};
return { socket, connected, messages, sendMessage };
}
// Использование в компоненте
function ChatComponent() {
const { connected, messages, sendMessage } = useWebSocket('http://localhost:3001');
const [input, setInput] = useState('');
const handleSend = () => {
if (input.trim()) {
sendMessage({ text: input, type: 'text' });
setInput('');
}
};
return (
Status: {connected ? 'Connected' : 'Disconnected'}
{messages.map((msg, idx) => (
{msg.text}
))}
setInput(e.target.value)} />
);
}
Шаг 7: Обработка ошибок и переподключение 🔄
Реализуем надёжную обработку ошибок и автоматическое переподключение.
// src/utils/reconnection.ts
import { Socket } from 'socket.io-client';
export class WebSocketReconnection {
private socket: Socket;
private maxReconnectAttempts: number;
private reconnectAttempts: number;
private reconnectDelay: number;
constructor(socket: Socket, maxAttempts: number = 5, delay: number = 1000) {
this.socket = socket;
this.maxReconnectAttempts = maxAttempts;
this.reconnectAttempts = 0;
this.reconnectDelay = delay;
}
setupReconnection() {
this.socket.on('disconnect', () => {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts);
setTimeout(() => {
this.reconnectAttempts++;
console.log(`Reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
this.socket.connect();
}, delay);
} else {
console.error('Max reconnection attempts reached');
}
});
this.socket.on('connect', () => {
this.reconnectAttempts = 0; // Reset при успешном подключении
console.log('Reconnected successfully');
});
}
}
// Использование
const socket = io('http://localhost:3001');
const reconnection = new WebSocketReconnection(socket);
reconnection.setupReconnection();
Best Practices для WebSocket API 🌟
✅ Рекомендации:
- Используйте Socket.io для упрощения работы с WebSocket
- Реализуйте переподключение для надёжности соединения
- Валидируйте сообщения на стороне сервера
- Используйте комнаты для групповой коммуникации
- Логируйте события для отладки
- Обрабатывайте ошибки корректно
- Используйте TypeScript для типобезопасности
- Тестируйте WebSocket соединения
⚠️ Важные моменты:
- WebSocket требует постоянное соединение, что может быть ресурсоёмко
- Некоторые прокси и балансировщики могут закрывать соединения
- Нужно обрабатывать разрывы соединения
- Firewall может блокировать WebSocket соединения
Заключение
WebSocket — мощный инструмент для real-time коммуникации. В этом туториале мы создали Mock WebSocket сервер, настроили Socket.io, реализовали обработку сообщений и научились тестировать WebSocket соединения.
💡 Что мы сделали:
- ✅ Создали Mock WebSocket сервер на Node.js
- ✅ Интегрировали Socket.io для упрощения работы
- ✅ Реализовали обработку различных типов сообщений
- ✅ Написали тесты для WebSocket соединений
- ✅ Подключили WebSocket клиент к фронтенду
- ✅ Добавили обработку ошибок и переподключение
Создайте Mock API за 2 минуты
Независимо от типа API (REST, GraphQL), вы можете создать Mock API с LightBox API и начать разработку frontend без ожидания backend. Поддерживает все популярные протоколы и форматы данных.
Попробовать бесплатно →