Bhargav Panth
Bhargav Panth

Reputation: 311

Sending a Websocket message to a specific client in Go (using Gorilla)

I am very new to Go and have found myself working with sockets as my first project. This is a redundant question, but I have failed to understand how to send a websocket update to a specific client in Go (using Gorilla).

The broad problem that I am trying to solve is - Building a typeahead using websockets and a search engine like ES/Lucene. I have maintained a bunch of indexes on my search engine and have a Go wrapper around it. When I started working on using websockets in Go, I have been finding almost all the examples showing broadcasting mechanism. When I tried to dig into this and tried to modify the example given in Gorilla's github repo based on the examples given in this thread and in this answer, I don't seem to understand connections and how does that fit in client.go

Ideally, the way I would like to see this working is -

How can the server uniquely identify the Client?

I have used the examples given on Gorilla's Github repo

From my codebase hub.go has the following

type Hub struct {
    // Registered clients.
    clients map[*Client]bool

    // Inbound messages from the clients.
    broadcast chan []byte

    // Register requests from the clients.
    register chan *Client

    // Unregister requests from clients.
    unregister chan *Client

    connections map[string]*connection
}

func newHub() *Hub {
    return &Hub{
        broadcast:  make(chan []byte),
        register:   make(chan *Client),
        unregister: make(chan *Client),
        clients:    make(map[*Client]bool),
        connection: make(map[*Client]bool), // is this alright?
    }
}

func (h *Hub) run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                close(client.send)
            }
        case message := <-h.broadcast:
            for client := range h.connections {
                select {
                case client.send <- message:
                default:
                    close(client.send)
                    delete(h.connections, client)
                }
            }
        }
    }
}

and I am unsure with what I should be adding to client.go

type Client struct {
    // unique ID for each client
    // id string

    // Hub object
    hub *Hub

    // The websocket connection.
    conn *websocket.Conn

    // Buffered channel of outbound messages.
    send chan []byte

    // connection --> (what should the connection property be?)
    connection string
}

Please note - I will be adding an Id field within the Client struct. How can I proceed from here?

Upvotes: 11

Views: 12463

Answers (1)

Thundercat
Thundercat

Reputation: 120951

The chat example shows how to implement broadcast. The chat example is not a good starting point for an application if broadcast is not required.

To send a message to a specific websocket connection, simply write to the connection using NextWriter or WriteMessage. These methods do not support concurrent writers, so you may need to use a mutex or goroutine to ensure a single writer.

The simple approach for finding a specific *websocket.Connection is to pass *websocket.Connection to the code that needs it. If the application needs to associate other state with a connection, then define a type to hold that state and pass a pointer to that around:

type Client struct {
    conn *websocket.Conn
    mu sync.Mutex
    ...
}

The Hub can be modified to send messages to specific connection, but it's a roundabout path if broadcast is not needed. Here's how to do it:

Add ID field to client:

 ID idType // replace idType with int, string, or whatever you want to use

Change the Gorilla hub field from connections map[*connection]bool to connections map[idType]*connection.

Define a message type containing the message data and the ID of the target client:

type message struct {
   ID idtype
   data []byte
}

Replace the hub broadcast field with:

   send chan message

Change the hub for loop to:

for {
    select {
    case client := <-h.register:
        h.clients[client.ID] = client
    case client := <-h.unregister:
        if _, ok := h.clients[client.ID]; ok {
            delete(h.clients, client.ID)
            close(client.send)
        }
    case message := <-h.send:
        if client, ok := h.clients[message.ID]; ok {
            select {
            case client.send <- message.data:
            default:
                close(client.send)
                delete(h.connections, client)
            }
        }
    }

Send messages to a specific client by creating a message with the appropriate ID:

   hub.send <- message{ID: targetID, data: data}

Upvotes: 22

Related Questions