Админка
please wait

Introdução

Os WebSockets permitem ligações bidirecionais e persistentes entre clientes e servidores, essenciais para funcionalidades em tempo real, como chat, notificações, atualizações em direto e edição colaborativa. Ao contrário do modelo pedido-resposta do HTTP, os WebSockets permitem que o servidor envie dados para os clientes instantaneamente. Este guia aborda padrões práticos de implementação de WebSockets.

Noções Básicas de WebSocket

Como Funcionam os WebSockets

  1. O cliente inicia um pedido de upgrade HTTP
  2. O servidor aceita e faz upgrade da ligação
  3. Inicia-se a comunicação bidirecional
  4. Qualquer uma das partes pode enviar mensagens a qualquer momento
  5. A ligação mantém-se até ser explicitamente encerrada

Quando Usar WebSockets

Usar WebSocketsUsar HTTP/REST
Atualizações em tempo realDados em modelo pedido-resposta
Aplicações de chatOperações CRUD
Notificações em diretoUploads de ficheiros
Edição colaborativaAtualizações pouco frequentes
JogosConteúdo estático

Node.js com Socket.IO

Configuração do Servidor

// 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'],
},
});
// Tratamento da ligação
io.on('connection', (socket) => {
console.log('Client connected:', socket.id);
// Tratar eventos do cliente
socket.on('message', (data) => {
console.log('Message received:', data);
// Difundir para todos os clientes
io.emit('message', data);
// Ou enviar para uma room específica
// io.to('room1').emit('message', data);
});
// Entrar na room
socket.on('join_room', (room) => {
socket.join(room);
console.log(`${socket.id} joined ${room}`);
socket.to(room).emit('user_joined', { userId: socket.id });
});
// Sair da room
socket.on('leave_room', (room) => {
socket.leave(room);
socket.to(room).emit('user_left', { userId: socket.id });
});
// Tratamento da desconexão
socket.on('disconnect', () => {
console.log('Client disconnected:', socket.id);
});
});
httpServer.listen(3001, () => {
console.log('Server running on port 3001');
});

Cliente (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;
// Entrar na room
socket.emit('join_room', room);
// Escutar mensagens
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 com Ratchet

Configuração do Servidor

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);
// Notificar os restantes
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);
// Remover de todas as 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

Configuração

// 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,
],
],
],

Difusão de Eventos

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

Difusão a partir do 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);
}

Cliente com 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,
});
// Escutar no canal privado
echo.private(`chat.${roomId}`)
.listen('MessageSent', (e) => {
console.log('New message:', e);
addMessage(e);
});
// Canal de presença (mostra quem está 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);
});

Autenticação

Socket.IO com JWT

// Servidor
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'));
}
});
// Cliente
const socket = io(SOCKET_URL, {
auth: {
token: localStorage.getItem('token'),
},
});

Escalabilidade com Redis

Socket.IO com 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));

Isto permite que vários servidores Socket.IO partilhem eventos.

Tratamento de Erros e Re-ligação

// Cliente
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');
// Mostrar erro ao utilizador
});

Boas Práticas

  1. Use rooms para mensagens direcionadas em vez de difundir para todos
  2. Implemente heartbeats para detetar ligações obsoletas
  3. Adicione autenticação para evitar acesso não autorizado
  4. Trate a re-ligação de forma adequada no cliente
  5. Use Redis adapter para escalabilidade horizontal
  6. Limite o tamanho das mensagens para evitar abusos
  7. Implemente rate limiting no envio de mensagens

Conclusão

Os WebSockets permitem comunicação em tempo real, essencial para aplicações modernas. Use Socket.IO para compatibilidade entre browsers, Laravel Broadcasting para aplicações Laravel, ou Ratchet para servidores PHP. Implemente autenticação adequada, tratamento de erros e estratégias de escalabilidade para uso em produção.

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