4

我正在尝试在我的 MVC 4 Web 上设置一个轻量级的 HTML5 服务器发送事件实现,而不使用可用于实现套接字和类似内容的库之一。我正在尝试的轻量级方法是:

客户端:( EventSourcejquery.eventsource用于 IE)

服务器端:长轮询AsynchController(很抱歉将原始测试代码放在这里,但只是为了提供一个想法)

public class HTML5testAsyncController : AsyncController
    {
        private static int curIdx = 0;
        private static BlockingCollection<string> _data = new BlockingCollection<string>();
        static HTML5testAsyncController()
        {
            addItems(10);
        }
 //adds some test messages
        static void addItems(int howMany)
        {
            _data.Add("started");
            for (int i = 0; i < howMany; i++)
            {
                _data.Add("HTML5 item" + (curIdx++).ToString());
            } _data.Add("ended");
        }

// here comes the async action, 'Simple'
        public void SimpleAsync()
        {
            AsyncManager.OutstandingOperations.Increment();

            Task.Factory.StartNew(() =>
            {
                var result = string.Empty; var sb = new StringBuilder();
                string serializedObject = null;
            //wait up to 40 secs that a message arrives
                if (_data.TryTake(out result, TimeSpan.FromMilliseconds(40000)))
                {
                    JavaScriptSerializer ser = new JavaScriptSerializer();
                    serializedObject = ser.Serialize(new { item = result, message = "MSG content" });
                    sb.AppendFormat("data: {0}\n\n", serializedObject);
                }
                AsyncManager.Parameters["serializedObject"] = serializedObject;
                AsyncManager.OutstandingOperations.Decrement();
            });
        }
  // callback which returns the results on the stream
        public ActionResult SimpleCompleted(string serializedObject)
        { ServerSentEventResult sar = new ServerSentEventResult(); 
            sar.Content = () => { return serializedObject; };
            return sar;

        }
  //pushes the data on the stream in a format conforming HTML5 SSE
        public class ServerSentEventResult : ActionResult
        {
            public ServerSentEventResult() { }
            public delegate string GetContent(); 
            public GetContent Content { get; set; }        
            public int Version { get; set; }
            public override void ExecuteResult(ControllerContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException("context");
                } if (this.Content != null)
                {
                    HttpResponseBase response = context.HttpContext.Response;
                    // this is the content type required by chrome 6 for server sent events              
                    response.ContentType = "text/event-stream";

                    response.BufferOutput = false;                // this is important because chrome fails with a "failed to load resource" error if the server attempts to put the char set after the content type          
                    response.Charset = null;
                    string[] newStrings = context.HttpContext.Request.Headers.GetValues("Last-Event-ID");
                    if (newStrings == null || newStrings[0] != this.Version.ToString())
                    {
                        string value = this.Content();
                        response.Write(string.Format("data:{0}\n\n", value));
                        //response.Write(string.Format("id:{0}\n", this.Version));
                    }
                    else
                    {
                        response.Write("");
                    }
                }
            }
        }
    }

问题出在服务器端,因为预期结果与实际情况之间仍有很大差距。

预期结果

  • EventSource打开到服务器的流连接,
  • the server keeps it open for a safe time (say, 2 minutes) so that I am protected from thread leaking from dead clients,
  • as new message events are received by the server (and enqueued to a thread safe collection such as BlockingCollection) they are pushed in the open stream to the client:

    • message 1 received at T+0ms, pushed to the client at T+x
    • message 2 received at T+200ms, pushed to the client at T+x+200ms

Actual behaviour:

  • EventSource opens a stream connection to the server,
  • the server keeps it open until a message event arrives (thanks to long polling)
  • once a message is received, MVC pushes the message and closes the connection.
  • EventSource has to reopen the connection and this happens after a couple of seconds.
    • message 1 received at T+0ms, pushed to the client at T+x
    • 消息 2 在T+200ms收到,在T+x+3200ms推送到客户端

这是不行的,因为它违背了使用 SSE 的目的,因为客户端像正常轮询一样重新开始重新连接,并且消息传递被延迟。

现在,问题是:在发送第一条消息并在同一连接上发送更多消息后,是否有一种本地方法可以保持连接打开?

4

1 回答 1

4

您希望使用Response.Flush发送数据,而不是依赖 SimpleComplete 发送数据。通过执行 AsyncManager.OutstandingOperations.Decrement(),您是在告诉 AsyncController 您已完成对请求的处理,并且已准备好发送响应并关闭连接。相反,您避免调用 OutStandingOperations.Decrement() 直到连接丢失等。每当您想向客户端推送消息时,您可以直接从某个后台线程调用Response.Write和 Response.Flush。此外,AsyncControllers 有一个默认超时,之后它们会自动关闭连接。为了解决这个问题,您需要将NoAsyncTimeoutAttribute用于相关操作。

附带说明一下,AsyncController 的接口并没有真正允许以干净的方式实现 SSE 流。考虑到我使用的是 Asp.NET MVC 4,我会亲自实现一个HttpTaskAsyncHandler 。

于 2012-06-09T04:57:23.840 回答