4

ソケットや類似のものを実装するために利用可能なライブラリのいずれかを使用せずに、MVC 4 Web で軽量の HTML5 Server-Sent Event 実装をセットアップしようとしています。私が試している軽量のアプローチは次のとおりです。

クライアント側: EventSource(またはjquery.eventsourceIE の場合)

サーバー側:長いポーリング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サーバーへのストリーム接続を開き、
  • サーバーは安全な時間 (たとえば 2 分間) 開いたままにして、デッド クライアントからのスレッド リークから保護します。
  • 新しいメッセージ イベントがサーバーによって受信されると (そして などのスレッド セーフなコレクションにエンキューされるとBlockingCollection)、それらはオープン ストリームでクライアントにプッシュされます。

    • メッセージ 1 は T+0ms で受信され、T+x でクライアントにプッシュされます
    • メッセージ 2 はT+200msで受信され、 T+x+200msでクライアントにプッシュされます

実際の動作:

  • EventSourceサーバーへのストリーム接続を開き、
  • サーバーはメッセージイベントが到着するまでそれを開いたままにします (長いポーリングのおかげで)
  • メッセージが受信されると、MVC はメッセージをプッシュして接続を閉じます。
  • EventSource接続を再開する必要があり、これは数秒後に発生します。
    • メッセージ 1 は T+0ms で受信され、T+x でクライアントにプッシュされます
    • メッセージ 2 はT+200msで受信され、 T+x+3200msでクライアントにプッシュされます

クライアントが通常のポーリングと同様に再接続を開始し、メッセージ配信が遅延するため、SSE を使用する目的が無効になるため、これは問題です。

さて、質問: 最初のメッセージを送信し、同じ接続でさらにメッセージを送信した後、接続を開いたままにするネイティブな方法はありますか?

4

1 に答える 1

4

データの送信を SimpleComplete に依存する代わりに、 Response.Flushを使用してデータを送信します。AsyncManager.OutstandingOperations.Decrement() を実行することで、リクエストの処理が完了し、レスポンスを送信して接続を閉じる準備ができたことを AsyncController に伝えます。代わりに、接続が失われるまで OutStandingOperations.Decrement() を呼び出さないようにします。クライアントにメッセージをプッシュするときはいつでも、バックグラウンド スレッドから直接Response.Writeと Response.Flush を呼び出します。また、AsyncController にはデフォルトのタイムアウトがあり、その後、自動的に接続が閉じられます。これを回避するには、関連するアクションにNoAsyncTimeoutAttributeを使用する必要があります。

余談ですが、AsyncController のインターフェースでは、SSE ストリームをきれいに実装することはできません。Asp.NET MVC 4 を使用していたので、個人的にHttpTaskAsyncHandlerを実装していたでしょう。

于 2012-06-09T04:57:23.840 に答える