Админка
please wait

Introduction

WebSockets enable bidirectional, persistent connections between clients and servers, essential for real-time features like chat, notifications, live updates, and collaborative editing. Unlike HTTP's request-response model, WebSockets allow the server to push data to clients instantly. This guide covers practical WebSocket implementation patterns.

WebSocket Basics

How WebSockets Work

  1. Client initiates an HTTP upgrade request
  2. Server accepts and upgrades the connection
  3. Bidirectional communication begins
  4. Either party can send messages anytime
  5. Connection persists until explicitly closed

When to Use WebSockets

Use WebSocketsUse HTTP/REST
Real-time updatesRequest-response data
Chat applicationsCRUD operations
Live notificationsFile uploads
Collaborative editingInfrequent updates
GamingStatic content

Node.js with Socket.IO

Server Setup

// server.js
import express from 'express';
import { createServer } from 'http';
import { Server } from 'socket.io';
const app = express();
const httpServer = createServer(app);
const io = new Server(httpServer, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST'],
},
});
// Connection handling
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
// Handle events from the client
socket.on('message', (data) => {
console.log('Message received:', data);
// Broadcast to all clients
io.emit('message', data);
// Or send to a specific room
// io.to('room1').emit('message', data);
});
// Join room
socket.on('join_room', (room) => {
socket.join(room);
console.log(`${socket.id} joined ${room}`);
socket.to(room).emit('user_joined', { userId: socket.id });
});
// Leave room
socket.on('leave_room', (room) => {
socket.leave(room);
socket.to(room).emit('user_left', { userId: socket.id });
});
// Disconnect handling
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
httpServer.listen(3001, () => {
console.log('Server running on port 3001');
});

Client (React)

// hooks/useSocket.js
import { useEffect, useState, useCallback } from 'react';
import { io } from 'socket.io-client';
const SOCKET_URL = 'http://localhost:3001';
export function useSocket() {
const [socket, setSocket] = useState(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const socketInstance = io(SOCKET_URL, {
transports: ['websocket'],
});
socketInstance.on('connect', () => {
setIsConnected(true);
console.log('Connected to server');
});
socketInstance.on('disconnect', () => {
setIsConnected(false);
console.log('Disconnected from server');
});
setSocket(socketInstance);
return () => {
socketInstance.disconnect();
};
}, []);
const sendMessage = useCallback((event, data) => {
if (socket) {
socket.emit(event, data);
}
}, [socket]);
return { socket, isConnected, sendMessage };
}
// components/Chat.jsx
import { useState, useEffect } from 'react';
import { useSocket } from '../hooks/useSocket';
export function Chat({ room }) {
const { socket, isConnected, sendMessage } = useSocket();
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
useEffect(() => {
if (!socket) return;
// Join room
socket.emit('join_room', room);
// Listen for messages
socket.on('message', (message) => {
setMessages((prev) => [...prev, message]);
});
socket.on('user_joined', ({ userId }) => {
setMessages((prev) => [...prev, { system: true, text: `${userId} joined` }]);
});
return () => {
socket.emit('leave_room', room);
socket.off('message');
socket.off('user_joined');
};
}, [socket, room]);
const handleSend = () => {
if (input.trim()) {
sendMessage('message', { room, text: input, timestamp: Date.now() });
setInput('');
}
};
return (
<div className="chat">
<div className="status">
{isConnected ? '🟢 Connected' : '🔴 Disconnected'}
</div>
<div className="messages">
{messages.map((msg, i) => (
<div key={i} className={msg.system ? 'system' : 'message'}>
{msg.text}
</div>
))}
</div>
<div className="input">
<input
value={input}
onChange={(e) => setInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
placeholder="Type a message..."
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
}

PHP with Ratchet

Server Setup

composer require cboden/ratchet
// src/ChatServer.php
<?php
namespace App;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface
{
protected $clients;
protected $rooms = [];
public function __construct()
{
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn)
{
$this->clients->attach($conn);
echo "New connection: {$conn->resourceId}\n";
}
public function onMessage(ConnectionInterface $from, $msg)
{
$data = json_decode($msg, true);
switch ($data['type']) {
case 'join':
$this->joinRoom($from, $data['room']);
break;
case 'message':
$this->broadcastToRoom($from, $data['room'], $data);
break;
}
}
protected function joinRoom(ConnectionInterface $conn, $room)
{
if (!isset($this->rooms[$room])) {
$this->rooms[$room] = new \SplObjectStorage;
}
$this->rooms[$room]->attach($conn);
// Notify others
foreach ($this->rooms[$room] as $client) {
if ($client !== $conn) {
$client->send(json_encode([
'type' => 'user_joined',
'userId' => $conn->resourceId,
]));
}
}
}
protected function broadcastToRoom(ConnectionInterface $from, $room, $data)
{
if (!isset($this->rooms[$room])) return;
$message = json_encode($data);
foreach ($this->rooms[$room] as $client) {
$client->send($message);
}
}
public function onClose(ConnectionInterface $conn)
{
$this->clients->detach($conn);
// Remove from all rooms
foreach ($this->rooms as $room => $clients) {
if ($clients->contains($conn)) {
$clients->detach($conn);
}
}
echo "Connection closed: {$conn->resourceId}\n";
}
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "Error: {$e->getMessage()}\n";
$conn->close();
}
}
// bin/server.php
<?php
require __DIR__ . '/../vendor/autoload.php';
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use App\ChatServer;
$server = IoServer::factory(
new HttpServer(
new WsServer(
new ChatServer()
)
),
8080
);
echo "WebSocket server running on port 8080\n";
$server->run();

Laravel Broadcasting

Configuration

// config/broadcasting.php
'connections' => [
'pusher' => [
'driver' => 'pusher',
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER'),
'useTLS' => true,
],
],
],

Event Broadcasting

// app/Events/MessageSent.php
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Queue\SerializesModels;
class MessageSent implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $message;
public $user;
public function __construct($message, $user)
{
$this->message = $message;
$this->user = $user;
}
public function broadcastOn()
{
return new PrivateChannel('chat.' . $this->message->room_id);
}
public function broadcastWith()
{
return [
'id' => $this->message->id,
'text' => $this->message->text,
'user' => [
'id' => $this->user->id,
'name' => $this->user->name,
],
'created_at' => $this->message->created_at->toIso8601String(),
];
}
}

Broadcasting from Controller

// app/Http/Controllers/ChatController.php
public function sendMessage(Request $request)
{
$message = Message::create([
'room_id' => $request->room_id,
'user_id' => auth()->id(),
'text' => $request->text,
]);
broadcast(new MessageSent($message, auth()->user()))->toOthers();
return response()->json($message);
}

Client with Laravel Echo

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
const echo = new Echo({
broadcaster: 'pusher',
key: process.env.PUSHER_APP_KEY,
cluster: process.env.PUSHER_APP_CLUSTER,
forceTLS: true,
});
// Listen on a private channel
echo.private(`chat.${roomId}`)
.listen('MessageSent', (e) => {
console.log('New message:', e);
addMessage(e);
});
// Presence channel (shows who's online)
echo.join(`chat.${roomId}`)
.here((users) => {
console.log('Users in room:', users);
})
.joining((user) => {
console.log('User joined:', user);
})
.leaving((user) => {
console.log('User left:', user);
});

Authentication

Socket.IO with JWT

// Server
io.use((socket, next) => {
const token = socket.handshake.auth.token;
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
socket.user = user;
next();
} catch (err) {
next(new Error('Authentication failed'));
}
});
// Client
const socket = io(SOCKET_URL, {
auth: {
token: localStorage.getItem('token'),
},
});

Scaling with Redis

Socket.IO with Redis Adapter

import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const pubClient = createClient({ url: 'redis://localhost:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));

This allows multiple Socket.IO servers to share events.

Error Handling and Reconnection

// Client
const socket = io(SOCKET_URL, {
reconnection: true,
reconnectionAttempts: 5,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 20000,
});
socket.on('connect_error', (error) => {
console.error('Connection error:', error);
});
socket.on('reconnect', (attemptNumber) => {
console.log('Reconnected after', attemptNumber, 'attempts');
});
socket.on('reconnect_failed', () => {
console.error('Reconnection failed');
// Show the error to the user
});

Best Practices

  1. Use rooms for targeted messaging instead of broadcasting to all
  2. Implement heartbeats to detect stale connections
  3. Add authentication to prevent unauthorized access
  4. Handle reconnection gracefully on the client
  5. Use Redis adapter for horizontal scaling
  6. Limit message size to prevent abuse
  7. Implement rate limiting for message sending

Conclusion

WebSockets enable real-time communication essential for modern applications. Use Socket.IO for cross-browser compatibility, Laravel Broadcasting for Laravel applications, or Ratchet for PHP servers. Implement proper authentication, error handling, and scaling strategies for production use.

 
 
 
Языки
Темы
Copyright © 1999 — 2026
ZK Interactive