在现代Web应用中,实时数据传输和高效的数据流处理变得越来越重要。AspNetCore 提供了多种流式响应机制,以满足不同场景下的需求。
在使用ChatGpt,deepseek的适合有没有想过ai的逐字显示回答是怎么实现的,下面将介绍三种主要的流式响应来实现此功能。
Server-Sent Events (SSE) 是一种允许服务器主动向客户端推送数据的机制,适用于实时更新的应用(如聊天应用、实时监控、新闻推送等)。
通过设置Content-Type: text/event-stream
来使用SSE协议,客户端就能实时接收服务器发送的消息。
服务器端:
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespaceStreamedResponseExample.Controllers
{
[Route("api/[controller]")]
[ApiController]
publicclassStreamController : ControllerBase
{
[HttpGet("sse")]
public async Task StreamSse()
{
Response.ContentType = "text/event-stream";
Response.Headers.Add("Cache-Control", "no-cache");
Response.Headers.Add("Connection", "keep-alive");
var messages = newstring[] {
"Hello, ",
"this is an SSE message!",
"Here's another message."
};
foreach (var message in messages)
{
await Response.WriteAsync($"data: {message}\n\n");
await Response.Body.FlushAsync(); // 强制立即发送
await Task.Delay(1000); // 每条消息间隔1秒
}
}
}
}
客户端-html:
<div id="messages"></div>
<script>
// 创建SSE连接
const eventSource = newEventSource('https://localhost:7148/WeatherForecast/sse');
// 监听消息事件
eventSource.onmessage = function (event) {
const messageContainer = document.getElementById('messages');
const newMessage = document.createElement('p');
newMessage.textContent = event.data;
messageContainer.appendChild(newMessage);
// 滚动到最新消息
messageContainer.scrollTop = messageContainer.scrollHeight;
};
// 监听打开连接事件
eventSource.onopen = function () {
console.log("连接已打开");
};
// 监听错误事件
eventSource.onerror = function (error) {
console.error("发生错误", error);
eventSource.close(); // 关闭连接
};
</script>
说明:
Response.ContentType = "text/event-stream"
:设置响应的类型为SSE。data: {message}\n\n
格式发送消息,每次都以“data:”开头。EventSource
API在客户端接收事件流。SSE是一种轻量级且简单的流式响应方式,尤其适用于向客户端推送消息的场景,如实时数据更新。
WebSocket 是一种全双工通信协议,适用于需要双向实时通信的应用,如在线聊天、多人游戏等。优势在于可以同时接收和发送消息,且没有HTTP请求/响应的开销。
通过WebSocket,客户端和服务器之间建立持久连接,双向传输消息。
使用WebSocket进行流式传输服务器端:
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespaceStreamedResponseExample.Controllers
{
publicclassChatHub : Hub
{
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
}
[Route("api/[controller]")]
[ApiController]
publicclassStreamController : ControllerBase
{
privatereadonly IHubContext<ChatHub> _hubContext;
public StreamController(IHubContext<ChatHub> hubContext)
{
_hubContext = hubContext;
}
[HttpGet("websocket")]
public async Task WebSocketStream()
{
var messages = newstring[] {
"Hello, ",
"this is a WebSocket message!",
"Here's another one."
};
foreach (var message in messages)
{
await _hubContext.Clients.All.SendAsync("ReceiveMessage", message);
await Task.Delay(1000); // 每条消息间隔1秒
}
}
}
}
客户端:
const connection = new signalR.HubConnectionBuilder()
.withUrl("/chatHub")
.build();
connection.on("ReceiveMessage", function (message) {
console.log("Received message: ", message);
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
分块传输编码是HTTP/1.1的一种机制,它允许服务器以多个“块”的形式将响应数据发送给客户端。客户端接收到每个块后可以立即处理,而不是等待所有数据传输完成。
ASP.NET Core默认支持分块传输编码,当响应体的内容未知时,分块传输会自动启用。
使用分块传输编码(Chunked Encoding)服务器端:
using Microsoft.AspNetCore.Mvc;
using System.Text;
using System.Threading.Tasks;
namespaceStreamedResponseExample.Controllers
{
[Route("api/[controller]")]
[ApiController]
publicclassStreamController : ControllerBase
{
[HttpGet("chunked")]
public async Task ChunkedResponse()
{
Response.ContentType = "text/plain";
var phrases = newstring[] {
"This is ",
"a chunked ",
"response! ",
"Enjoy it."
};
foreach (var phrase in phrases)
{
byte[] buffer = Encoding.UTF8.GetBytes(phrase);
await Response.Body.WriteAsync(buffer, 0, buffer.Length);
await Response.Body.FlushAsync(); // 强制发送数据块
await Task.Delay(1000); // 每块之间的延时
}
}
}
}
客户端:
客户端可以像普通HTTP请求一样使用fetch
或XMLHttpRequest
接收分块数据,并逐步处理每个数据块。
<body>
<div id="content"></div>
<script>
fetch('https://localhost:7128/WeatherForecast/ChunkedResponse/chunked')
.then(response => {
const reader = response.body.getReader();
const decoder = newTextDecoder();
const contentDiv = document.getElementById('content');
functionreadChunk () {
reader.read().then(({ done, value }) => {
if (done) return;
contentDiv.innerHTML += decoder.decode(value, { stream: true }) + '<br>';
readChunk(); // 继续读取下一个数据块
});
}
readChunk();
});
</script>
</body>
说明:
关注公众号“Net分享”,技术文章第一时间推送,随缘更新 , 分享一些你可能注意不到的细节。