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);
}
}
使用者端
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
:一旦獲取到響應頭即完成操作,不用等到整個內容響應
<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);
}
}