我正在尝试实现一个全局按钮计数器,该计数器会在任何/不同的用户单击它时更新。所以这个想法是,如果一个人点击按钮,我会在我的页面实例上看到计数器更新。
我目前有长轮询技术在工作,或者我认为是这样,但经过审查,我相信我在向所有浏览器"广播"更新时遇到了错误。
当前的错误是,例如,如果我打开了两个浏览器,并且我连续单击一个浏览器,则我单击该按钮的浏览器只会更新一半时间。它将获得 1 3 5 等,而另一个浏览器显示 2 4 6 等。
在线审查后,我认为这可能与频道和广播到网站上的所有浏览器有关。如果有人能帮我举例说明我每次如何将更新发送到所有浏览器,我将不胜感激。
客户:
<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>
服务器:
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)
}
}
问题不在于Go代码(单独;参见PS PS),而在于浏览器(Chrome)。向同一 URL 发出 2 个请求是按顺序进行的,而不是并行的。
溶液您需要在 longpoll URL 中添加一个唯一的时间戳来欺骗浏览器:
req.open ('GET', url+"?"+(new Date().getTime()), true);
PS - 我通过这个问题学到了很多关于 Go 通道和互斥锁的知识。谢谢:)
PSPS - James 的答案 (https://stackoverflow.com/a/19803051/143225) 是让服务器端 Go 代码一次处理多个请求的关键,因为 Go 通道是阻塞的,这意味着一次只能接收 1 个 goroutine。因此,OP问题的解决方案是结合前端和后端代码更改。
Go 频道不是多播的。 也就是说,如果您有多个从通道读取的 goroutine,则当您将值写入通道而不是将其广播给所有读取器时,只有一个会唤醒。
一种替代方法是改用条件变量:
var (
lock sync.Mutex
cond = sync.NewCond(&lock)
)
在PollResponse
处理程序中,您可以使用以下命令等待条件:
lock.Lock()
cond.Wait()
message := strconv.Itoa(counter)
lock.Unlock()
// write message to response
在PushHandler
处理程序中,可以使用以下内容广播更改:
lock.Lock()
counter += 1
cond.Broadcast()
lock.Unlock()