长轮询,”全局”按钮,向所有人广播?

huangapple go评论220阅读模式
英文:

Long Polling, "Global" Button, Broadcast to everyone?

问题

我正在尝试实现一个全局按钮计数器,当任何/不同的用户点击它时,它会更新。所以想法是,如果有人点击按钮,我会在我的页面实例上看到计数器更新。

我目前已经使用长轮询技术,或者我认为是这样,但是经过审查,我认为在向所有浏览器“广播”更新时出现了错误。

目前的错误是,例如我打开了两个浏览器,并且我不断地在一个浏览器上点击,我点击按钮的那个浏览器只有一半的时间更新。它会显示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"
	"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)
	}
}
英文:

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:

&lt;html&gt;
&lt;script language=javascript&gt;
function longpoll(url, callback) {
var req = new XMLHttpRequest (); 
req.open (&#39;GET&#39;, url, true); 
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) { 
if (req.status == 200) {
callback(req.responseText);
longpoll(url, callback);
} else {
alert (&quot;long-poll connection lost&quot;);
}
}
};
req.send(null);
}
function recv(msg) {
var box = document.getElementById(&quot;counter&quot;);
box.innerHTML += msg + &quot;\n&quot;;
}
function send() {
var box = document.getElementById(&quot;counter&quot;);
var req = new XMLHttpRequest (); 
req.open (&#39;POST&#39;, &quot;/push?rcpt=&quot;, true); 
req.onreadystatechange = function (aEvt) {
if (req.readyState == 4) { 
if (req.status == 200) {
} else {
alert (&quot;failed to send!&quot;);
}
}
};
req.send(&quot;hi&quot;)
//box.innerHTML += &quot;test&quot; ;  
}
&lt;/script&gt;
&lt;body onload=&quot;longpoll(&#39;/poll&#39;, recv);&quot;&gt;
&lt;h1&gt; Long-Poll Chat Demo &lt;/h1&gt;
&lt;p id=&quot;counter&quot;&gt;&lt;/p&gt;
&lt;button onclick=&quot;send()&quot; id=&quot;test&quot;&gt;Test Button&lt;/button&gt;
&lt;/body&gt;
&lt;/html&gt;

Server:

package main
import (
&quot;net/http&quot;
&quot;log&quot;
&quot;io&quot;
//	&quot;io/ioutil&quot;
&quot;strconv&quot;
)
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 &lt;- strconv.Itoa(counter)
}
func PollResponse(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, &lt;-messages)
}
func main() {
http.Handle(&quot;/&quot;, http.FileServer(http.Dir(&quot;./&quot;)))
http.HandleFunc(&quot;/poll&quot;, PollResponse)
http.HandleFunc(&quot;/push&quot;, PushHandler)
err := http.ListenAndServe(&quot;:8010&quot;, nil)
if err != nil {
log.Fatal(&quot;ListenAndServe: &quot;, err)
}
}

答案1

得分: 8

Go通道不支持多播。也就是说,如果你有多个goroutine从通道中读取数据,当你向通道写入一个值时,只有一个goroutine会被唤醒,而不是广播给所有的读取者。

一个替代方案是使用条件变量:

var (
    lock sync.Mutex
    cond = sync.NewCond(&lock)
)

在你的PollResponse处理程序中,你可以等待条件变量:

lock.Lock()
cond.Wait()
message := strconv.Itoa(counter)
lock.Unlock()
// 将消息写入响应

在你的PushHandler处理程序中,你可以广播变化:

lock.Lock()
counter += 1
cond.Broadcast()
lock.Unlock()

以上是翻译好的内容,请确认是否满意。

英文:

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(&amp;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()

答案2

得分: 8

问题不在于Go代码(单独来说;参见PS PS),而是浏览器(Chrome)。对同一URL进行两次请求是按顺序而不是并行进行的。

解决方案
您需要向长轮询URL添加一个唯一的时间戳来欺骗浏览器:

req.open('GET', url+"?"+(new Date().getTime()), true);

PS - 通过这个问题,我对Go通道和互斥锁有了很多了解。谢谢 长轮询,”全局”按钮,向所有人广播?

PS PS - James的答案(https://stackoverflow.com/a/19803051/143225)是使服务器端的Go代码能够处理多个请求的关键,因为Go通道是阻塞的,这意味着一次只能有一个goroutine接收。因此,对于原始问题的解决方案是前端和后端代码的组合更改。

英文:

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 (&#39;GET&#39;, url+&quot;?&quot;+(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.

huangapple
  • 本文由 发表于 2013年11月6日 08:51:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/19802037.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定