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

时间:2016-03-01 09:46:41

标签: javascript asp.net-mvc server-sent-events

我看到代码here的奇怪行为。

客户端(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方法中的循环会选择新项目并通过{{1}发出事件(在服务器上使用Debug.Print确认)。但是,在第二次按ping之前,浏览器不会触发Response.Write,浏览器会发出另一个事件;此时第一个事件的数据达到onmessage

我该如何解决这个问题?

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

onmessage

这就是实际发生的事情:

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

(在Chrome中运行时,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 请求在“网络”标签中列为常规待处理状态。我不确定这是否是服务器发送事件的正常行为,或者可能是&#39;与此问题有关。)

修改

message之后的msg变量的字符串表示如下所示:

Response.Write

非常明确地包括换行符。

5 个答案:

答案 0 :(得分:1)

这不是每个人的答案,但希望它会引领一个。我能够使用以下代码。

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>

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

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

re.search("^[A-Z]+([A-Z\w]+|\s)+$","Diploma in Fine Art from Royal School of Art & Design")

答案 3 :(得分:0)

编辑:请先尝试创建对象,然后将其传递给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");

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

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

答案 4 :(得分:0)

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

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

问题here中有一个例子。