你在一家火锅店点餐。传统餐厅的做法是后厨把所有菜全部做好,再一次性端上桌——你盯着空桌子,从下单到第一口菜要忍受漫长的等待。而火锅店完全不同:毛肚先切好先上,虾滑随后跟上,你边涮边吃,第一盘菜30秒就到位,后面的菜还在后厨飞转。AI服务的实时交互,正经历着从“传统餐厅”到“火锅店”的转变。 支撑这个转变的核心技术,叫“分块传输编码”(Chunked Transfer Encoding)。它让HTTP响应不再是一整块“团餐”,而是一条“传菜流水线”:服务端每生成一小段数据,就立刻打包发出,客户端收到一段就用一段。大模型流式对话中你看到的一个个蹦出的文字、实时翻译里逐句浮现的译文,背后都是这条流水线在高速运转。 本文将带你从零理解分块传输编码,以及流式API设计、SSE/WebSocket配合、前端流式消费和工程实践。读完后,你将明白如何设计一套低延迟、体验丝滑的实时AI交互API。

岗位关联

一句话价值:掌握分块传输编码与流式API设计,是打造低延迟、高体验AI实时交互服务(如ChatGPT式流式对话)的必备技能,也是AI后端工程师与全栈开发者的核心加分项。

目录
  1. 可视化演示
  2. 流式响应原理
  3. 流式API设计
  4. SSE协议配合
  5. WebSocket对比
  6. 前端流式消费
  7. 工程实践要点
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示HTTP分块传输编码数据流、SSE与WebSocket双向通信对比、以及LLM Token级流式推送过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

分块传输流程 服务器将数据分割为chunk 每个chunk独立传输到客户端 无需预知Content-Length

流式响应原理

你参加公司团建,行政订了“团餐”——所有人到齐才能开饭,你饿了也得等。改用“火锅自助”后,食材一盘一盘上,你边吃边等,第一盘毛肚30秒就到位。HTTP协议的世界里也存在这两种模式。传统响应体像团餐:服务端必须知道Content-Length,把所有数据准备齐全,一次性发给客户端。而分块传输编码让服务端化身“火锅传菜员”,数据边生成边发送,客户端边接收边处理。

分块传输编码是HTTP/1.1协议(RFC 7230)定义的一种传输机制。当响应头中带有Transfer-Encoding: chunked时,响应体不再是一个整体,而是一个接一个的“分块”(chunk)。每个分块自带长度标记,服务端可以在不知道总长度的情况下开始发送,这正是AI模型流式输出的核心需求——模型生成第一个token时,谁也不知道最终会生成多少字。

分块的数据格式非常简洁:每个分块由“十六进制长度行 + 数据 + 回车换行”组成,最后以0\r\n\r\n作为终止块标识传输结束。客户端解析到0长度的分块,就知道数据全部到达。注意分块传输与Content-Length互斥——因为总长度未知,响应头中不会出现Content-Length。此外,分块编码也能用于流式压缩数据的传输,服务端可以先整体压缩再逐块发送。

回到火锅店的比喻:分块传输的每个分块就像一只小餐盘——盘子上贴着重量标签(size_hex),里面装着菜品(data),厨房做好一盘端一盘,最后上一盘写着“0“的空盘示意”菜上齐了”。你不需要等所有菜做好,第一盘毛肚下肚时,后面的虾滑还在锅里翻滚呢。

chunked响应=sizehex\r\n data\r\n⏟第1块+sizehex\r\n data\r\n⏟第2块+⋯+0\r\n\r\n⏟终止块\displaystyle \text{chunked响应} = \underbrace{\text{size}_{hex} \backslash r \backslash n \ \text{data} \backslash r \backslash n}_{\text{第1块}} + \underbrace{\text{size}_{hex} \backslash r \backslash n \ \text{data} \backslash r \backslash n}_{\text{第2块}} + \cdots + \underbrace{0 \backslash r \backslash n \backslash r \backslash n}_{\text{终止块}}
通俗地说:每个分块是一盘贴着“重量标签”的菜,最后上一盘写着"0"的空盘示意菜已上齐。
  1. size_hex每块数据的字节数以十六进制表示,如4表示4字节、1f表示31字节,该行以\r\n结尾。
    sizehex\text{size}_{hex}
  2. data实际数据内容,紧随长度行之后,数据后跟\r\n作为本块结尾。
    data\text{data}
  3. 终止块发送0\r\n\r\n表示所有分块传输完毕,连接可复用或关闭。
    0\r\n\r\n0 \backslash r \backslash n \backslash r \backslash n

流式API设计

快递公司设计分拣线时,最核心的问题是:攒够一整车再发,还是来一件发一件?前者成本低但慢,后者快但运力利用率低。AI服务设计流式API时,面临的正是同样的权衡:是等模型把完整答案生成了再返回,还是边生成边返回?

流式API设计的核心思路是“增量生成,边生成边返回”。以AI对话为例,模型每解码出一个token,服务端就写入响应流并立即flush(刷新到网络缓冲区),而不是等完整回答生成完毕。响应流在传输层通常依赖分块传输编码,在应用层则有三种主流形态:纯HTTP流式(如JSON Lines增量返回)、SSE(Server-Sent Events)、WebSocket。选型时主要看交互是单向推送还是双向对话。

衡量流式体验最关键的指标是TTFT(Time To First Token,首字延迟),即从客户端发出请求到收到第一个数据块的时间。设计时还要权衡分块大小:分块过小会导致网络包数量暴增、吞吐降低;分块过大会推高TTFT、体验变差。AI对话场景通常追求几字节到几十字节的小分块,让首字尽快闪现。

好的分拣线,第一件包裹永远最快出发。AI流式API同样应该让第一个token“抢跑”——用户看到第一个字的瞬间,就建立了“它在思考、它回应我了”的心理预期,后续内容即使有节奏波动也能被容忍。TTFT是整个流式体验的地基。

TTFT=Tfirst_token−Trequest_sent\displaystyle \text{TTFT} = T_{\text{first\_token}} - T_{\text{request\_sent}}
通俗地说:TTFT就是你喊了一声“上菜”,到第一盘菜端上桌之间的时间。
  1. T_first_token客户端收到服务端返回第一个数据块的时间点,是流式体验的“第一公里”。
    Tfirst_tokenT_{\text{first\_token}}
  2. T_request_sent客户端发出HTTP请求的时间点,TTFT的起算基准。
    Trequest_sentT_{\text{request\_sent}}
  3. TTFT计算两者相减得到首字延迟。流式API设计的核心目标之一就是让TTFT尽可能小。
    TTFT=Tfirst_token−Trequest_sent\text{TTFT} = T_{\text{first\_token}} - T_{\text{request\_sent}}

SSE协议配合

包裹发出后,物流系统给你发短信:“您的包裹已从杭州发出。”你只能接收通知,不能通过短信回复快递公司。SSE(Server-Sent Events)正是这种单向推送机制——服务端持续把事件推给客户端,客户端只负责接收和展示。

SSE是一种基于HTTP的应用层协议,响应Content-Type为text/event-stream。它天然兼容分块传输编码:服务端把每个事件作为一个数据块发送,浏览器通过EventSource API自动解析。相比WebSocket,SSE不需要复杂的握手和帧协议,还自带断线重连能力,正好契合AI流式对话“只读模型输出”的场景——用户通常只需要看AI说话,并不需要中途发指令。

SSE的消息格式非常简洁。一条消息由若干行以\n结尾的字段组成,以空行(\n\n)结束。常用字段包括:data(数据)、event(事件类型)、id(事件ID)、retry(重连间隔)。其中id配合Last-Event-ID请求头可以实现断点续传——客户端重连时带上最后收到的事件ID,服务端从该位置继续推送,对话不丢字。

物流短信通知虽然单向,但因为轻巧可靠,成为包裹追踪的标配。SSE之于AI流式对话,也是那个“轻巧可靠”的单向通道——不需要双向对话?用SSE准没错。

SSE消息=event: type⏟可选+data: payload⏟必填,可多行+id: seq⏟可选+\n\n\displaystyle \text{SSE消息} = \underbrace{\text{event: } type}_{\text{可选}} + \underbrace{\text{data: } payload}_{\text{必填,可多行}} + \underbrace{\text{id: } seq}_{\text{可选}} + \backslash n \backslash n
通俗地说:SSE短信通知单,每一条消息是一张格式固定的通知单,以空行作为单据分隔线。
  1. event字段声明事件类型,前端addEventListener('xxx')监听对应类型;省略时触发默认onmessage。
    event: type\text{event: type}
  2. data字段消息数据,一行一个data,多行会以换行符拼接为一条消息。
    data: payload\text{data: payload}
  3. id字段事件ID,浏览器重连时自动通过Last-Event-ID头发给服务端,实现续传。
    id: seq\text{id: seq}
  4. 空行结束\n\n表示一条事件的结束,浏览器收到后触发事件回调。
    \n\n\backslash n \backslash n

WebSocket对比

当快递员需要对你说“您家电梯在维修,我放丰巢了”,而你回复“好的放丰巢吧”时,单向短信不够用了。你需要一条专属客服专线,双方随时可以开口。WebSocket就是这个“专属专线”。

WebSocket是建立在TCP之上的全双工通信协议。客户端先发送HTTP Upgrade请求,服务端回复101 Switching Protocols后,连接升级为WebSocket通道,此后双方可以随时互发消息。与SSE的单向推送相比,WebSocket天然支持“用户打断AI生成”这类交互——客户端可以在AI滔滔不绝时发送一条“停一下”的消息。

WebSocket帧结构比SSE复杂。每个帧以一个或多个字节的头开始:FIN标记是否最后一帧,OPCODE标记帧类型(1文本、2二进制、8关闭),MASK标记客户端是否掩码,LEN记录负载长度。正是这些位域让WebSocket能高效承载双向、二进制、大消息等丰富场景,但也带来了比SSE更高的协议和运维复杂度。选择时遵循一个原则:服务端→客户端单向流用SSE,强双向交互(协同编辑、实时游戏、语音对讲)用WebSocket。

专属专线有专属专线的价格。WebSocket需要维护长连接状态、处理心跳和重连、配置反向代理的超时策略,运维成本明显高于HTTP流的SSE。在AI对话这个“服务端单向输出为主”的场景里,大多数团队最终选择SSE而不是WebSocket——就像你不会为每天一次的快递通知专门租一条专线。

WebSocket帧头=FIN(1)+RSV(3)+OPCODE(4)+MASK(1)+LEN(7/16/64)⏟2∼14字节\displaystyle \text{WebSocket帧头} = \underbrace{\text{FIN}(1) + \text{RSV}(3) + \text{OPCODE}(4) + \text{MASK}(1) + \text{LEN}(7/16/64)}_{2 \sim 14 \text{字节}}
通俗地说:WebSocket帧头是快递面单上的一串编码,用几个比特就写清了包裹的类型、大小和拆包规则。
  1. FIN1位,值为1表示当前帧是消息的最后一帧,消息可能被拆成多帧发送。
    FIN=1\text{FIN} = 1
  2. OPCODE4位,帧类型标识,1为文本帧、2为二进制帧、8为关闭帧、9为ping。
    OPCODE=1 (文本)\text{OPCODE} = 1 \ (\text{文本})
  3. MASK1位,客户端发往服务端的帧必须置1并使用4字节掩码密钥,防止缓存投毒攻击。
    MASK=1\text{MASK} = 1
  4. LEN负载长度,7位可表达0~125,更大时扩展为16位或64位。
    LEN=7/16/64 bit\text{LEN} = 7/16/64 \ \text{bit}

前端流式消费

你是收件人,签收方式决定了你的体验。如果坚持“整车到齐再统一搬回家”,那和团购没区别;聪明的做法是“来一件拆一件”,让拆箱的快乐提前发生。前端消费流式API,关键就在这个“拆箱”姿势。

很多人以为用了fetch就自动有流式效果,实际上response.json()和response.text()都会等整个body到达后才解析,完全失去了流式意义。真正流式的姿势是:拿到response.body(一个ReadableStream),调用getReader()获得读取器,然后通过for await...of循环逐块读取数据。每读到一块,就立即解析并渲染到页面。

对于SSE流,可以更简单——原生EventSource对象自动解析text/event-stream,onmessage回调逐条处理事件。但EventSource不支持自定义请求头,需要携带认证信息时,得用fetch + ReadableStream手动解析。注意流式读取是“推”模式,消费者处理速度跟不上时会产生背压,可以通过reader.cancel()主动取消订阅,停止后续数据的到达。

正确的“拆箱姿势”让流式体验真正落地。当AI模型的每个token到达时,页面上的文字像打字机一样逐字浮现,用户感受到的不是“等待一个答案”,而是“观看一个思考过程”。这就是分块传输编码在前端的价值兑现时刻。

渲染进度=∑i=1nlen(chunki)total×100%\displaystyle \text{渲染进度} = \frac{\displaystyle\sum_{i=1}^{n} \text{len}(\text{chunk}_i)}{\text{total}} \times 100\%
通俗地说:每收到一个包裹就称一下重量、累计起来,就能算出已经收到了整批货的百分之几。
  1. chunk_iReadableStream读取到的第i个数据块,可能是几字节到几KB的文本片段。
    chunki\text{chunk}_i
  2. len(chunk_i)当前块的数据长度,逐块累加得到已接收总量。
    len(chunki)\text{len}(\text{chunk}_i)
  3. total完整响应的总长度,可从协议元数据或内容特征预估,用于计算进度百分比。
    total\text{total}

工程实践要点

快递物流中心里,每辆车有GPS心跳信号、分拣线有缓冲区和限流阀、派送失败有“过会儿再送”的智能重试。一个生产级的流式API服务,同样需要这三件套:心跳保活、背压控制、退避重试。

超时与心跳:流式长连接可能持续几十秒甚至几分钟,中间设备的空闲超时会悄悄断开连接。服务端应定期发送心跳包(SSE注释行": ping\n\n"或WebSocket的ping帧)维持连接活跃,客户端也应设置合理的超时——不是总超时,而是“相邻两个块之间的间隔超时”,超过即判定连接异常。背压控制:模型推理速度可能超过客户端消费速度,服务端需要缓冲区削峰填谷,防止内存被积压数据撑爆。

错误重试是流式服务的保命符。网络抖动导致连接中断时,客户端应自动重连。重试策略推荐指数退避(Exponential Backoff)+ 随机抖动(Jitter):每次重试的等待时间随尝试次数指数增长,并叠加一个随机量,避免大量客户端在同一时刻集体重连造成“重试风暴”(Thundering Herd)。

有了心跳、背压和退避重试,这条“快递分拣流水线”才能昼夜不息地运转。AI流式API的工程地基,就藏在这些看似琐碎的细节里——它们决定了你的服务在真实网络环境下,是丝滑还是翻车。

backoff=min⁡(cap, base×2attempt)+jitter\displaystyle \text{backoff} = \min\left(\text{cap}, \ \text{base} \times 2^{\text{attempt}}\right) + \text{jitter}
通俗地说:第一次等待0.5秒,第二次1秒,第三次2秒……最多等30秒,再加一个随机小偏移,让大家的“重试闹钟”不撞车。
  1. base初始退避时间,如0.5秒,第一次重试等待约base时间。
    base=0.5s\text{base} = 0.5 \text{s}
  2. attempt当前重试次数,从0开始,每失败一次尝试次数加1。
    attempt=0,1,2,…\text{attempt} = 0, 1, 2, \dots
  3. cap退避时间上限,如30秒,防止等待时间无限增长。
    cap=30s\text{cap} = 30 \text{s}
  4. jitter随机抖动值,取[0, base)间的随机数,让重试时间错开。
    jitter∈[0,base)\text{jitter} \in [0, \text{base})

AI 应用场景

大模型流式对话(ChatGPT类产品)

阶段:模型推理与输出阶段

解决的问题:传统方式必须等模型生成完整回答才返回,首字延迟高达数十秒,用户盯着空屏干等。AI通过SSE + 分块传输编码逐token推送,用户几乎立即看到“打字机效果”。

具体案例:某AI客服产品接入流式API后,用户等待感显著下降,会话完成率提升23%。后端用FastAPI的StreamingResponse输出SSE事件流,前端fetch + ReadableStream逐块渲染Markdown。

实时翻译与语音转写平台

阶段:数据接入与推理输出阶段

解决的问题:同声传译场景中,说话人说完一句话之前就需要开始翻译。流式API让翻译结果随语音输入逐句输出,而不是等整段语音识别完再一次性返回。

具体案例:某国际会议系统使用WebSocket双向流式通道:客户端持续上传音频分片,服务端流式返回识别 + 翻译结果,端到端延迟小于300ms。选择WebSocket是因为需要同时上传音频流和接收文本流,属于强双向交互。

Python 演示

python50 行
"""演示分块传输编码:AI流式对话的编码与消费""" import time  def chunk_encode(data, chunk_size=10):    """服务端:将AI生成的文本编码为HTTP分块传输格式"""    chunks = []    for i in range(0, len(data), chunk_size):        chunk = data[i:i + chunk_size]        size = format(len(chunk), 'x')        chunks.append(f"{size}\r\n{chunk}\r\n")    chunks.append("0\r\n\r\n")    return "".join(chunks)  def chunk_decode(stream):    """客户端:解析分块传输格式,逐个产出数据块"""    pos = 0    while pos < len(stream):        line_end = stream.index("\r\n", pos)        size = int(stream[pos:line_end], 16)        pos = line_end + 2        if size == 0:            break        chunk = stream[pos:pos + size]        yield chunk        pos += size + 2  def main():    # 模拟AI模型逐token生成回答    answer = "分块传输编码让AI对话像打字机一样流畅。"    print("服务端编码:")    encoded = chunk_encode(answer, chunk_size=10)    print(encoded)     print("客户端流式消费:")    received = ""    for chunk in chunk_decode(encoded):        received += chunk        print(f"  收到{len(chunk):2d}字节: {chunk!r}")        time.sleep(0.1)  # 模拟逐字渲染耗时    print(f"完整响应: {received}")    assert received == answer    print("分块编码与解码校验通过!")  if __name__ == "__main__":    main()
目的:用标准库模拟HTTP分块传输编码的编码与解码过程,演示“服务端边生成边发送、客户端边接收边渲染”的流式思维。
  1. size = format(len(chunk), 'x')将块长度转为十六进制字符串,符合HTTP分块传输规范
  2. chunks.append("0\r\n\r\n")追加终止块,标识所有数据发送完毕
  3. line_end = stream.index("\r\n", pos)从当前位置找到长度行的结束位置
  4. int(stream[pos:line_end], 16)把十六进制长度解析为整数
  5. yield chunk生成器逐块产出数据,模拟真实客户端流式消费

常见误区

误区很多初学者以为设置`Transfer-Encoding: chunked`就等于实现了SSE,觉得只要响应是分多次到达的就是SSE。

正解分块传输编码是HTTP传输层机制,SSE是应用层协议。SSE依赖分块传输或类似流式机制来推送事件,但分块传输的响应不一定是SSE——它可以是普通流式文本、JSON Lines等。类比来说,快递分拣线(分块)是基础设施,短信通知(SSE)是建立在分拣线之上的一种服务形态。

为何常见:两者都表现为“数据分多次到达”,初学者容易混淆底层传输机制与应用层协议的分层关系。

误区既然WebSocket能双向通信,那所有实时场景都应该用WebSocket,SSE是过时技术。

正解WebSocket是全双工,但握手复杂、需维护长连接状态;SSE是单向的,但轻量、自带断线重连、基于HTTP可直接使用CDN和代理。AI流式对话场景中服务端→客户端单向推送占绝对主导,SSE更简单可靠。专属专线(WebSocket)虽强但贵,短信通知(SSE)往往够用且便宜。

为何常见:看到“WebSocket更强大”就认为全面优于SSE,忽略了架构复杂度和场景匹配度。

误区认为HTTP响应必须一次性完整返回,否则客户端会认为响应出错。

正解只要正确使用Transfer-Encoding: chunked,客户端就能正确处理流式数据。分块编码用终止块0\r\n\r\n标识结束,而不是依赖连接关闭。就像火锅店不会等所有菜做好才开席,一盘盘上菜完全符合规则。

为何常见:对HTTP协议的理解停留在“请求-响应-关闭”的简单模型,不了解Keep-Alive和流式响应的协议细节。

误区写了fetch API就自动获得流式效果,`response.json()`会边接收边解析数据。

正解response.json()会等待整个body到达后才开始解析,完全不流式。要流式必须用response.body.getReader()配合ReadableStream逐块读取。类比来说,json()等整车快递到齐才拆箱,getReader()是来一件拆一件。

为何常见:fetch API封装得很好,初学者不知道json()内部会消费整个流,误以为Promise返回就是流式。

误区为了减少分块数量、降低传输开销,把每个分块设得特别大,认为这样效率最高。

正解分块大小的设计取决于业务。AI流式对话需要小分块(几字节到几十字节)来降低TTFT,让用户尽快看到首个token;大文件下载适合大分块减少开销。分块大小是“实时性 vs 吞吐量”的权衡。火锅要一片片涮才入味,一整坨肉丢进去反而砸锅。

为何常见:直觉上“少而大”效率高,忽略了实时交互场景对首字延迟的高度敏感。

面试题

Q1. 关于分块传输编码,以下哪项描述正确?

单选题 · easy

  • A.分块传输依赖Content-Length指定总长
  • B.分块传输以0\r\n\r\n作为终止标记← 正确答案
  • C.分块传输须待全部数据就绪再发送
  • D.分块传输编码仅支持文本类型数据

答案:B。分块传输编码不需要Content-Length(A错),它边生成边发送(C错),支持二进制数据(D错)。终止块0\r\n\r\n标识传输完毕(B对)。

Q2. 在AI流式对话中,TTFT(首字延迟)的含义是?

单选题 · mid

  • A.从请求发起到收到完整响应的总时间
  • B.从请求发起到收到第一个数据块的时间← 正确答案
  • C.从服务端生成首个字再到客户端的耗时
  • D.从服务端收到请求到生成首个字的时间

答案:B。TTFT指从客户端发出请求(T_request_sent)到收到第一个token/数据块(T_first_token)的时间差。C和D只是TTFT的一部分片段,A是总响应时间,都不完整。

Q3. SSE(Server-Sent Events)使用哪种MIME类型?

单选题 · mid

  • A.application/json格式的数据
  • B.text/event-stream的MIME类型← 正确答案
  • C.text/plain的普通文本类型
  • D.multipart/form-data的表单类型

答案:B。SSE的Content-Type必须是text/event-stream,浏览器识别该类型后才会解析事件流并触发onmessage等回调。其他三个MIME类型均不支持SSE的事件流语义。

Q4. WebSocket与SSE的关键区别是?

单选题 · mid

  • A.WebSocket只能服务端发客户端收
  • B.SSE支持全双工双向实时通信
  • C.WebSocket支持双向全双工通信← 正确答案
  • D.SSE基于UDP协议进行传输

答案:C。WebSocket是TCP之上的全双工协议,客户端和服务端可同时互发消息(C对)。SSE是单向的(仅服务端→客户端)且基于HTTP而非UDP,A和B和D均错误。

Q5. 前端实现流式消费需要哪些Web API?(多选)

多选题 · mid

  • A.response.json()同步解析
  • B.response.body.getReader()← 正确答案
  • C.ReadableStream的for await← 正确答案
  • D.response.text()完整接收

答案:BC。getReader()返回读取器,配合ReadableStream可以用for await...of逐块读取数据(B、C对)。response.json()和response.text()都会等整个body到达,不是流式API。

Q6. 分块传输编码中,分块大小用什么进制表示?

单选题 · easy

  • A.使用十进制数字表示分块大小
  • B.使用十六进制数字表示分块大小← 正确答案
  • C.使用八进制数字表示分块大小
  • D.使用二进制数字表示分块大小

答案:B。分块大小用十六进制表示,如4表示4字节、1f表示31字节,以\r\n结尾。这是HTTP/1.1 RFC 7230规定的格式。

Q7. 关于流式API的工程实践,以下哪些措施能提升体验?(多选)

多选题 · hard

  • A.减小分块大小以降低首字延迟← 正确答案
  • B.心跳包保活空闲连接防超时← 正确答案
  • C.指数退避加随机抖动控制重试← 正确答案
  • D.禁用TCP保活以节省资源

答案:ABC。小分块能降低TTFT(A对);心跳包防止NAT/代理超时断开连接(B对);指数退避+抖动避免重试风暴(C对)。禁用TCP保活反而会导致连接被中间设备回收(D错)。

Q8. gzip压缩与分块传输编码的关系是?

单选题 · hard

  • A.压缩后必须使用分块传输编码
  • B.压缩可以先于分块传输在服务端进行← 正确答案
  • C.分块传输会破坏gzip压缩的数据
  • D.压缩和分块传输是互斥的两种方式

答案:B。服务端可以先用gzip压缩整个响应体,再对压缩后的数据做分块传输。分块编码是传输层机制,不影响应用层压缩。A绝对化错误,C和D混淆了层次关系。

Q9. 客户端收到分块响应时,如何判断数据已全部到达?

单选题 · easy

  • A.收到以0起始的终止块标记← 正确答案
  • B.收到Chunked的end响应头
  • C.连接空闲超过30秒判定完成
  • D.数据长度达到Content-Length

答案:A。分块编码以0\r\n\r\n作为终止块,客户端解析到size为0的块即表示传输结束。分块编码没有Content-Length头(D错),B和C是臆造的判断方式。

Q10. 设计AI流式对话API时,以下哪些属于合理设计?(多选)

多选题 · hard

  • A.用SSE推送每个增量token← 正确答案
  • B.客户端用ReadableStream逐块渲染← 正确答案
  • C.服务端攒满1KB才flush一次
  • D.断线后基于Last-Event-ID续传← 正确答案

答案:ABD。SSE推送增量和ReadableStream逐块消费是流式对话的关键(A、B对);Last-Event-ID实现断点续传,防止断线丢字(D对)。C攒满1KB才flush会显著推高TTFT,对话场景应尽快flush(C错)。