C#通过API接口返回流式响应内容---分块编码方式

1、背景

上一篇文章《C#通过API接口返回流式响应内容---SSE方式》阐述了通过SSE(Server Send Event)方式,由服务器端推送数据到浏览器。本篇是通过分块编码的方式实现

2、效果

3、具体代码

3.1 API端实现

csharp 复制代码
[HttpGet]
public async Task ChunkedResponse()
{
    Response.ContentType = "text/plain";
    Response.Headers["Transfer-Encoding"] = "chunked"; //设置编码传输方式
    Response.Headers["Access-Control-Allow-Origin"] = "*"; //可以实现跨域访问

    //模拟DeepSeek的返回内容
    var phrases = new string[] { "你好!", "我是", "北京清华长庚医院", "信息管理部的", "郑林" };

    for (int i = 0; i < phrases.Length; i++)
    {
        //1、将内容转为UTF-8编码
        byte[] sendContentArray = Encoding.UTF8.GetBytes(phrases[i]);
        //2、内容的长度
        var sendContentLength = sendContentArray.Length.ToString("X"); //转为16进制的标识

        //3、将长度内容写入到Response中
        var chunkedContentLength = Encoding.UTF8.GetBytes($"{sendContentLength}\r\n");
        await Response.Body.WriteAsync(chunkedContentLength, 0, chunkedContentLength.Length);

        //4、将内容与CRLF(\r\n)一起写入Response中
        var chunkedContentArray = Encoding.UTF8.GetBytes($"{phrases[i]}\r\n");
        await Response.Body.WriteAsync(chunkedContentArray, 0, chunkedContentArray.Length);

        await Response.Body.FlushAsync();  // 强制发送数据块
        await Task.Delay(1000);  // 每块之间的延时
    }
	
		//5、将数据终止标识写入到响应
    byte[] endLenghtBuffer = Encoding.UTF8.GetBytes("0\r\n");
    await Response.Body.WriteAsync(endLenghtBuffer, 0, endLenghtBuffer.Length);

    byte[] endDataBuffer = Encoding.UTF8.GetBytes("\r\n");
    await Response.Body.WriteAsync(endDataBuffer, 0, endDataBuffer.Length);

    await Response.Body.FlushAsync();//强制发送数据块
}

3.2 浏览器端的代码

html 复制代码
<!DOCTYPE html>
<html>
<head>
<meta 
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>文档标题</title>
</head>
 
<body>
    <div id="content"></div>
    <script>
     fetch('http://localhost:5105/api/Stream/ChunkedResponse')
        .then(response => {
            if (!response.ok) {
                console.log('异常发生!');
            }
        return response.body;
    }).then(stream => {
        console.log('进入方法');
        const reader = stream.getReader();
        const decoder =new TextDecoder();
        const contentDiv =document.getElementById('content');
        
        function readChunk() {
            reader.read().then(({ value, done }) => {
                if (done) {
                    console.log('读取结束!');
                    return;
                }
                var contentserver=decoder.decode(value, {stream:true}) //解析数据
                contentDiv.innerHTML+= contentserver +'&nbsp;'; //将内容追加到界面中
                readChunk(); //递归读取数据
                //setTimeout(readChunk, 100); // 也可以使用延时1秒后继续读取下一个数据块。但没有区别,大家可以试试
        });
        }
        
        readChunk(); // 启动读取流程
  })
  .catch(error => {
    console.error('Error:', error);
  });
    </script>
</body>
 
</html>

4、原理

4.1 代码解释

服务器端的返回(就是响应(Response))中分块编码的样例如下:

html 复制代码
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked

25\r\n
This is the data in the first chunk\r\n

1C\r\n
and this is the second one\r\n

0\r\n
\r\n 

这里面包含3部分的信息

1、响应头信息中包含:Content-Type: text/plain以及Transfer-Encoding: chunked,因此在API的代码中使用了:

csharp 复制代码
Response.ContentType = "text/plain";
Response.Headers["Transfer-Encoding"] = "chunked"; //设置编码传输方式

2、分块编码的数据,是放在body中,并且满足如下规则

csharp 复制代码
[长度]\r\n
[数据]\r\n

(1)有两行,第一行表示长度、第二行表示数据

(2)长度为十六进制的数字,代表第二行数据的length【注意不包括\r\n

(3)每行的最后都必须有CRLF(\r\n)

(4)整个数据的最后,要以0为结尾,告知服务器,数据已经结束。即上面样例中的:

csharp 复制代码
0\r\n
\r\n 

3、英文字符与中文字符的长度不一样,因此需要进行utf-8编码后,统一计算长度

4.2 前端代码说明

Fetch API是现代 Web 开发中的一个重要组成部分,它提供了一种简单且一致的方式来访问网络资源。我们要读取的内容就是Response的body,在浏览器中Response.Body是ReadableStream对象,因此可以按照流对象处理方式既可。为了实现流式输出,可以使用TransformStream 类来处理数据流,也可以使用TextDecoder直接解析。两者的却别是:前者返回一个Uint8Array数组;后者直接解析数据,更加便捷。详细的可以参考第二个参考材料。

5、参考资料

1、分块编码(Transfer-Encoding: chunked)
2、fetch实现流式输出的实现原理

相关推荐
量子位4 天前
DeepDiver-V2来了,华为最新开源原生多智能体系统,“团战”深度研究效果惊人
ai编程·deepseek
封奚泽优4 天前
班级互动小程序(Python)
python·deepseek
陈敬雷-充电了么-CEO兼CTO4 天前
视频理解新纪元!VideoChat双模架构突破视频对话瓶颈,开启多模态交互智能时代
人工智能·chatgpt·大模型·多模态·世界模型·kimi·deepseek
大模型真好玩4 天前
大模型工程面试经典(五)—大模型微调与RAG该如何选?
人工智能·面试·deepseek
文 丰6 天前
【centos7】部署ollama+deepseek
centos·deepseek
文 丰6 天前
【openEuler 24.03 LTS SP2】真实实验部署ollama0.11.6+deepseekR1:1.5b+open-webUI
centos·deepseek
Ai尚研修-贾莲7 天前
全链路自主构建智慧科研写作系统——融合LLM语义理解、多智能体任务协同与n8n自动化工作流构建
人工智能·agent·智能体·deepseek·n8n·智慧科研写作·llm语义理解
AI大模型8 天前
基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程
docker·llm·deepseek
freephp9 天前
企业级LLM已经到了next level:LangChain + DeepSeek = 王炸
langchain·deepseek
孤狼程序员10 天前
DeepSeek文献太多太杂?一招制胜:学术论文检索的“核心公式”与提问艺术
人工智能·文献搜索·deepseek