I’ve been slowly learning go by making changes to the gorilla websocket Chat demo project -https://github.com/gorilla/websocket/tree/master/examples/chat. It’s been really fun but I’ve hit a bit of a barrier regarding channels and communicating across go routines.
In the example project, a Hub maintains a list of connected clients, is run as a single go routine and exposes channels for adding clients, removing clients and broadcasting messages to all clients. For each connected Client, 2 go routines are run to read messages and write messages. I think I get all that and can follow the code.
However, I’ve modified the Client
struct to contain a unique clientId
(provided as a web socket message after the client has connected). If the same clientId
makes a second connection, I want to close that connection, but I’m not sure how to implement a synchronous call between the Hub
go routine and the Client
read go routine?
My first attempt was to add a new channel that the Client
could send the clientId
across to indicate to the Hub
to remove any existing connections. I added a new unregisterId
string channel and then sent the message from the client. The run
method in Hub
now looks like this:
...
func (h *Hub) run() {
for {
select {
case client := <-h.register:
h.clients[client] = true
// New code here: receive clientId to unregister on a new channel
case clientId := <-h.unregisterId:
for client := range h.clients {
if client.clientId == clientId {
delete(h.clients, client)
close(client.send)
}
}
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
...
And the readPump
function in Client
was modified to:
...
for {
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
log.Printf("error: %v", err)
}
break
}
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
//clientId read from JSON message
c.hub.unregisterId <- clientId
//Assume that any previous client with the same ID has now been disconnected
c.clientId = clientId
...
However, this obviously doesn’t work as the client go routine just carries on after sending the message to the unregisterId
channel. The Hub
go routine then resumes and closes the connection as it has now has the same clientId
that was sent down the channel.
What I want is for the client go routine to block until the Hub
has finished processing the message from the channel. From reading around, I think my options are:
-
Call a new function on the Hub (from the Client go routine) to remove the existing connections and use a Mutex to lock the client map in the
Hub
. -
(Somehow) pass a WaitGroup to the
Hub
(via the channel?) and use that from theClient
go routine to wait for theHub
to remove any existing clients -
Signal back to the
Client
from theHub
using another channel once any existing connections have been removed (but again, I’ve no idea how I would make this channel available to theHub
)
Any suggestions or pointers on how best to approach this would be very much welcomed!