zishang520/socket.io

repository·main·Indexed 19 days ago

https://github.com/zishang520/socket.io

A modern, idiomatic Go implementation of Socket.IO for real-time, bidirectional communication over WebSockets and other transports. It includes a base adapter for scaling and specialized adapters for MongoDB (using Change Streams) and PostgreSQL (using LISTEN/NOTIFY), both of which are wire-compatible with their respective Node.js counterparts to support mixed-language deployments.

Tokens
62K
Snippets
217
Records
285
Agent score
68%

What's inside zishang520/socket.io

  1. What's new in Socket.IO for Go v3

    main

    Socket.IO for Go v3.0.0 is a major release that introduces several architectural and functional improvements:

    • Monorepo Consolidation: Previously separate repositories (like engine.io-go-parser, socket.io-client-go, etc.) are now merged into a single monorepo with 9 versioned submodules.
    • Protocol Alignment: The protocol is aligned with Socket.IO v4+, improving compatibility with the JavaScript ecosystem.
    • Thread Safety: Includes concurrency fixes such as atomic socket flags, mutex-protected middleware, and goroutine leak prevention.
    • Type Safety: Introduces types.Atomic[T] (replacing atomic.Value), types.Optional[T] for null safety, and strongly typed Handshake fields.
    • New Utility Packages: Includes pkg/slices (safe slice operations), pkg/queue (ordered message delivery), and pkg/request (HTTP client).
    • Redis Cluster Support: Adds sharded broadcast operators and dynamic channel subscription management.
    • DoS Prevention: Implements HTTP body size limits on polling transport and configurable attachment count limits.
    • Minimum Go Version: Requires Go 1.26.0 or higher.
  2. Understand Sharded Subscription Modes

    main

    When using the Sharded Pub/Sub adapter, you can configure the SubscriptionMode to control how channels are managed:

    • StaticSubscriptionMode: Uses 2 fixed channels per namespace.
    • DynamicSubscriptionMode (Default): Uses 2 + 1 channel per public room.
    • DynamicPrivateSubscriptionMode: Uses a separate channel per room (including private rooms).
  3. Choose the right Valkey adapter type

    main

    Depending on your Valkey deployment and requirements, choose one of the following three adapter types:

    • Classic Pub/Sub (ValkeyAdapterBuilder): Suitable for standalone or replicated Valkey instances.
    • Sharded Pub/Sub (ShardedValkeyAdapterBuilder): Uses SSUBSCRIBE/SPUBLISH for Valkey Cluster environments (requires Valkey 7+).
    • Streams (ValkeyStreamsAdapterBuilder): Uses Valkey Streams for persistent messages and session recovery.
  4. Deploy mixed Go and Node.js Socket.IO clusters

    main

    The Go adapter is wire-compatible with the Node.js socket.io-postgres-adapter and socket.io-postgres-emitter. To successfully mix Go and Node.js servers in the same cluster, ensure the following configurations match across all nodes:

    • Channel Prefix: Both must use the same Key (default: socket.io).
    • Attachment Table: Both must use the same TableName (default: socket_io_attachments).
    • Namespaces: Both must use the same namespace names.
  5. How the Unix Domain Socket Adapter works

    main

    The adapter facilitates low-latency Inter-Process Communication (IPC) for multi-process deployments on a single machine.

    Peer Discovery

    Each Socket.IO server node creates a unique Unix Domain Socket listener file following the pattern: /tmp/socket.io.sock.{server-uid}

    When broadcasting, the adapter scans the socket directory for all peer listener files matching the base path pattern and sends the message to each peer via Unix datagram sockets.

    Message Encoding

    • JSON: Used for non-binary messages.
    • MessagePack: Used for binary messages.
  6. Understand Adapter Types and Interfaces

    main

    The package provides three primary levels of adapter functionality depending on your scaling and session requirements:

    1. Base Adapter: The core interface for broadcasting messages to rooms.
    2. Cluster Adapter: Extends the Base Adapter with methods for managing multiple server instances (e.g., ServerCount()).
    3. Session-Aware Adapter: Extends the Base Adapter with session management capabilities like SaveSession and GetSession.
    type Adapter interface {
        Broadcast([]Room, *BroadcastOptions, ...any)
        BroadcastWithAck([]Room, *BroadcastOptions, ...any) <-chan []any
        // ... other methods
    }
    
    type ClusterAdapter interface {
        Adapter
        ServerCount() int
        // Additional cluster-specific methods
    }
    
    type SessionAwareAdapter interface {
        Adapter
        SaveSession(id string, session any)
        GetSession(id string) any
        // Session management methods
    }
  7. How the MongoDB adapter works

    main

    The adapter enables horizontal scaling by using MongoDB Change Streams.

    When a Socket.IO server needs to broadcast a message or perform a cross-node operation, it inserts a document into the shared MongoDB collection. All other server instances watching that same collection via Change Streams receive the notification and process the event (e.g., broadcasting to their locally connected clients).

    This architecture is compatible with the Node.js @socket.io/mongo-adapter, allowing you to build mixed-language deployments (Go and Node.js) that share the same state.

  8. Configure data cleanup with Capped Collections or TTL Indexes

    main

    The adapter requires a mechanism to clean up old event documents in the shared MongoDB collection. You can choose between two methods:

    Create a capped collection in MongoDB. This is the most efficient method for most use cases.

    db.createCollection("socket.io-adapter-events", { capped: true, size: 1e6 })

    2. TTL Index

    Alternatively, use a Time-To-Live (TTL) index to expire documents after a certain period.

    Step 1: Create the index in MongoDB

    db.collection("socket.io-adapter-events").createIndex(
        { createdAt: 1 },
        { expireAfterSeconds: 3600 }
    )

    Step 2: Enable the AddCreatedAtField option in your Go code If using a TTL index, you must tell the adapter to include the createdAt field so the index can function.

    opts := &mgadapter.MongoAdapterOptions{}
    opts.SetAddCreatedAtField(true)
  9. How the PostgreSQL adapter works

    main

    The adapter uses a dual-mechanism approach to handle inter-node communication:

    1. LISTEN/NOTIFY: Used for lightweight pub/sub of messages that fall below the PayloadThreshold.
    2. Attachment Table: For large payloads or binary data exceeding the threshold, the adapter stores the data in a PostgreSQL table and sends a notification containing a reference to that data.

    Messages are serialized as JSON for direct NOTIFY or MessagePack for attachment storage. This design ensures compatibility with the Node.js socket.io-postgres-adapter.

  10. Breaking Changes in Socket.IO v3

    main

    Upgrading to v3 introduces several breaking changes. Key changes include:

    • Protocol Compatibility: v3 aligns with Socket.IO v4+. You must update your client-side library to socket.io-client@^4.0.0.
    • Redis Adapter Types: Replace types.String with types.Atomic[string].
    • Handshake Access: Handshake headers and query parameters now use .Header().Get() and .Query().Get() methods instead of direct map access.
    • Configuration Methods: GetRaw* methods now return values directly instead of pointers.
    • ParameterBag: Migrate from *utils.ParameterBag to *types.ParameterBag.
    • Transport Upgrades: Upgrades() now returns []string instead of *types.Set[string].
    • HttpContext API: Several methods like GetHost() and GetMethod() have been renamed or moved to sub-objects (e.g., ctx.Host(), ctx.Method(), ctx.Query().Get()).
    • ExtendedError: The Data() method is now a field Data.
  11. Understand the v3 Module Architecture

    main

    The v3 release uses a monorepo structure where all components are organized into versioned submodules under the github.com/zishang520/socket.io/ root. This structure allows you to import specific components while maintaining unified versioning.

    Key modules include:

    • v3: Root module containing shared types and interfaces.
    • parsers/engine/v3: Engine.IO protocol parser.
    • parsers/socket/v3: Socket.IO protocol parser.
    • servers/engine/v3: Engine.IO server.
    • servers/socket/v3: Socket.IO server.
    • clients/engine/v3: Engine.IO client.
    • clients/socket/v3: Socket.IO client.
    • adapters/adapter/v3: Base adapter interface.
    • adapters/redis/v3: Redis adapter and emitter.
    github.com/zishang520/socket.io/
    ├── v3                          # Root: shared types, interfaces
    ├── parsers/
    │   ├── engine/v3               # Engine.IO protocol parser
    │   └── socket/v3               # Socket.IO protocol parser
    ├── servers/
    │   ├── engine/v3               # Engine.IO server
    │   └── socket/v3               # Socket.IO server
    ├── clients/
    │   ├── engine/v3               # Engine.IO client
    │   └── socket/v3               # Socket.IO client
    └── adapters/
        ├── adapter/v3              # Base adapter interface
        └── redis/v3                # Redis adapter (+ emitter)
  12. Handle Config GetRaw* return types with types.Optional[T]

    main

    All GetRaw* methods in v3 now return types.Optional[T] instead of pointer types to improve null safety. When accessing the value, use the .Get() method.

    // Before
    func configExample(config ConnectionStateRecoveryInterface) {
        if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
            fmt.Printf("Duration: %d", *duration)
        }
    }
    
    // After
    func configExample(config ConnectionStateRecoveryInterface) {
        if duration := config.GetRawMaxDisconnectionDuration(); duration != nil {
            fmt.Printf("Duration: %d", duration.Get())
        }
    }