GO Websocket send all clients a message - go

Everything works fine with this code (shortened it for better reading).
When Client1 sends a request to the Server, the Server responses to him instantly. But, the other clients can not see the response message.
So I want to make it go further: When a client sends a request to the server, the server will response to all clients so that all clients can see the message.
How can I do that? Any examples or nice tutorials for beginners?
Thanks in advance!
Server:
import (
"github.com/gorilla/websocket"
)
func main() {
http.Handle("/server", websocket.Handler(echoHandler))
}
func echoHandler(ws *websocket.Conn) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return
}
for {
messageType, p, err := conn.ReadMessage()
if err != nil {
return
}
print_binary(p) // simple print of the message
err = conn.WriteMessage(messageType, p);
if err != nil {
return
}
}
}

You have to use connection pool to broadcast messages to all connections.
You can use that as tutorial/sample http://gary.burd.info/go-websocket-chat
Simplifying:
Connection pool is a collection of registered connections. See hub.connections:
type connection struct {
// The websocket connection.
ws *websocket.Conn
// Buffered channel of outbound messages.
send chan []byte
// The hub.
h *hub
}
type hub struct {
// Registered connections. That's a connection pool
connections map[*connection]bool
...
}
To broadcast message for all clients, we iterate over connection pool like this:
case m := <-h.broadcast:
for c := range h.connections {
select {
case c.send <- m:
default:
delete(h.connections, c)
close(c.send)
}
}
}
h.broadcast in that example is a channel with messages we need to broadcast.
We use default section of the select statement to delete connections with full or blocked send channels. See What is the benefit of sending to a channel by using select in Go?

Related

Get WebSocket error close sent when I navigate to other page

I am getting back error websocket: close sent when I try to send data from my server side to client side(dashboard page). The error happen when I navigate to home page and back to dashboard page. Everything works fine initially with the Dashboard Page
My dashboard page code
let socket = new ReconnectingWebSocket("ws://127.0.0.1:8004/wsendpoint");
console.log("Attempting Connection...");
socket.onopen = () => {
console.log("Successfully Connected");
};
socket.onclose = event => {
console.log("Socket Closed Connection: ", event);
};
socket.onerror = error => {
console.log("Socket Error: ", error);
};
socket.onmessage = function (event) {
console.log("message received: " + event.data);
}
My Server side code (writer and reader
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
defer ws.Close()
go writer(ws)
go reader(ws)
}
Writer, get data from a channel when new data come in from database
func writer(conn *websocket.Conn) {
for {
singleset := <-singleorder // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
}
}
Reader, read data from client side
func reader(conn *websocket.Conn) {
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
I also got error WebSocket is closed before the connection is established. and reconnecting-websocket.min.js:1 WebSocket connection to 'ws://127.0.0.1:8004/wsendpoint' failed: WebSocket is closed before the connection is established.
As you can see all the code are very simple because I just start to learn websocket and follow simple tutorial. I tried to search on web but webworker seems little bit complex for me and saw about the ping pong method but I am not sure if its still valid if I navigate. Can I establish websocket on home page so the connection is not closed ? Since I only have two pages on client side.
Thanks in advance for any guidance on how to deal with these situation!
The basic strategy for dealing with navigation away from the page, page close or errors is this: The client creates the websocket connection on page load. The server expects client connections to come and go and cleans up resources when the client connection errors.
Here's why you get the error 'websocket: close sent': When the user navigates away from the page, the browser sends a close message to the server as part of the websocket closing handshake. The Gorilla package responds to the close message by sending another close message back to the client. From that point on, the server cannot send messages on the connection. The connection returns an error from write methods after the close message is sent.
Close messages are returned as errors from the websocket read methods. To fix the problem, modify the code to handle errors in general. There's no need to handle the closing handshake specifically.
Here's the updated code. The wsEndpoint function creates a channel for the reader to signal the writer that the reader is done. The defer ws.Close() statement is removed because the reader and writer goroutines will take responsibility for closing the connection.
func wsEndpoint(w http.ResponseWriter, r *http.Request) {
upgrader.CheckOrigin = func(r *http.Request) bool { return true }
// upgrade this connection to a WebSocket connection
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("upgrade error %s", err)
}
done := make(chan struct{})
go writer(ws, done)
go reader(ws, done)
}
The reader closes the connection on return. It also closes the done channel to signal the writer that the reader is done.
func reader(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
defer close(done)
for {
_, p, err := conn.ReadMessage() //what is message type?
if err != nil {
log.Println("there is errors%s", err)
return
}
}
}
The writer also closes the connection on return. When the connection is closed, read on the connection immediately returns an error causing the read goroutine to complete. The writer waits on the done channel. Receive on the channel yields the zero value when the channel is closed by the reader. The writer returns on write errors instead of looping forever as in the question.
func writer(conn *websocket.Conn, done chan struct{}) {
defer conn.Close()
for {
select {
case <-done:
// the reader is done, so return
return
case singleset := <-singleorder: // get data from channel
jsonString, err := json.Marshal(singleset )
err = conn.WriteMessage(1, []byte(jsonString))
if err != nil {
log.Println(err)
return
}
}
}
}
An application should expect the connection to be closed for any number of reasons including the user navigating away from the page.
It's likely that the code that sends to channel singleorder needs to know that the connection was closed, but we cannot see that code here. I'll leave it to you to figure out how to handle that.

How to communicate between multiple goroutines with for loops with blocking function calls inside one of them

I'm writing a Go app which accepts a websocket connection, then starts:
listen goroutine which listens on the connection for client messages and sends response for the client based on the received message via channel to updateClient.
updateClient goroutine which writes to the connection.
processExternalData goroutine which receives data from message queue, sends the data to updateClient via a channel so that updateClient can update the client with the data.
I'm using gorilla library for websocket connections, and its read call is blocking. In addition, both its write and read methods don't support concurrent calls, which is the main reason I have the updateClient goroutine which is the single routine which calls write method.
The problem arises when I need to close the connection which can happen at least in 2 cases:
The client closed the connection or error occurred during read.
processExternalData finished, there's no more data to update the client and the connection should be closed.
So updateClient needs to somehow notify listen to quit and vice versa listen needs to somehow notify updateClient to quit. updateClient has a quit channel inside select but listen can't have select because it already has a for loop with blocking read call inside.
So what I did is I added isJobFinished field on the connection type which is a condition for for loop to work:
type WsConnection struct {
connection *websocket.Conn
writeChan chan messageWithCb
quitChan chan bool
isJobFinished bool
userID string
}
func processExternalData() {
// receive data from message queue
// send it to client via writeChan
}
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
conn.quitChan <- true
}()
// keep the loop for communication with client
for !conn.isJobFinished {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}
func updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
}
if wsConn.isJobFinished {
if msg.callback != nil {
msg.callback() // sends on `wsConn.quitChan` from a goroutine
}
}
case <-wsConn.quitChan:
// clean up
wsConn.connection.Close()
close(wsConn.writeChan)
return
}
}
}
I'm wondering if a better pattern exists in Go for such cases. Specifically, I'd like to be able to have a quit channel inside listen as well so updateClient can notify it to quit instead of maintaining isJobFinished field. Also in this case there's no danger of not protecting isJobFinished field because only one method writes to it but if the logic gets more complicated then having to protect the field inside the for loop in listen will probably negatively impact the performance.
Also I can't close the quiteChan because both listen and updateClient use it and there's no way to know for them when it's closed by another one.
Close the connection to break the listen goroutine out of the blocking read call.
In updateClient, add a defer statement to close the connection and clean up other resources. Return from the function on any error or a notification from the quit channel:
updateClient(w http.ResponseWriter, req *http.Request) {
upgrader.CheckOrigin = func(req *http.Request) bool {
return true
}
connection, err := upgrader.Upgrade(w, req, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer connection.Close() // <--- Add this line
wsConn := &WsConnection{
connection: connection,
writeChan: make(chan messageWithCb),
quitChan: make(chan bool),
}
defer close(writeChan) // <-- cleanup moved out of loop below.
go wsConn.listen()
for {
select {
case msg := <-wsConn.writeChan:
err := connection.WriteJSON(msg.message)
if err != nil {
log.Println("connection.WriteJSON error: ", err)
return
}
case <-wsConn.quitChan:
return
}
}
}
In the listen function, loop until error reading the connection. Read on the connection returns immediately with an error when updateClient closes the connection.
To prevent listen from blocking forever in the case where updateClient returns first, close the quit channel instead of sending a value.
func (conn *WsConnection) listen() {
defer func() {
conn.connection.Close()
close(conn.quitChan) // <-- close instead of sending value
}()
// keep the loop for communication with client
for {
_, message, err := conn.connection.ReadMessage()
if err != nil {
log.Println("read:", err)
break
}
// convert message to type messageWithCb
switch clientMessage.MessageType {
case userNotFound:
conn.writeChan <- messageWithCb{
message: map[string]interface{}{
"type": user,
"payload": false,
},
}
default:
log.Printf("Unknown message type received: %v", clientMessage)
}
}
log.Println("end of listen")
}
The field isJobFinished is not needed.
One problem with the code in the question and in this answer is that close of writeChan is not coordinated with sends to the channel. I cannot comment on a solution to this problem without seeing the processExternalData function.
It may make sense to use a mutex instead of a goroutine to limit write concurrency. Again, the code in the processExternalData function is required to comment further on this topic.

Accessing service layer from different goroutines for concurrent execution

I have different Clients, each has its own goroutines which reads messages from a websocket.
Those clients have in their a struct a Service struct that processes messages and updates the DB (postgres) accordingly.
Once the processing is finished, it send the output to a central Hub struct via a channel.
My question is - is the ProcessMessage func of Service done concurrently?
Meaning, are the message processing, DB interactions, etc. done concurrently in the example posted here? or is each processing blocking all the rest ?
Main :
func main() {
...
// delivery channel (service --> hub)
delivery := make(chan []byte)
// hub
hub := connection.NewHub(delivery)
go hub.Run()
// service
service := chat.NewService(delivery, storage, cache)
}
Then for each connecting client, I create a Client struct, passing the Service and the Hub to it.
Also, each Client's reading from a websocket is done in a goroutine :
func ServeWs(hub *Hub, service chat.Service, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println(err)
return
}
client := &Client{hub: hub, conn: conn, service: service, send: make(chan []byte, 256)}
client.hub.register <- client
go client.writePump()
go client.readPump()
}
Client :
func (c *Client) readPump() {
for {
_, message, err := c.conn.ReadMessage()
...
...
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
...
c.service.ProcessMessage(message)
}
}
Service :
func (s *service) ProcessMessage(incoming protocol.Incoming) {
is this blocking other clients? or is is executed concurrently for each Client ?
// all the logic with postgres, redis, etc...
result, ok := s.storage.InsertMessage(...)
...
...
s.delivery <- result // sends the output to Hub via the delivery channel
}

one to one chat in golang

I want to create one to one chat in revel framework but it gives error. Firstly work in revel chat according to demo but refreshing page did not work so I tried this method and dont know how to handle single chat.
Here is an error:
app server.go:2848: http: panic serving 127.0.0.1:50420: interface conversion: interface is nil, not io.Writer goroutine 166 [running]: net/http.(*conn).serve.func1(0xc4201d03c0)
my go code is where I handle ws root,single user chat need to db connection to. I'm using posgres for it
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
// Configure the upgrader
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
Created string `json:"created"`
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8090")
err := http.ListenAndServe(":8090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
I think you use gorilla/websocket API incorrectly. You copied the echo example which, being a basic demo, is expected to handle a single ws connection only. Start with the chat example. Particularly pay attention to the fact that serveWs is a non-blocking call while your handleConnections is blocking, i.e. it never returns. Take a look here for a full-featured example of gorilla/websocket API use:
https://github.com/tinode/chat/blob/master/server/wshandler.go
As correctly pointed out by Cerise L, you most certainly have a race on your clients although I think it's unlikely to produce a panic. I think the most likely source of panic is a call to Upgrade on a closed http connection. It's impossible to say exactly because you did not post the full output of the panic.

Golang one to one chat

I want make one to one chat on golang and I find this simple script with websocket it work really well and it is one room with how much users inside you want. But I want convert it to one to one like facebook this is script if someone can help because I dont know am I need use more connections or filter users.
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool) // connected clients
var broadcast = make(chan Message) // broadcast channel
// Configure the upgrader
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// Define our message object
type Message struct {
Email string `json:"email"`
Username string `json:"username"`
Message string `json:"message"`
Created string `json:"created"`
}
func main() {
// Create a simple file server
fs := http.FileServer(http.Dir("public"))
http.Handle("/", fs)
// Configure websocket route
http.HandleFunc("/ws", handleConnections)
// Start listening for incoming chat messages
go handleMessages()
// Start the server on localhost port 8000 and log any errors
log.Println("http server started on :8090")
err := http.ListenAndServe(":8090", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
func handleConnections(w http.ResponseWriter, r *http.Request) {
// Upgrade initial GET request to a websocket
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Fatal(err)
}
// Make sure we close the connection when the function returns
defer ws.Close()
// Register our new client
clients[ws] = true
for {
var msg Message
// Read in a new message as JSON and map it to a Message object
err := ws.ReadJSON(&msg)
if err != nil {
log.Printf("error: %v", err)
delete(clients, ws)
break
}
// Send the newly received message to the broadcast channel
broadcast <- msg
}
}
func handleMessages() {
for {
// Grab the next message from the broadcast channel
msg := <-broadcast
// Send it out to every client that is currently connected
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
log.Printf("error: %v", err)
client.Close()
delete(clients, client)
}
}
}
}
am I need change this part
clients[ws] = true
You would need to do few things:
Get rid of broadcast channel
Somehow pass & get from request to which client your user want to connect. Some room number/name, secret code? For example an URL parameter /ws?chat=abc. You probably would need to maintain a map[chatid][]*websocket.Conn
Match 2 (or more) clients.
Maintain a map, probably of type map[*websocket.Conn]*websocket.Conn
On receiving a message from a client lookup the map and send the message to the matching client. In similar way as in handleMessages() but just once.
Please note StackOverflow is not a place to ask to write code for you.

Resources