C#通过API接口返回流式响应内容---SSE方式

发布于:2025-03-17 ⋅ 阅读:(24) ⋅ 点赞:(0)

1、背景

今年春节大火的DeepSeek,其中大家比较感兴趣的就是,DeepSeek返回的是一句一句的蹦出来的。这个就是流式响应。C#也可以实现,本篇就是展示流式响应的一个Demo。

2、实现效果

实现的效果如下:
在这里插入图片描述

3、具体实现

3.1 API端代码

创建一个asp.net core api项目,在controller中定义流式方式,代码如下:

using Microsoft.AspNetCore.Mvc;

namespace SSEWebApplication.Controllers
{
    [Route("api/[controller]/[action]")]
    [ApiController]
    public class StreamController : ControllerBase
    {
        [HttpGet]
        public async Task GetStreamSse()
        {
            Response.ContentType = "text/event-stream";
            Response.Headers["Cache-Control"] = "no-cache";
            Response.Headers["Connection"] = "keep-alive";
            Response.Headers["Access-Control-Allow-Origin"] = "*"; //可以实现跨域访问

            //假设流式的数据返回
            var messages = new string[] {"你好!","我是","北京清华长庚医院","信息管理部","郑林"};

            //模拟DeepSeek的流式返回
            for (int i = 0; i < messages.Length; i++)
            {
                if(i== messages.Length-1)
                {
                    await Response.WriteAsync($"data:{messages[i]}\n\n");
                    await Response.Body.FlushAsync();
                }
                else
                {
                    await Response.WriteAsync($"data:{messages[i]}\n\n");
                    await Response.Body.FlushAsync();
                    await Task.Delay(1000);
                }
            }
        }
    }
}

3.2 前端代码

前端代码如下:

<!DOCTYPE html>
<html>

<head>
<title>流式展示</title>
</head>

<body>
    <div id="messages"></div>

<script>
    // 创建SSE连接
    const eventSource =new EventSource('http://localhost:5105/api/Stream/GetStreamSse');
    
    // 监听消息事件
    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>
</body>
</html>

4、原理

在代码中我们使用了EventSource,这个称之为:服务器发送事件。有点类似socket,只不过这个是单向的,只能服务器发给客户端。

4.1 API的实现

1、API部分很简单,本质上就是一个文本流,类似我们下载文件一样,只不过,下载文件的是Stream流(二进制数据流),而EventSource传递的是string字符串。
2、API端发送有点类似海浪,一波一波的。如何判断发送给前端的这波数据结束了呢,就是\n\n
3、EventSource发送的数据的时候,是有格式要求的:

[发送类型]: 待发送的字符串

发送类型有:event、data、id、retry。

他们怎么用呢?有时候,我们需要把消息发给张三、李四,或同一个界面中的不同部分(股票的最新数据,以及当前企业的财务的消息数据),需要展示/更新界面的不同地方。服务器端就会这么写

if(sendtype=="company")
{
	await Response.WriteAsync($"event:company\n");
	await Response.WriteAsync($"data:发布了第三季度财务报表");	
	await Response.WriteAsync($"\n\n");	
	await Response.Body.FlushAsync();
}else
{
	await Response.WriteAsync($"event:stock\n");
	await Response.WriteAsync($"data:123");	
	await Response.WriteAsync($"\n\n");	
	await Response.Body.FlushAsync();
}

然后前端就可以这么写

evtSource.addEventListener("company", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  newElement.textContent = `ping at ${time}`;
  eventList.appendChild(newElement);
});

evtSource.addEventListener("stock", (event) => {
  const newElement = document.createElement("li");
  const eventList = document.getElementById("list");
  const time = JSON.parse(event.data).time;
  newElement.textContent = `ping at ${time}`;
  eventList.appendChild(newElement);
});

5、参考资料

1、服务器发送事件
2、阮一峰—Server-Sent Events 教程


网站公告

今日签到

点亮在社区的每一天
去签到