C#實現 Server-sent Events
- 2021 年 1 月 7 日
- 筆記
- c#, js, Server sent Events, sse
基於http協議交互的推送方法大概方法如下:
- 輪詢(ajax),比較耗費伺服器資源。COMET方式(COMET 技術並不是 HTML 5 )
- websocket 雙向數據推送,靈活,功能強大
- Server-sent-event(簡稱SSE),單項數據推送(Server-sent Events 規範是 HTML 5 規範的一個組成部分)
這裡我們研究一下SSE;
一、什麼是SSE
Server-sent Events 規範是 HTML 5 規範的一個組成部分,具體的規範文檔見參考資源。該規範比較簡單,主要由兩個部分組成:第一個部分是伺服器端與瀏覽器端之間的通訊協議,第二部分則是在瀏覽器端可供 JavaScript 使用的 EventSource 對象。通訊協議是基於純文本的簡單協議。伺服器端的響應的內容類型是「text/event-stream」。響應文本的內容可以看成是一個事件流,由不同的事件所組成。每個事件由類型和數據兩部分組成,同時每個事件可以有一個可選的標識符。不同事件的內容之間通過僅包含回車符和換行符的空行(「\r\n」)來分隔。每個事件的數據可能由多行組成。嚴格地說,HTTP協議無法做到伺服器主動推送資訊。但是有一種變通的發光法,就是伺服器向客戶端聲明,接下來要發送的是流資訊,也就是說,發送的不是一次性的數據包,而是一個數據流,會連續不斷的發送過來。這是客戶端不會關閉連接,會一直等待伺服器發過來的數據流,影片播放就是這樣的例子。本質上這種通訊就是以流資訊的方式,完成一次用時很長的下載。
二、SSE傳輸協議分析
了解了什麼是SSE之後就發現這種模式針對後端開發來說是一個巨大的改進,可以像ajax一樣,卻比ajax節省資源;能實現websocket的伺服器推送卻不需要更換協議和埠,就像寫一個特別點的api介面一樣方便。跟蹤一下sse的報文顯示,
1 : this is a comment\n 2 reply: 3000\n 3 event: message\n 4 data: first\n\n 5 data: second\n\n 6 id: 100\n 7 event: myevent\n 8 data: third\n\n 9 id: 101\n 10 : this is a comment\n 11 data: fourth\n 12 data: fourth continue\n\n
接下就按如下來分析報文內容:
類型為空白,表示該行是注釋,會在處理時被忽略。
類型為 data,表示該行包含的是數據。以 data 開頭的行可以出現多次。所有這些行都是該事件的數據。
類型為 event,表示該行用來聲明事件的類型。瀏覽器在收到數據時,會產生對應類型的事件。
類型為 id,表示該行用來聲明事件的標識符。
類型為 retry,表示該行用來聲明瀏覽器在連接斷開之後進行再次連接之前的等待時間。
三、C#實現SSE服務端
SSE的內容還是很簡潔的,了解了差不多了,現在開始做起來。
1.根據SSE規範對html的頭部進行處理,主要就是添加text/event-stream類型,去掉快取
1 HttpContext.Current.Response.ContentType = "text/event-stream; charset=utf-8"; 2 HttpContext.Current.Response.SetHeader(ResponseHeaderType.CacheControl, "no-cache"); 3 HttpContext.Current.Response.SetHeader(ResponseHeaderType.KeepAlive, "timeout=5"); 4 HttpContext.Current.Response.Status = HttpStatusCode.OK; 5 HttpContext.Current.Response.SendHeader(-1);
2.封裝SSE數據格式,SSE的數據都是採用UTF8進行處理的
1 ServerSent(Encoding.UTF8.GetBytes($"id: {id?.Trim()}\nevent: {@event?.Trim()}\ndata: {SerializeHelper.Serialize(t)}\n\n"));
僅需二步就已經完成了SSE服務端的處理了,下面是SAEA.MVC下面的一個完整封裝類EventStream
1 /**************************************************************************** 2 *項目名稱:SAEA.MVC 3 *CLR 版本:4.0.30319.42000 4 *機器名稱:WALLE-PC 5 *命名空間:SAEA.MVC 6 *類 名 稱:EventStream 7 *版 本 號:V1.0.0.0 8 *創建人: yswenli 9 *電子郵箱:[email protected] 10 *創建時間:2021/1/6 14:02:09 11 *描述: 12 *===================================================================== 13 *修改時間:2021/1/6 14:02:09 14 *修 改 人: yswenli 15 *版 本 號: V1.0.0.0 16 *描 述: 17 *****************************************************************************/ 18 using SAEA.Common; 19 using SAEA.Common.Serialization; 20 using SAEA.Common.Threading; 21 using SAEA.Http.Model; 22 using System.Net; 23 using System.Text; 24 25 namespace SAEA.MVC 26 { 27 /// <summary> 28 /// SSE伺服器事件流 29 /// </summary> 30 public class EventStream : ActionResult, IEventStream 31 { 32 /// <summary> 33 /// 最後一次接收到的事件的標識符 34 /// </summary> 35 public int LastEventID 36 { 37 get; 38 private set; 39 } 40 41 /// <summary> 42 /// SSE伺服器事件流 43 /// </summary> 44 /// <param name="retry">指定瀏覽器重新發起連接的時間間隔</param> 45 public EventStream(int retry = 3 * 1000) 46 { 47 this.ContentEncoding = Encoding.UTF8; 48 49 if (HttpContext.Current.Request.Headers.ContainsKey("Last-Event-ID")) 50 { 51 if (int.TryParse(HttpContext.Current.Request.Headers["Last-Event-ID"], out int id)) 52 { 53 LastEventID = id; 54 } 55 } 56 57 HttpContext.Current.Response.ContentType = "text/event-stream; charset=utf-8"; 58 HttpContext.Current.Response.SetHeader(ResponseHeaderType.CacheControl, "no-cache"); 59 HttpContext.Current.Response.SetHeader(ResponseHeaderType.KeepAlive, "timeout=5"); 60 HttpContext.Current.Response.Status = HttpStatusCode.OK; 61 HttpContext.Current.Response.SendHeader(-1); 62 63 //心跳 64 var pong = $"SAEAServer PONG {DateTimeHelper.Now:yyyy:MM:dd HH:mm:ss.fff}"; 65 66 TaskHelper.LongRunning(() => 67 { 68 ServerSent(Encoding.UTF8.GetBytes($": {SerializeHelper.Serialize(pong)}\n\n")); 69 }, 1000); 70 71 //斷開重連時長 72 ServerSent(Encoding.UTF8.GetBytes($"retry: {retry}\n\n")); 73 } 74 /// <summary> 75 /// 發送通知 76 /// </summary> 77 /// <param name="str"></param> 78 /// <param name="event"></param> 79 /// <param name="id"></param> 80 public void ServerSent<T>(T t, string @event = "message", string id = "") where T : class 81 { 82 if (t != null) 83 ServerSent(Encoding.UTF8.GetBytes($"id: {id?.Trim()}\nevent: {@event?.Trim()}\ndata: {SerializeHelper.Serialize(t)}\n\n")); 84 } 85 /// <summary> 86 /// 發送通知 87 /// </summary> 88 /// <param name="content"></param> 89 public void ServerSent(byte[] content) 90 { 91 HttpContext.Current.Response.SendData(content); 92 } 93 } 94 }
3.使用EventStream類快速實現伺服器推送
將EventStream集成到Controller中,那麼在業務繼承類中就可以直接使用封裝好的SSE功能了,如下例:
1 /**************************************************************************** 2 *項目名稱:SAEA.MVCTest.Controllers 3 *CLR 版本:4.0.30319.42000 4 *機器名稱:WALLE-PC 5 *命名空間:SAEA.MVCTest.Controllers 6 *類 名 稱:EventStreamController 7 *版 本 號:V1.0.0.0 8 *創建人: yswenli 9 *電子郵箱:[email protected] 10 *創建時間:2021/1/6 13:57:09 11 *描述: 12 *===================================================================== 13 *修改時間:2021/1/6 13:57:09 14 *修 改 人: yswenli 15 *版 本 號: V1.0.0.0 16 *描 述: 17 *****************************************************************************/ 18 using SAEA.MVC; 19 using System; 20 using System.Collections.Generic; 21 using System.Text; 22 using System.Threading; 23 24 namespace SAEA.MVCTest.Controllers 25 { 26 /// <summary> 27 /// EventStreamController 28 /// </summary> 29 public class EventStreamController : Controller 30 { 31 /// <summary> 32 /// 發送通知 33 /// </summary> 34 /// <returns></returns> 35 public ActionResult SendNotice() 36 { 37 try 38 { 39 var es = GetEventStream(); 40 41 for (int i = 0; ; i++) 42 { 43 var str = $"SAEA.MVC EventStream Test {i}"; 44 45 es.ServerSent(str); 46 47 Thread.Sleep(1000); 48 } 49 } 50 catch (Exception ex) 51 { 52 53 } 54 return Empty(); 55 } 56 } 57 }
四、驗證SSE功能
了解了SSE技術相關理論,並按理論封裝了EventStream,最後使用EventStream實現了一個推送測試邏輯,接下來就是使用js的EventSource對象在瀏覽器中來驗證了。
創建一個網頁,在html中的js中輸入:
1 var source = new EventSource("/api/eventstream/sendnotice"); 2 source.onmessage = function (event) { 3 document.getElementById("eventstream").innerHTML += event.data + "<br/>"; 4 };
打開瀏覽器的工發者工具,在網路選項中查看詳細內容:
轉載請標明本文來源://www.cnblogs.com/yswenli/p/14246521.html
更多內容歡迎我的的github://github.com/yswenli/SAEA
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~