Last active
January 1, 2016 09:39
-
-
Save deckarep/8125909 to your computer and use it in GitHub Desktop.
Untested unfortunately but you get the idea.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | |
} |
@patcito, I just realize I didn't use the correct identifier to lock the map. Fixed now.
@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
@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.