Websocket、SSE(Server-Sent Events)和長輪詢(Long Polling)都是用於網頁和服務端通訊的技術。
Websocket是一種全雙工通訊協議,能夠實現客戶端和服務端之間的實時通訊。它基於TCP協議,並且允許伺服器主動向客戶端推送資料,同時也允許客戶端向伺服器傳送資料。
SSE是一種單向通訊協議,允許伺服器向客戶端推送資料,但不支援客戶端向伺服器傳送資料。SSE建立在HTTP協議上,透過在HTTP響應中使用特殊的Content-Type和事件流(event stream)格式來實現。
長輪詢是一種技術,客戶端向伺服器傳送一個請求,並且伺服器保持連線開啟直到有資料可以返回給客戶端。如果在指定的時間內沒有資料可用,則伺服器會關閉連線,客戶端需要重新建立連線並再次發起請求。
New Bing聊天頁面是透過WebSocket進行通訊。
Open AI的ChatGPT介面則是透過SSE協議由服務端推送資料
事實上,以上幾種方式包括長輪詢,都可以實現逐字顯示的效果。那還有沒有其他的辦法可以實現這種效果了呢?
流式響應
當客戶端返回流的時候,客戶端可以實時捕獲到返回的資訊,並不需要等全部Response結束了再處理。
下面就用ASP.NET Core Web API作為服務端實現流式響應。
返回文字內容
服務端
[HttpPost("text")]
public async Task Post()
{
string filePath = "文件.txt";
Response.ContentType = "application/octet-stream";
var reader = new StreamReader(filePath);
var buffer = new Memory<char>(new char[5]);
int writeLength = 0;
//每次讀取5個字元寫入到流中
while ((writeLength = await reader.ReadBlockAsync(buffer)) > 0)
{
if (writeLength < buffer.Length)
{
buffer = buffer[..writeLength];
}
await Response.WriteAsync(buffer.ToString());
await Task.Delay(100);
}
}
客戶端
- C# HttpClient
public async void GetText()
{
var url = "http://localhost:5000/config/text";
var client = new HttpClient();
using HttpRequestMessage httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, url);
var response = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseHeadersRead);
await using var stream = await response.Content.ReadAsStreamAsync();
var bytes = new byte[20];
int writeLength = 0;
while ((writeLength = stream.Read(bytes, 0, bytes.Length)) > 0)
{
Console.Write(Encoding.UTF8.GetString(bytes, 0, writeLength));
}
Console.WriteLine();
Console.WriteLine("END");
}
HttpCompletionOption
列舉有兩個值,預設情況下使用的是ResponseContentRead
-
ResponseContentRead
:等到整個響應完成才完成操作 -
ResponseHeadersRead
:一旦獲取到響應頭即完成操作,不用等到整個內容響應
- js XMLHttpRequest
<script>
var div = document.getElementById("content")
var url = "http://localhost:5000/config/text"
var client = new XMLHttpRequest()
client.open("POST", url)
client.onprogress = function (progressEvent) {
div.innerText = progressEvent.target.responseText
}
client.onloadend = function (progressEvent) {
div.append("END")
}
client.send()
</script>
用axios請求就是監聽onDownloadProgress
了。
瀏覽器是透過
Response Header
中的Content-Type
來解析服務端響應體的。如果後端介面沒有設定Response.ContentType = "application/octet-stream"
,onprogress
只會在響應全部完成後觸發。
返回圖片
服務端
[HttpGet("img")]
public async Task Stream()
{
string filePath = "pixelcity.png";
new FileExtensionContentTypeProvider().TryGetContentType(filePath, out string contentType);
Response.ContentType = contentType ?? "application/octet-stream";
var fileStream = System.IO.File.OpenRead(filePath);
var bytes = new byte[1024];
int writeLength = 0;
while ((writeLength = fileStream.Read(bytes, 0, bytes.Length)) > 0)
{
await Response.Body.WriteAsync(bytes, 0, writeLength);
await Task.Delay(100);
}
}