C#實現 Server-sent Events
基於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 };
開啟瀏覽器的工發者工具,在網路選項中檢視詳細內容:
轉載請標明本文來源:https://www.cnblogs.com/yswenli/p/14246521.html
更多內容歡迎我的的github:https://github.com/yswenli/SAEA
如果發現本文有什麼問題和任何建議,也隨時歡迎交流~