问题
I am trying to implement a global button counter that updates as any/different users click it. So the idea is if one person clicks the button, I see the counter update on my instance of the page.
I currently have the long polling technique working, or so I think, but after review I believe I have an error with "broadcasting" the update to all browsers.
The error currently is that if for example I have two browsers open, and I continuously click on one browser, that browser that I click the button only updates half the time. It will get 1 3 5 etc while the other browser displays 2 4 6 etc.
After reviewing online, I think this may have to do with channels and broadcasting to all those browsers that are on the site. If anyone can help me with an example of how I might send the update to all browsers, every time, I'd really appreciate it.
Client:
<html>
<script language=javascript>
function longpoll(url, callback) {
var req = new XMLHttpRequest ();
req.open ('GET', url, true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
callback(req.responseText);
longpoll(url, callback);
} else {
alert ("long-poll connection lost");
}
}
};
req.send(null);
}
function recv(msg) {
var box = document.getElementById("counter");
box.innerHTML += msg + "\n";
}
function send() {
var box = document.getElementById("counter");
var req = new XMLHttpRequest ();
req.open ('POST', "/push?rcpt=", true);
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) {
if (req.status == 200) {
} else {
alert ("failed to send!");
}
}
};
req.send("hi")
//box.innerHTML += "test" ;
}
</script>
<body onload="longpoll('/poll', recv);">
<h1> Long-Poll Chat Demo </h1>
<p id="counter"></p>
<button onclick="send()" id="test">Test Button</button>
</body>
</html>
Server:
package main
import (
"net/http"
"log"
"io"
// "io/ioutil"
"strconv"
)
var messages chan string = make(chan string, 100)
var counter = 0
func PushHandler(w http.ResponseWriter, req *http.Request) {
//body, err := ioutil.ReadAll(req.Body)
/*if err != nil {
w.WriteHeader(400)
}*/
counter += 1
messages <- strconv.Itoa(counter)
}
func PollResponse(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, <-messages)
}
func main() {
http.Handle("/", http.FileServer(http.Dir("./")))
http.HandleFunc("/poll", PollResponse)
http.HandleFunc("/push", PushHandler)
err := http.ListenAndServe(":8010", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
回答1:
The issue isn't the Go code(alone;see PS PS), it's the browser(Chrome). Making 2 requests to the same URL happens in sequence, not in parallel.
Solution You need to add a unique timestamp to the longpoll URL to trick the browser:
req.open ('GET', url+"?"+(new Date().getTime()), true);
PS - I learned a lot about Go channels and mutex with this question. Thanks :)
PS PS - James' answer (https://stackoverflow.com/a/19803051/143225) is key to getting the server side Go code to handle more than 1 request at a time, because Go channels are blocking which means only 1 goroutine can receive at a time. So the solution to the OP's question is a combination of front and back end code changes.
回答2:
Go channels are not multi-cast. That is, if you have multiple goroutines reading from the channel, only one will wake up when you write a value to the channel rather than it being broadcast to all readers.
One alternative would be to use a condition variable instead:
var (
lock sync.Mutex
cond = sync.NewCond(&lock)
)
In your PollResponse
handler, you can wait on the condition with:
lock.Lock()
cond.Wait()
message := strconv.Itoa(counter)
lock.Unlock()
// write message to response
In your PushHandler
handler, you can broadcast the change with:
lock.Lock()
counter += 1
cond.Broadcast()
lock.Unlock()
来源:https://stackoverflow.com/questions/19802037/long-polling-global-button-broadcast-to-everyone