【分段傳輸】c#使用IAsyncEnumerable實現流式分段傳輸

四處觀察發表於2023-10-20

引言

    在使用SSE的時候,前端可以實現流式傳輸,但是有個問題就是這是一個獨佔的連線,相當於如果你不手動關閉連線,就會一直請求,一直連線呼叫介面,而且傳送的資料格式也是按照定義好的協議來,而使用c#自帶的IAsyncEnumerable也可以實現流式傳輸,不過返回的資料是在之前返回的基礎上進行累加,需要自己做處理,我的例子是使用的是ajax來實現,群友有提供了fetch的實現程式碼,接下來我們看看c#IAsyncEnumerable實現傳輸的ajax方案和fetch的程式碼吧。

AJAX

     下面是原始碼和gif效果展示,可以看到我們返回的是一個IAsyncEnumerable<int>型別的結果,在第二段程式碼,我們都知道ajax是根據xhrhttprequest封裝的,所以自然也可以用一些它的一些事件,所以我們在此處用了onprogress來監聽我們請求的進度,在這裡我們就可以獲取到每一次寫了哪些東西,從而實現一個流傳輸,因為後端寫也是一個位元組一個位元組去寫的,前端接收也是如此。

 [HttpGet("Postb")]
 public async IAsyncEnumerable<int> PostB()
 {
    await foreach (var item in  GetData())
     {
         yield return item;
     }
 }
  private async IAsyncEnumerable<int> GetData()
  {
      foreach (int item in Enumerable.Range(0,100))
      {
          await Task.Delay(100);
          yield return item;
      }
  }

 

<!DOCTYPE html>
<html>
<head>
    <title>AJAX Example</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script>

        function callAjax() {
            $.ajax({
                url: 'http://localhost:5203/WeatherForecast/Postb',
    method: 'GET',
    contentType: 'application/json',
    xhrFields: {
      onprogress: function (e) {
        var msg = e.currentTarget.response;
        $("#list").append(`<h5>${msg}</h5>`);
        console.log("接收的資料是=>" + msg);
        },
        onchange: function (a) {
            debugger;
        }
    },
    success: function () {
      console.log("分塊讀取完成");
    },
    error: function (xhr, status, error) {
      console.log("請求失敗");
      console.log("錯誤資訊: " + error);
    }
  });
        }
    </script>
</head>
<body>
    <button onclick="callAjax()">呼叫AJAX</button>
</body>
</html>

 SSE

    SSE全稱Server Sent Event,從名字我們可以看出,這是一個服務端單向傳送到客戶端的,與WebSocket不同,但是兩者都是長連線,上面的ajax的響應標頭是applycation/json,SSE的必須是text/event-stream,並且SSE的傳送的引數也都是有固定的格式,每一個傳送的訊息都是由\n\n分割,每一個message由若干個可選的欄位組成,例如下面,field:value是一個message裡面的內容,field可選範圍是下面那四個,第二程式碼段是後端的程式碼,展示了一個完整的message,包括了data,event,retry和id,其中上面,我們設定了響應的Content-type是text/event-stream,設定是不快取no-cache,下面設定是保持連線,keepalive,因為是長連線嘛,id和data可以隨便給,retry是埠連線後的一個重新連線時間,event是一個事件的名稱,我們給客戶端返回這個格式的內容,客戶端就會根據這個內容就返回資料,呼叫我們的event,從而實現一個流式輸出。

[field]: value\n  //這是一個Message

//下面是可選的欄位
data
event
id
retry
 [HttpGet("Posta")]
 public  IActionResult Posta()
 {
     if (Response.Headers.ContainsKey("Content-Type"))
     {
         Response.Headers.Remove("Content-Type");
         Response.Headers.Add("Content-Type", "text/event-stream");
     }
     else
     {
         Response.Headers.Add("Content-Type", "text/event-stream");
     }
     Response.Headers.Add("Cache-Control", "no-cache");
     Response.Headers.Add("Connection", "keep-alive");
     string data =
               $"id: {Random.Shared.Next()} \n" +
               $"retry: {Random.Shared.Next(0, 100) * 30}\n" +
               $"event: message\n" +
               $"data: {Random.Shared.Next()}\n\n";return Content(data);
 }
<!DOCTYPE html>
<html>
<head>
    <title>SSE Example</title>
    <script>
        var eventSource = new EventSource("http://localhost:5203/WeatherForecast/Posta");  
        eventSource.addEventListener("message", function(event) {
var a=document.getElementById("aaa");
a.innerHTML+="<a>"+event.data+"</a><br>"
            console.log("Received message: " + event.data);
        });
        eventSource.addEventListener("error", function(event) {
            console.log("Error occurred");
        });
    </script>
</head>
<body>
<div id='aaa'></div>
</body>
</html>

 總結

    以上便是今天的全部內容,當然,圖片的流式傳輸,返回html然後顯示,也可以直接去給響應流寫資料,content-type是stream的形式,會一點一點的載入,感興趣的朋友可以自己手動嘗試一下下咯。

相關文章