客户端始终响应上一个服务器发送的事件,而不是当前事件

Client always responding to previous server-sent event, not current event

本文关键字:事件 响应 上一个 服务器 客户端      更新时间:2023-09-26

我在这里看到代码的奇怪行为。

客户端(Javascript):

<input type="text" id="userid" placeholder="UserID" /><br /> 
<input type="button" id="ping" value="Ping" />
  
<script>
    var es = new EventSource('/home/message');
    es.onmessage = function (e) {
        console.log(e.data);
    };
    es.onerror = function () {
        console.log(arguments);
    };
    $(function () {
        $('#ping').on('click', function () {
            $.post('/home/ping', {
                UserID: parseInt($('#userid').val()) || 0
            });
        });
    });
</script>

服务器端 (C#):

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Web.Mvc;
using Newtonsoft.Json;
   
namespace EventSourceTest2.Controllers {
    public class PingData {
        public int UserID { get; set; }
        public DateTime Date { get; set; } = DateTime.Now;
    }
    public class HomeController : Controller {
        public ActionResult Index() {
            return View();
        }  
        static ConcurrentQueue<PingData> pings = new ConcurrentQueue<PingData>();
        public void Ping(int userID) {
            pings.Enqueue(new PingData { UserID = userID });
        }
        public void Message() {
            Response.ContentType = "text/event-stream";
            do {
                PingData nextPing;
                if (pings.TryDequeue(out nextPing)) {
                    var msg = "data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "'n'n";
                    Response.Write(msg);
                }
                Response.Flush();
                Thread.Sleep(1000);
            } while (true);
        }
    }
}

一旦我按 ping 将新项目添加到pings队列中,Message 方法中的循环就会选取新项目并通过Response.Write发出事件(使用服务器上的 Debug.Print 确认)。但是,浏览器不会触发onmessage,直到我第二次按 ping,并且浏览器发出另一个事件;此时,来自第一个事件的数据到达 onmessage

我该如何解决这个问题?

<小时 />

澄清一下,这是我期望的行为:

Client                       Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
                             Eneque new item to pings
                             Message loop issues server-sent event
EventSource calls onmessage

这是实际发生的事情:

Client                       Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
                             Eneque new item to pings
                             Message loop issues server-sent event
(Nothing happens)
Press Ping button again
New XHR to /home/ping
EventSource calls onmessage with previous event data

(在 Chrome 中运行时,message请求在"网络"标签中列为"始终待处理"。我不确定这是否是服务器发送事件的正常行为,或者可能与问题有关。

编辑

Response.Write之后的msg变量的字符串表示形式如下所示:

"data:{'"UserID'":105,'"Date'":'"2016-03-11T04:20:24.1854996+02:00'"}'n'n"

非常清楚地包括换行符。

这不是一个答案,但希望它能引导一个答案。我能够让它使用以下代码工作。

public void Ping(int id)
{
    pings.Enqueue(new PingData { ID = id });
    Response.ContentType = "text/plain";
    Response.Write("id received");
}
public void Message()
{
    int count = 0;
    Response.ContentType = "text/event-stream";
    do {
        PingData nextPing;
        if (pings.TryDequeue(out nextPing)) {
            Response.ClearContent();
            Response.Write("data:" + nextPing.ID.ToString() + " - " + nextPing.Date.ToLongTimeString() + "'n'n");
            Response.Write("event:time" + "'n" + "data:" + DateTime.Now.ToLongTimeString() + "'n'n");
            count = 0;
            Response.Flush();
        }
        if (!Response.IsClientConnected){break;}
        Thread.Sleep(1000);
        count++;
    } while (count < 30);   //end after 30 seconds of no pings
}

产生影响的代码行是 第二Response.Write .该消息不会出现在浏览器中,直到下一次 ping 与您的问题类似,但 ping 始终显示。如果没有该行,ping 将仅在下一次 ping 之后出现,或者一旦我的 30 秒计数器用完。

30 秒计时器后出现的丢失消息使我得出结论,这要么是 .Net 问题,要么是我们缺少的东西。这似乎不是事件源问题,因为该消息出现在服务器事件上,而且我在使用 PHP 进行 SSE 时没有遇到任何问题。

作为参考,这是我用来测试的 JavaScript 和 HTML。

<input type="text" id="pingid" placeholder="ID" /><br />
<input type="button" id="ping" value="Ping" />
<div id="timeresponse"></div>
<div id="pingresponse"></div>
<script>
    var es = new EventSource('/Home/Message');
    es.onmessage = function (e) {
        console.log(e.data);
        document.getElementById('pingresponse').innerHTML += e.data + " - onmessage<br/>";
    };
    es.addEventListener("ping", function (e) {
        console.log(e.data);
        document.getElementById('pingresponse').innerHTML += e.data + " - onping<br/>";
    }, false);
    es.addEventListener("time", function (e) {
        document.getElementById('timeresponse').innerHTML = e.data;
    }, false);
    es.onerror = function () {
        console.log(arguments);
        console.log("event source closed");
        es.close();
    };
    window.onload = function(){
        document.getElementById('ping').onclick = function () {
            var xmlhttp = new XMLHttpRequest();
            xmlhttp.onload = function () {
                console.log(this.responseText);
            };
            var url = '/Home/Ping?id=' + document.getElementById('pingid').value;
            xmlhttp.open("GET", url);
            xmlhttp.send();
        };
    };
</script>

由于事件流只是文本数据,因此在将第一个事件写入响应之前缺少双换行符可能会影响客户端。来自 mdn 文档的示例建议

header("Content-Type: text/event-stream'n'n");

这可以应用于.NET响应处理(请注意Response.ClearContent()的副作用)。

如果感觉太笨拙,你可以用一个保持活动的评论开始你的直播(如果你想避免超时,你可能不得不定期发送评论):

: just a keep-alive comment followed by two line-breaks, Response.Write me first

我不确定这是否有效,因为我现在无法尝试,但是添加一个 End 呢?

Response.Flush();
Response.End();

.net 的默认行为是序列化对会话状态的访问。它阻止并行执行。请求按顺序处理,对会话状态的访问是会话的独占。您可以覆盖每个类的默认状态。

 [SessionState(SessionStateBehavior.Disabled)]
 public class MyPulsingController
 {
 }

这里的问题中对此进行了说明。

编辑:请您先尝试创建对象,然后将其传递给 Enqueue?如:

PingData myData = new PingData { UserID = userID };
pings.Enqueue(myData);

可能会发生一些奇怪的事情,Dequeue 认为它已经完成了工作,但 PingData 对象尚未正确构造。

我们也可以尝试console.log("I made it to the function")而不是console.log(e.data).

----以下要求的先前信息

----

请确保服务器 Debug.Print 确认以下代码行:

Response.Write("data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "'n'n");

真的被执行了吗?请仔细检查一下。如果您可以捕获服务器发送的响应,那么我们可以看到它是什么吗?

我们还能看看你测试过的浏览器吗?并非所有浏览器都支持服务器事件。