Skip to content

Instantly share code, notes, and snippets.

@deckarep
Last active January 1, 2016 09:39
Show Gist options
  • Save deckarep/8125909 to your computer and use it in GitHub Desktop.
Save deckarep/8125909 to your computer and use it in GitHub Desktop.
Untested unfortunately but you get the idea.
package main
import (
"github.com/codegangsta/martini"
"github.com/gorilla/websocket"
"log"
"net"
"net/http"
"sync"
)
/*
Go is a great langauge but you have to be aware of what happens when you are mutating state from multiple goroutines.
Everytime a web request comes in, in your /sock handler down below Go will create a new goroutine to service that
request, this means, you could potentially have a lot of new gourtines concurrently running that are modifiying the
ActiveClients map defined below this text. By design, maps are not concurrent-safe, meaning that if they are being
modified by multiple goroutines concurrently, they can easily get into an invalid state.
If all the /sock handler was doing, was only reading to but not writing the ActiveClients, then you would be fine.
But in this script you are also writing to the ActiveClients therefore you must synchronize access to map.
You can do that one of two ways: Using channels, or using mutexes. These topics are fairly big topics on their own
but here is how you can use a mutex to make sure that are safely mutating the state of the map.
See this page for further reading: http://blog.golang.org/go-maps-in-action
*/
var ActiveClients = make(map[ClientConn]int)
var ActiveClientsRWMutex = sync.RWMutex
type ClientConn struct {
websocket *websocket.Conn
clientIP net.Addr
}
func addClient(cc ClientConn) {
ActiveClientsRWMutex.Lock()
ActiveClients[cc] = 0
ActiveClientsRWMutex.Unlock()
}
func main() {
m := martini.Classic()
m.Get("/", func() string {
return `<html><body><script src='//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js'></script>
<ul id=messages></ul><form><input id=message><input type="submit" id=send value=Send></form>
<script>
var c=new WebSocket('ws://localhost:3000/sock');
c.onopen = function(){
c.onmessage = function(response){
console.log(response.data);
var newMessage = $('<li>').text(response.data);
$('#messages').append(newMessage);
$('#message').val('');
};
$('form').submit(function(){
c.send($('#message').val());
return false;
});
}
</script></body></html>`
})
m.Get("/sock", func(w http.ResponseWriter, r *http.Request) {
log.Println(ActiveClients)
ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
if _, ok := err.(websocket.HandshakeError); ok {
http.Error(w, "Not a websocket handshake", 400)
return
} else if err != nil {
log.Println(err)
return
}
client := ws.RemoteAddr()
sockCli := ClientConn{ws, client}
addClient(sockCli)
for {
messageType, p, err := ws.ReadMessage()
if err != nil {
return
}
for client, _ := range ActiveClients {
if err := client.websocket.WriteMessage(messageType, p); err != nil {
return
}
}
}
})
m.Run()
}
@deckarep
Copy link
Author

@patcito, should a client access the mutex it will wait indefinitely until the mutex unlocks. This is the desired behavior to ensure the ActiveClient is always in a valid state. The advantage over using a channel is in this scenario, simpler and less code...but Go encourages sharing data by communicating so for more complex applications, you will want to go the channel route most likely.

@deckarep
Copy link
Author

@patcito, I just realize I didn't use the correct identifier to lock the map. Fixed now.

@patcito
Copy link

patcito commented Dec 26, 2013

@deckarep I still get this error "./server.go:30: type sync.RWMutex is not an expression".

Edit: just had to remove the = at #30

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment