# 在 .NET 使用 Server-Sent-Events(SSE) [![hackmd-github-sync-badge](https://hackmd.io/QKKXbuKbSoaczKJa-8db5A/badge)](https://hackmd.io/QKKXbuKbSoaczKJa-8db5A) 本文主要介紹在 .NET 中使用 SSE 實現即時通訊。 ## SSE 與其他即時通訊方式的比較 在 JavaScript 中,常見的即時通訊方式有以下三種作法: * Polling:Client 定期向 Server 發送請求,進行資料請求和回應。這種方式常常會導致過多的請求和回應,佔用大量資源。 * [Server-Sent-Events(SSE)](https://developer.mozilla.org/zh-TW/docs/Web/API/Server-sent_events/Using_server-sent_events):一種單向連線,由 Server 向 Client 推送更新資料。此種方式有以下優點:單向連線,可降低伺服器負擔,只有一個TCP連線,減少頻寬浪費。 * [WebSocket](https://developer.mozilla.org/zh-TW/docs/Web/API/WebSocket):Client 與 Server 之間建立雙向連線,雙方可隨時傳送資料。WebSocket 可以減少網路流量,提高效率。 ## SSE JavaScript 的實作 SSE 預設有 `open`、`error` 和 `message` 三個事件,以下是一個簡單的 JavaScript 範例: ### SSE 程式碼範例 ```javascript const sse = new EventSource('Your API Url'); // 監聽 open 事件,當連接成功時會觸發此事件 sse.addEventListener('open', function (e) { console.log('SSE connection opened'); }); // 監聽 error 事件,當連接錯誤時會觸發此事件 sse.addEventListener('error', function (e) { console.log('SSE connection error'); }); // 監聽 message 事件,當接收到訊息時會觸發此事件 sse.addEventListener('message', function (e) { console.log('SSE message received', e); const data = JSON.parse(e.data); const messageElement = document.createElement('div'); messageElement.textContent = data.message; document.body.appendChild(messageElement); }); // 監聽自訂的 end 事件 sse.addEventListener('end', function (e) { console.log('SSE custon end', e); sse.close(); }); ``` ### `withCredentials` 屬性 在建立 Server-Sent Events(SSE)物件時,可以使用 `withCredentials` 屬性指定當發送跨域請求時,是否要傳送 CORS 驗證資訊,此屬性在同源網站的情況下並無作用。 :::warning SSE 使用跨域請求時,Server 端的 Header 也需要進行相應的 CORS 設定。 ::: ```javascript const sse = new EventSource('Your API Url', { withCredentials: true } ); ``` ## 在 .NET 中實作 SSE Server * Server 端會透過向 Client 端發送一條包含事件 (event) 和資料 (data) 的訊息,Client 端可以使用 EventSource 物件接收這些訊息。事件和資料都是可選的,每條訊息以一個空行結束。 * SSE Server 回傳的 Content-Type 為 `text/event-stream`。 ### 以 ASHX (泛型處理常式) 實作 ASHX 是 ASP.NET Web Forms 的一個特性,可以簡單地處理 Web 請求。在 SSE 的應用中,可以使用 ASHX 來處理 SSE 的請求。以下是一個簡單的 ASHX 程式碼範例: ```csharp public class SseHandler : IHttpHandler { public void ProcessRequest (HttpContext context) { // 設定 response 的 Content-Type 為 text/event-stream context.Response.ContentType = "text/event-stream"; context.Response.CacheControl = "no-cache"; // 模擬一個 SSE 事件流,每秒發送一個訊息 int count = 0; while (count < 10) { count++; context.Response.Write("data: " + "{\"message\": \"Hello SSE " + count + "\"}\n\n"); context.Response.Flush(); System.Threading.Thread.Sleep(1000); } // 回傳自訂的 end 事件,前面 JavaScript 範例會在此 Close SSE context.Response.Write("event: end\ndata: {}\n\n"); context.Response.Flush(); context.Response.End(); } public bool IsReusable { get { return false; } } } ``` :::info 每條訊息以空行作結束,所以第一個 `\n` 表示訊息換行,第二個 `\n` 以示訊息結束。 ::: ### 以 ASP.ENT Core Web API 實作 ```csharp [ApiController] [Route("[controller]")] public class SseController : ControllerBase { [HttpGet] public async Task GetAsync() { // 設定 response 的 Content-Type 為 text/event-stream Response.Headers.Add("Content-Type", "text/event-stream;"); Response.Headers.Add("Cache-Control", "no-cache"); // 模擬一個 SSE 事件流,每秒發送一個訊息 int count = 0; while (count < 10) { count++; await Response.WriteAsync($"data: {{\"message\": \"Hello SSE {count}\"}}\n\n"); await Response.Body.FlushAsync(); await Task.Delay(TimeSpan.FromSeconds(1)); } // 回傳自訂的 end 事件,前面 JavaScript 範例會在此 Close SSE await Response.WriteAsync("event: end\ndata: {}\n\n"); await Response.Body.FlushAsync(); } } ``` ###### tags: `.NET` `Server-Sent-Events`