DevToolBoxฟรี
บล็อก

WebSocket Complete Guide: Real-Time Communication with ws and Socket.io

12 min readโดย DevToolBox

TL;DR

WebSocket provides a persistent, bidirectional channel over a single TCP connection — unlike HTTP which is request-response. Use ws:// or wss:// (secure) with the native browserWebSocket API, the ws library for Node.js servers, or Socket.io when you need rooms, reconnection, and fallbacks. Authenticate via JWT in the first message or query param. Scale horizontally with sticky sessions + Redis Pub/Sub. Implement exponential backoff reconnection for robustness. Use our online WebSocket tester to debug connections instantly.

WebSocket vs HTTP — Persistent Bidirectional Connection Explained

HTTP was designed for the document web: a client asks, a server answers, and the conversation ends. Each request is independent and stateless. This model works well for fetching pages, loading assets, or calling REST APIs — but it breaks down when the server needs to push data to clients without being asked first.

WebSocket solves this with a persistent connection. After an HTTP upgrade handshake, the TCP connection stays open and becomes a bidirectional channel. Both sides can send frames at any time with extremely low overhead (as little as 2 bytes of framing overhead per message vs hundreds of bytes of HTTP headers).

FeatureHTTP/1.1WebSocket
ConnectionShort-lived (keep-alive is optional)Persistent until closed
DirectionClient initiates (request-response)Full-duplex (either side can send)
Overhead per message~500–1000 bytes (headers)2–10 bytes (framing)
Server pushPolling or SSE onlyNative, zero-latency
Protocolhttp:// / https://ws:// / wss://
Best use casesCRUD APIs, file downloads, page loadsChat, live feeds, gaming, collaboration

The upgrade handshake is critical to understand. The client sends a normal HTTP GET with special headers:

# Client sends HTTP Upgrade request:
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

# Server responds with 101 Switching Protocols:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

# From this point, the TCP connection carries WebSocket frames — not HTTP

Use wss:// (WebSocket Secure) in production — it tunnels WebSocket over TLS exactly like HTTPS does for HTTP. Plain ws:// should only be used in local development.

Browser WebSocket API — onopen, onmessage, readyState, send()

Every modern browser ships with a native WebSocket API. No libraries required for the client side.

// 1. Open a connection
const ws = new WebSocket('wss://example.com/ws');

// 2. Connection established
ws.onopen = () => {
  console.log('Connected, readyState:', ws.readyState); // 1 = OPEN
  ws.send('Hello server!');
  // Send JSON
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }));
};

// 3. Receive messages from server
ws.onmessage = (event) => {
  const data = event.data;          // string or Blob (binary)
  const msg = JSON.parse(data);     // parse JSON messages
  console.log('Received:', msg);
};

// 4. Connection closed
ws.onclose = (event) => {
  console.log('Closed:', event.code, event.reason);
  // code 1000 = normal, 1006 = abnormal (network failure)
};

// 5. Error
ws.onerror = (error) => {
  console.error('WebSocket error:', error);
  // Note: always followed by onclose
};

// 6. readyState values
// ws.CONNECTING = 0   (upgrade in progress)
// ws.OPEN       = 1   (ready to send/receive)
// ws.CLOSING    = 2   (close handshake in progress)
// ws.CLOSED     = 3   (connection closed)

// 7. Close gracefully
ws.close(1000, 'User logged out');  // code + optional reason string

// 8. Binary data
ws.binaryType = 'arraybuffer';      // or 'blob' (default)
ws.onmessage = (event) => {
  const buffer = event.data;        // ArrayBuffer
  const view = new Uint8Array(buffer);
};

Always check ws.readyState === WebSocket.OPEN before calling ws.send() — sending on a closed or connecting socket throws an error. Buffer messages in a queue and flush them in onopen if needed.

Node.js ws Library — WebSocketServer, Broadcast, Heartbeat

The ws npm package is the most popular, lightweight WebSocket server for Node.js. It is a thin wrapper over the native WebSocket protocol with no opinion on your architecture.

npm install ws
npm install --save-dev @types/ws   # TypeScript types
import { WebSocketServer, WebSocket } from 'ws';
import http from 'http';

const server = http.createServer();
const wss = new WebSocketServer({ server });

// Broadcast helper — send to all connected clients
function broadcast(wss: WebSocketServer, data: string) {
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(data);
    }
  });
}

wss.on('connection', (ws, req) => {
  const ip = req.socket.remoteAddress;
  console.log('Client connected from', ip);

  ws.on('message', (rawData) => {
    const message = rawData.toString();
    console.log('Received:', message);

    // Echo to sender
    ws.send(JSON.stringify({ type: 'echo', data: message }));

    // Broadcast to all clients
    broadcast(wss, JSON.stringify({ type: 'broadcast', from: ip, data: message }));
  });

  ws.on('close', (code, reason) => {
    console.log('Client disconnected:', code, reason.toString());
  });

  ws.on('error', (err) => {
    console.error('WebSocket error:', err);
  });

  // Send welcome message
  ws.send(JSON.stringify({ type: 'welcome', message: 'Connected!' }));
});

// Ping/pong heartbeat — detect dead connections
const HEARTBEAT_INTERVAL = 30_000; // 30 seconds

wss.on('connection', (ws) => {
  (ws as any).isAlive = true;
  ws.on('pong', () => { (ws as any).isAlive = true; });
});

setInterval(() => {
  wss.clients.forEach((ws) => {
    if ((ws as any).isAlive === false) {
      ws.terminate(); // Connection is dead
      return;
    }
    (ws as any).isAlive = false;
    ws.ping(); // Send ping frame — client auto-responds with pong
  });
}, HEARTBEAT_INTERVAL);

server.listen(8080, () => console.log('WS server on ws://localhost:8080'));

The ping/pong heartbeat pattern is essential. Without it, connections that drop silently (e.g., a mobile device going offline) will stay in wss.clients forever, leaking memory. The server pings every 30 seconds; if no pong is received, the connection is terminated.

Socket.io — Rooms, Namespaces, Emit/On, Acknowledgments, Redis Adapter

Socket.io is a feature-rich real-time library that provides rooms, namespaces, automatic reconnection, broadcasting, and a Redis adapter for horizontal scaling.

npm install socket.io        # server
npm install socket.io-client # client
// --- SERVER (Node.js) ---
import { Server } from 'socket.io';
import http from 'http';

const httpServer = http.createServer();
const io = new Server(httpServer, {
  cors: { origin: 'https://yourapp.com', credentials: true },
});

io.on('connection', (socket) => {
  console.log('Connected:', socket.id);

  // Join a room
  socket.on('join-room', (roomId: string) => {
    socket.join(roomId);
    io.to(roomId).emit('user-joined', { userId: socket.id });
  });

  // Custom event
  socket.on('chat-message', (msg: string) => {
    // Broadcast to everyone in the room EXCEPT sender
    socket.to('room-1').emit('chat-message', { from: socket.id, msg });
  });

  // Acknowledgment (callback)
  socket.on('save-data', async (data, callback) => {
    try {
      await db.save(data);
      callback({ status: 'ok' });            // success ack
    } catch (err) {
      callback({ status: 'error', message: (err as Error).message });
    }
  });

  socket.on('disconnect', () => {
    console.log('Disconnected:', socket.id);
  });
});

// Namespace — separate channel for admin
const adminNsp = io.of('/admin');
adminNsp.on('connection', (socket) => {
  socket.emit('admin-data', { stats: 'secret' });
});

httpServer.listen(3000);

// --- CLIENT ---
import { io } from 'socket.io-client';

const socket = io('https://yourapp.com', {
  auth: { token: 'jwt-here' },           // auth token
  reconnectionDelayMax: 10000,           // max 10s between retries
});

socket.emit('join-room', 'room-42');

socket.on('chat-message', (data) => {
  console.log(data.from, ':', data.msg);
});

// Acknowledgment
socket.emit('save-data', { key: 'value' }, (response) => {
  if (response.status === 'ok') console.log('Saved!');
});

For scaling across multiple processes, use the Redis adapter:

npm install @socket.io/redis-adapter ioredis

import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'ioredis';

const pubClient = createClient({ host: 'redis-host', port: 6379 });
const subClient = pubClient.duplicate();

await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// Now events are synchronized across all Socket.io server instances

React useWebSocket Hook — Reconnect Logic, Message History, Lazy Connect

The react-use-websocket library provides a batteries-included hook for WebSocket connections in React applications, handling reconnection, message queuing, and state management automatically.

npm install react-use-websocket
import useWebSocket, { ReadyState } from 'react-use-websocket';

interface ChatMessage {
  from: string;
  text: string;
}

export function ChatComponent() {
  const [input, setInput] = useState('');

  const {
    sendMessage,
    sendJsonMessage,
    lastMessage,
    lastJsonMessage,
    readyState,
    getWebSocket,
  } = useWebSocket('wss://example.com/ws', {
    // Automatic reconnection with backoff
    shouldReconnect: (closeEvent) => closeEvent.code !== 1000,
    reconnectAttempts: 10,
    reconnectInterval: (attemptNumber) =>
      Math.min(1000 * 2 ** attemptNumber, 30000), // exponential backoff

    // Keep last 50 messages in history
    queryParams: { token: 'jwt-token' },   // appended to URL

    // Callbacks
    onOpen: () => console.log('WebSocket connected'),
    onClose: (event) => console.log('Closed:', event.code),
    onError: (event) => console.error('Error:', event),
    onMessage: (event) => {
      const data = JSON.parse(event.data) as ChatMessage;
      setMessages((prev) => [...prev, data]);
    },
  });

  // Lazy connect — don't connect until a condition is met
  const { sendMessage: lazyWs } = useWebSocket(
    'wss://example.com/ws',
    { connect: isLoggedIn } // only connects when isLoggedIn = true
  );

  const connectionStatus = {
    [ReadyState.CONNECTING]: 'Connecting',
    [ReadyState.OPEN]: 'Open',
    [ReadyState.CLOSING]: 'Closing',
    [ReadyState.CLOSED]: 'Closed',
    [ReadyState.UNINSTANTIATED]: 'Uninstantiated',
  }[readyState];

  return (
    <div>
      <p>Status: {connectionStatus}</p>
      {lastMessage && <p>Last message: {lastMessage.data}</p>}
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <button
        onClick={() => {
          sendJsonMessage({ type: 'chat', text: input });
          setInput('');
        }}
        disabled={readyState !== ReadyState.OPEN}
      >
        Send
      </button>
    </div>
  );
}

For a manual reconnect hook without any library, implement the pattern yourself using useRef and useEffect with the exponential backoff algorithm shown in the FAQ section below.

WebSocket Authentication — JWT in Query Param vs Cookie vs First Message

WebSocket connections do not support custom HTTP headers during the upgrade handshake (browsers restrict this). There are three practical authentication patterns:

Pattern 1: JWT in Query Parameter

// Client
const token = localStorage.getItem('jwt');
const ws = new WebSocket(`wss://example.com/ws?token=${token}`);

// Server (Node.js ws)
import jwt from 'jsonwebtoken';
import { parse } from 'url';

wss.on('connection', (ws, req) => {
  const { query } = parse(req.url || '', true);
  const token = query.token as string;

  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    (ws as any).userId = (payload as any).sub;
    (ws as any).authenticated = true;
  } catch {
    ws.close(1008, 'Invalid token'); // 1008 = Policy Violation
    return;
  }
});

Downside: tokens in URLs appear in server access logs and browser history. Mitigate by using short-lived tokens (60-second expiry) generated specifically for WebSocket connections.

Pattern 2: First Message Authentication

// Client — send auth as first message
ws.onopen = () => {
  ws.send(JSON.stringify({ type: 'auth', token: getJwt() }));
};

// Server — expect auth as first message
wss.on('connection', (ws) => {
  let authenticated = false;

  ws.on('message', (rawData) => {
    const msg = JSON.parse(rawData.toString());

    if (!authenticated) {
      if (msg.type !== 'auth') {
        ws.close(1008, 'Must authenticate first');
        return;
      }
      try {
        const payload = jwt.verify(msg.token, process.env.JWT_SECRET!);
        (ws as any).userId = (payload as any).sub;
        authenticated = true;
        ws.send(JSON.stringify({ type: 'auth-ok' }));
      } catch {
        ws.close(1008, 'Invalid token');
      }
      return;
    }

    // Authenticated — handle message normally
    handleMessage(ws, msg);
  });

  // Close unauthenticated connections after 5 seconds
  setTimeout(() => {
    if (!authenticated) ws.close(1008, 'Auth timeout');
  }, 5000);
});

Pattern 3: Socket.io Middleware Auth

// Client
const socket = io('https://example.com', {
  auth: { token: getJwt() },
});

// Server middleware — runs before 'connection' event
io.use((socket, next) => {
  const token = socket.handshake.auth.token;
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET!);
    socket.data.userId = (payload as any).sub;
    next(); // allow connection
  } catch (err) {
    next(new Error('Unauthorized')); // reject connection
  }
});

io.on('connection', (socket) => {
  console.log('Authenticated user:', socket.data.userId);
});

Python websockets Library — asyncio Server, Client, Broadcast

The websockets library is the standard Python choice for WebSocket servers. It integrates natively with asyncio and is production-ready with TLS support.

pip install websockets
# --- SERVER ---
import asyncio
import websockets
from websockets.server import WebSocketServerProtocol

# Connected clients registry
connected: set[WebSocketServerProtocol] = set()

async def broadcast(message: str) -> None:
    if connected:
        await asyncio.gather(
            *[ws.send(message) for ws in connected],
            return_exceptions=True,  # don't stop on individual errors
        )

async def handler(websocket: WebSocketServerProtocol) -> None:
    connected.add(websocket)
    try:
        await websocket.send('{"type": "welcome"}')

        async for raw_message in websocket:
            data = json.loads(raw_message)
            print(f"Received: {data}")

            if data["type"] == "broadcast":
                await broadcast(raw_message)
            else:
                await websocket.send(json.dumps({"type": "echo", "data": data}))

    except websockets.exceptions.ConnectionClosedOK:
        pass   # normal closure
    except websockets.exceptions.ConnectionClosedError as e:
        print(f"Connection error: {e.code} {e.reason}")
    finally:
        connected.discard(websocket)

async def main() -> None:
    async with websockets.serve(handler, "0.0.0.0", 8765):
        print("WebSocket server started on ws://0.0.0.0:8765")
        await asyncio.Future()  # run forever

asyncio.run(main())
# --- CLIENT ---
import asyncio
import websockets
import json

async def client():
    uri = "wss://example.com/ws"
    async with websockets.connect(uri) as websocket:
        # Send
        await websocket.send(json.dumps({"type": "hello", "data": "world"}))

        # Receive
        response = await websocket.recv()
        print("Response:", json.loads(response))

        # Listen loop
        async for message in websocket:
            print("Incoming:", message)

asyncio.run(client())

For FastAPI integration, use fastapi.WebSocket which supports dependency injection and middleware:

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Echo {client_id}: {data}")
    except WebSocketDisconnect:
        print(f"Client {client_id} disconnected")

Go gorilla/websocket — Upgrader, ReadMessage, WriteMessage, Concurrent Writes

gorilla/websocket is the most widely used WebSocket library for Go. It is mature, well-documented, and handles the low-level framing, masking, and close handshake for you.

go get github.com/gorilla/websocket
package main

import (
    "log"
    "net/http"
    "sync"

    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        // Validate Origin header to prevent CSWSH
        origin := r.Header.Get("Origin")
        return origin == "https://yourapp.com"
    },
}

// Client with mutex for concurrent writes
type Client struct {
    conn *websocket.Conn
    mu   sync.Mutex
}

func (c *Client) WriteJSON(v interface{}) error {
    c.mu.Lock()
    defer c.mu.Unlock()
    return c.conn.WriteJSON(v)
}

func handleWS(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Printf("Upgrade error: %v", err)
        return
    }
    defer conn.Close()

    client := &Client{conn: conn}

    // Set read deadline / pong handler for heartbeat
    conn.SetReadDeadline(time.Now().Add(60 * time.Second))
    conn.SetPongHandler(func(string) error {
        conn.SetReadDeadline(time.Now().Add(60 * time.Second))
        return nil
    })

    for {
        messageType, p, err := conn.ReadMessage()
        if err != nil {
            if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
                log.Println("Client closed connection")
            } else {
                log.Printf("Read error: %v", err)
            }
            return
        }

        log.Printf("Received (%d): %s", messageType, p)

        // Echo back
        if err := client.WriteJSON(map[string]string{
            "type": "echo",
            "data": string(p),
        }); err != nil {
            log.Printf("Write error: %v", err)
            return
        }
    }
}

func main() {
    http.HandleFunc("/ws", handleWS)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

The sync.Mutex on writes is critical: gorilla/websocket connections are not safe for concurrent writes. If you have a goroutine that pings AND a goroutine that writes messages, they will race. Use a mutex or a dedicated write channel (chan []byte) that a single writer goroutine drains.

Scaling WebSockets — Sticky Sessions, Redis Pub/Sub, Horizontal Scaling

Scaling WebSockets is harder than scaling stateless HTTP APIs because connections are persistent and bound to a specific server process. A message sent to Server A cannot reach clients connected to Server B without a coordination layer.

Sticky Sessions

Configure your load balancer to always route the same client to the same server. This is the simplest approach but limits true horizontal scaling.

# Nginx upstream with ip_hash for sticky sessions
upstream websocket_backends {
    ip_hash;                          # route by client IP
    server ws1.example.com:8080;
    server ws2.example.com:8080;
    server ws3.example.com:8080;
}

server {
    listen 443 ssl;
    location /ws {
        proxy_pass http://websocket_backends;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;     # keep connection alive for 1 hour
        proxy_send_timeout 3600s;
    }
}

Redis Pub/Sub for Cross-Server Messaging

// Without Redis: Server A cannot reach Server B's clients
// With Redis: any server publishes → Redis → all servers → their clients

import { createClient } from 'redis';

const publisher = createClient({ url: process.env.REDIS_URL });
const subscriber = publisher.duplicate();

await Promise.all([publisher.connect(), subscriber.connect()]);

// When a message comes in on this server, publish to Redis
ws.on('message', async (data) => {
  await publisher.publish('chat:room-1', data.toString());
});

// Subscribe: deliver Redis messages to local clients in this room
await subscriber.subscribe('chat:room-1', (message) => {
  wss.clients.forEach((client) => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
});

// Connection limits per server (tune based on RAM/CPU)
// A 1GB RAM Node.js server can typically handle ~10,000 idle WS connections
// Use cluster mode or worker_threads to utilize multiple CPU cores

Error Handling and Reconnection — Exponential Backoff, Close Codes, Graceful Shutdown

Production WebSocket clients must handle disconnections gracefully. Networks are unreliable and servers restart. A naive immediate reconnect loop can overwhelm a restarting server (thundering herd problem).

Exponential Backoff Reconnection

class ReconnectingWebSocket {
  private ws: WebSocket | null = null;
  private delay = 1000;        // start at 1s
  private maxDelay = 30000;    // cap at 30s
  private maxAttempts = 10;
  private attempts = 0;
  private shouldClose = false;

  constructor(private readonly url: string) {
    this.connect();
  }

  private connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('WebSocket connected');
      this.delay = 1000;    // reset backoff on successful connect
      this.attempts = 0;
    };

    this.ws.onmessage = (event) => {
      this.onMessage?.(event);
    };

    this.ws.onclose = (event) => {
      if (this.shouldClose) return; // manual close — don't reconnect
      if (event.code === 1000) return; // normal close — don't reconnect

      this.attempts++;
      if (this.attempts > this.maxAttempts) {
        console.error('Max reconnect attempts reached');
        return;
      }

      // Exponential backoff with jitter
      const jitter = Math.random() * 1000;
      const nextDelay = Math.min(this.delay * 2, this.maxDelay) + jitter;
      this.delay = nextDelay;

      console.log(`Reconnecting in ${Math.round(nextDelay)}ms (attempt ${this.attempts})`);
      setTimeout(() => this.connect(), nextDelay);
    };
  }

  send(data: string) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(data);
    } else {
      console.warn('WebSocket not ready, message dropped');
    }
  }

  close() {
    this.shouldClose = true;
    this.ws?.close(1000, 'Client closed');
  }

  onMessage?: (event: MessageEvent) => void;
}

Close Codes Reference

CodeNameReconnect?Meaning
1000Normal ClosureNoClean shutdown, both sides agreed
1001Going AwayYesBrowser tab closed or server restarting
1006Abnormal ClosureYesNo close frame — network failure or crash
1008Policy ViolationNoAuth failed, rate limited, banned
1011Internal ErrorYesUnexpected server error
1012Service RestartYesServer intentionally restarting

Graceful Server Shutdown

// Gracefully close all connections before process exit
process.on('SIGTERM', () => {
  console.log('SIGTERM received — closing WebSocket connections');

  wss.clients.forEach((ws) => {
    ws.close(1012, 'Server restarting'); // 1012 = Service Restart
  });

  wss.close(() => {
    console.log('All WS connections closed');
    server.close(() => process.exit(0));
  });

  // Force exit after 10 seconds if clients don't close cleanly
  setTimeout(() => process.exit(1), 10_000);
});

Test Your WebSocket Server

Use our online WebSocket tester to connect to any ws:// or wss:// endpoint, send custom messages, and inspect frames in real time — no installation required.

Frequently Asked Questions

Does WebSocket work through firewalls and proxies?

Most firewalls allow WebSocket because the upgrade handshake looks like a regular HTTP request on port 80 or 443. However, some corporate HTTP proxies that do not understand WebSocket may strip the Upgrade header, breaking the connection. Always use wss:// on port 443 — TLS-encrypted traffic is harder for proxies to inspect and less likely to be blocked. Socket.io's HTTP long-polling fallback handles this case automatically.

Should I use WebSocket or Server-Sent Events (SSE)?

Use SSE when you only need server-to-client push (notifications, live feeds, log streaming) — it is simpler, works over regular HTTP/2, and auto-reconnects natively. Use WebSocket when you need true bidirectional communication (chat, gaming, collaborative editing where the client also sends frequent messages). SSE has a simpler implementation and better compatibility with HTTP infrastructure (CDNs, proxies), but WebSocket offers lower latency and binary frame support.

What is the maximum number of WebSocket connections a server can handle?

Each WebSocket connection is a file descriptor. Linux defaults to 1024 file descriptors per process — raise this with ulimit -n 65535 or in /etc/security/limits.conf. Memory is the real limit: an idle Node.js WebSocket connection uses roughly 40–60 KB of RAM. A 1 GB RAM server can handle approximately 15,000–25,000 idle connections. Active connections with large message queues use significantly more. Use connection pooling and evict idle connections with heartbeat timeouts.

Key Takeaways

  • Use wss:// in production: Always use WebSocket Secure (TLS) — plain ws:// exposes data and breaks on many proxies.
  • Implement heartbeats: Ping/pong every 30 seconds detects silent connection drops and frees dead socket handles.
  • Exponential backoff on reconnect: Never reconnect immediately — use doubling delays with jitter to avoid thundering herd.
  • Socket.io for features, ws for performance: Socket.io adds rooms/namespaces/acks; raw ws is leaner for high-connection scenarios.
  • Authenticate on connection or first message: Validate JWT before allowing any application-level messages.
  • Mutex for Go writes: gorilla/websocket is not concurrent-write-safe — always lock before writing.
  • Sticky sessions + Redis Pub/Sub is the standard horizontal scaling pattern for WebSocket clusters.
  • Close code 1006 means network failure: Always reconnect on 1006; only skip reconnect on 1000 (normal) and 1008 (policy violation).
  • Raise OS file descriptor limits: ulimit -n 65535 is required for high-connection servers.
  • Graceful shutdown: Send close code 1012 and wait for client acknowledgment before killing the process.
𝕏 Twitterin LinkedIn
บทความนี้มีประโยชน์ไหม?

อัปเดตข่าวสาร

รับเคล็ดลับการพัฒนาและเครื่องมือใหม่ทุกสัปดาห์

ไม่มีสแปม ยกเลิกได้ตลอดเวลา

ลองเครื่องมือที่เกี่ยวข้อง

{ }JSON Formatter🔓CORS Tester

บทความที่เกี่ยวข้อง

API Testing: Complete Guide with cURL, Supertest, and k6

Master API testing with this complete guide. Covers HTTP methods, cURL, fetch/axios, Postman/Newman, supertest, Python httpx, mock servers, contract testing, k6 load testing, and OpenAPI documentation.

CORS Tester: Fix CORS Errors and Configure Cross-Origin Requests — Complete Guide

Fix CORS errors and configure cross-origin requests. Complete guide covering CORS headers, preflight requests, Express/Next.js/Nginx/FastAPI configuration, credentials, debugging, and security.

การยืนยันตัวตน JWT: คู่มือการใช้งานฉบับสมบูรณ์

สร้างระบบยืนยันตัวตน JWT ตั้งแต่เริ่มต้น โครงสร้างโทเค็น, access และ refresh token, การใช้งาน Node.js, การจัดการฝั่งไคลเอนต์, แนวทางปฏิบัติด้านความปลอดภัย และ Next.js middleware