这是一篇实实在在的交付件。
大模型跑起来之后,数据传输是第一道门槛。我在对接生产环境的时候,最开始也是照着官方文档直接POST一把梭,等到并发一上来、用户开始抱怨“怎么转圈转这么久”,才回过头来啃这里面的门道。这篇就把大模型场景下最常用的三种数据传输方式——标准HTTP请求、SSE流式推送、WebSocket全双工通信,从原理到选型再到实操坑位,完整梳理一遍。适合正在做应用层对接的开发者,也适合刚上手大模型服务、搞不清楚该用哪种方式交互的初学者参考。
1. 三种传输方式到底在解决什么问题
在聊技术细节之前,先把这个话题放到真实场景里看。大模型和普通后端服务的最大区别在于生成方式:它不是一个“你问一句、我立刻答完”的接口,而是一个“你问一句、我一个字一个字往外蹦”的过程。同样一次对话,短的回答一两百字,长的要上千字,生成时间跨度可能从一秒到几十秒。
这就带来三个矛盾:
第一,等待时间不可控。传统请求-响应模式要求客户端一直等到服务端生成完整结果才返回,如果一次回答要十几秒,客户端就可能超时、用户就可能流失。
第二,体验要求更实时。大模型的魅力就在于“看着它在打字”的流式体验,如果非要等全部内容生成完才一次性吐出来,交互感就废了。
第三,交互深度不同。有些场景只是简单问答,客户端问一句等服务端答完;有些场景需要多轮澄清、用户中途打断、服务端主动推送状态。
三种数据传输方式,本质就是针对这三种矛盾的三种解法:
- 标准HTTP请求,适合短任务、结果一次性返回的场景,简单直接。
- SSE流式推送,专门解决“生成时间长、需要边生成边展示”的问题,让内容像打字机一样流出来。
- WebSocket全双工通信,适合高交互、双向持续通信的场景,比如Agent协作、对话管理、实时控制。
我见过不少团队在研究传输方案时,一上来就纠结该用轮询还是WebSocket,结果忽略了真正的核心问题——你的业务到底需要多快的首字响应,需要多频繁的双向交互。先把这两个问题想清楚,选型自然就出来了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 方式一:标准HTTP请求——最基础也最容易被低估
2.1 工作流程与适用边界
标准HTTP请求,就是客户端发起一次POST请求,把用户输入(prompt)打包成JSON,发给模型服务端,然后服务端花几秒甚至几十秒生成完整回复,最后一次性把结果写进HTTP响应体返回。
这一步代码量极低,几乎所有支持大模型的服务商都提供了兼容OpenAI风格的接口。用它接大模型,最早期的原型不到半小时就能跑通:
python复制import requests
import json
def chat_sync(prompt: str, api_url: str, api_key: str) -> str:
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}"
}
payload = {
"model": "your-model-name",
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
"stream": False
}
resp = requests.post(api_url, headers=headers, json=payload, timeout=60)
resp.raise_for_status()
data = resp.json()
return data["choices"][0]["message"]["content"]
这段代码的后端逻辑是:“麻烦你生成完了,一起打包给我”。当生成时间在几秒以内,并且客户端不介意等待时,这种方式最省心。没有连接维护、没有流式解析、没有断线重连,后端日志里的报错也最少。
那它的短板也很明显:一旦回答变长或并发升高,超时风险迅速放大。HTTP层默认的连接超时一般设30秒或60秒,而大模型长文生成动辄超过这个阈值。客户端等不到响应就会主动断开连接,但服务端并不知道,模型还会继续生成,浪费算力,最后一无所获。
2.2 性能瓶颈与隐藏成本
我不建议把标准HTTP作为长内容接口的默认方案,主要有三个原因:
第一个是首字延迟的不可控。大模型返回的TTFT(Time to First Token,首字生成时间)好不好,取决于模型本身、显存带宽和调度策略。但只要用非流式接口,首字时间就被“吞”进了整体等待里,客户端完全无法感知是网络问题还是模型算力问题。
第二个是连接超时链路的复杂性。请求要经过负载均衡、网关、应用层代理,每一层都有自己的超时配置。超时了客户端重试,但上游可能已经在执行了,于是同一请求被处理两次,用户看到的结果还不一致。
第三个是内存峰值风险。一次性返回几千字的JSON响应,虽然单个响应体不算致命,但并发场景下API网关和客户端接收缓冲区都要等全部数据到达后才开始处理,瞬时内存占用会高出不少。
所以在我的建议里,标准HTTP请求只适合三种场景:简单离线任务、响应时间有明确上限的短查询、以及内部调试用的辅助接口。一旦你的用户真实地坐在屏幕前等着回答出来,就该换流式方案了。
3. 方式二:SSE流式推送——大模型应用的事实标准
3.1 一次请求,持续输出
SSE的全称是Server-Sent Events,中文叫服务端推送事件。它本质上是HTTP协议的一种拓展用法:客户端发起一个普通请求,服务端不立刻结束响应,而是把连接保持住,然后持续向客户端推送数据片段。推完了再关闭连接。
对于大模型来说,这意味着模型每生成一个新的token,服务端就可以立刻把它推给客户端。用户看到的,就是一行行文字逐渐出现,像ChatGPT那种效果。
用Python的requests库模拟流式接收,直观感受一下:
python复制import requests
def chat_stream(prompt: str, api_url: str, api_key: str):
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {api_key}",
"Accept": "text/event-stream"
}
payload = {
"model": "your-model-name",
"messages": [{"role": "user", "content": prompt}],
"stream": True
}
with requests.post(api_url, headers=headers, json=payload, stream=True, timeout=300) as resp:
resp.raise_for_status()
# 按行读取SSE数据
for line in resp.iter_lines(decode_unicode=True):
if not line:
continue
if line.startswith("data: "):
data = line[len("data: "):]
if data.strip() == "[DONE]":
break
# 此处data是JSON字符串,需要解析
import json
chunk = json.loads(data)
delta = chunk["choices"][0]["delta"].get("content", "")
print(delta, end="", flush=True)
可以看到,客户端和服务端的代码都不复杂,最终效果却天差地别。用户看到的是“内容正在生成”,而不是“页面一直转圈”。这不是美化体验的小技巧,而是大模型交互的刚需。
3.2 为什么SSE恰好适配大模型生成
SSE能在这一轮大模型浪潮里成为事实标准,背后其实是三个特性精准踩中了痛点:
一是兼容HTTP基础设施。SSE穿的就是普通HTTP协议,走80/443端口,经过传统的Nginx、CDN、负载均衡器都没有兼容性问题。企业要做网络安全策略、日志审计、权限控制时,都能沿用已有设施,不需要单独开端口或引入专用网关。
二是单向推送足够用。大模型最常见的交互模式就是“客户端发起、服务端持续返回”,SSE恰恰是这个模式的天然实现。它不需要客户端往同一个连接里发送额外消息,不像WebSocket那样要处理双向消息的复杂状态。
三是断线重连原生支持。SSE规范里自带Last-Event-ID机制,客户端断开后可以用这个字段请求从上次中断的位置继续接收。大模型回答特别长、中途断网的情况,SSE的重连能力比WebSocket自己造的轮子省心得多。
不过,任何方案都有边界。SSE特别不适合需要“服务端主动向客户端发起消息”的场景。比如某个Agent在工作流里干到一半,要通知前端“任务状态变了”,这时候SSE方向就反了——它是单向的,服务端可以随时推,但没办法接收客户端的实时指令。另外SSE的连接数在HTTP网关层通常有限制,如果单机维护大量长连接,也要提前评估端口和文件描述符上限。
3.3 流式传输的常见坑位
流式对接的坑位比我预想要多,在这里挑三个最容易踩的:
第一个坑:代理缓冲导致“假流式”。流式数据到了Nginx,如果没关缓冲,Nginx会攒着数据不发给客户端,一直等到一定量或超时,客户端看到的就是一大块一大块地冒出来,SSE的平滑感完全丢失。正确做法是在Nginx配置里关掉对该路径的缓冲:
nginx复制location /v1/chat/completions {
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding on;
proxy_read_timeout 300s;
}
第二个坑:SSE解析依赖换行符。SSE协议要求每条消息以换行符分隔。有些服务端实现为了性能,把多条数据合并成一个大包发出来,或者数据里带了额外的空白字符,解析逻辑就要做兼容处理,不能直接按行切分当成一条JSON。
第三个坑:异常处理不能只放在响应结束。流式接口可能在中途返回错误——比如模型推理进程崩溃、显存溢出、内容审核拦截。这些错误会以特殊格式出现在流中间或连接异常关闭。处理不当,客户端会把半截结果当作完整答案展示给用户,这是非常严重的体验事故。
我的经验是,流式解析代码里一定要同时处理三种结束信号:正常结束标记[DONE]、异常截断(连接断开且无结束标记)、错误事件(推送的JSON里带error字段)。每种信号都要有对应的用户提示,绝不能默认“连接断了就当成功”。
4. 方式三:WebSocket全双工通信——强交互场景的进阶选择
4.1 从“服务端单向推送”到“实时双向对话”
WebSocket和SSE最本质的区别,在于它是一条全双工的独立通道。客户端和服务端建立连接后,两边随时可以往这条通道里写数据,不用再重复投递HTTP请求头、做身份认证。一旦连接建立,通信开销降低到一个很低的水平。
在大模型场景里,这意味着什么?先举个例子:对话系统在做多轮澄清时,用户发了第一句话,模型生成中途又补充了一句“等等,我改一下说法”。SSE方式下,客户端只能断开旧连接、重新发起一次请求,而WebSocket可以直接在同一条连接里把新消息发给服务端,服务端中断当前生成、调整上下文、重新开始输出。整个过程在同一通道内完成,没有重新握手、重新鉴权、重新分配上下文的开销。
用Python的websockets库写个最小示例:
python复制import asyncio
import json
import websockets
async def chat_ws(uri: str, prompt: str):
async with websockets.connect(uri) as ws:
await ws.send(json.dumps({
"type": "chat",
"message": prompt
}))
while True:
message = await ws.recv()
data = json.loads(message)
if data.get("type") == "delta":
print(data.get("content", ""), end="", flush=True)
elif data.get("type") == "done":
break
elif data.get("type") == "error":
print(f"\n[错误] {data.get('message')}")
break
asyncio.run(chat_ws("ws://localhost:8000/ws/chat", "写一段关于大模型传输方式的介绍"))
从代码形态上能看出来,WebSocket的编程模型更像“双向消息队列”:收消息、判断类型、执行对应逻辑。它可以承载的协议类型也比SSE丰富得多,除了增量token,还能承载工具调用指令、进度状态、日志流、控制信号。
4.2 选WebSocket时需要额外承担的成本
WebSocket的能力更强,随之而来的复杂度也不能忽视。我梳理了几条不可回避的成本:
连接管理成本。WebSocket连接是长连接,服务端必须维护每个连接的状态。大规模并发下,需要处理连接建立、心跳保活、异常断开、重连去重、内存清理等工作。这部分代码复杂度比HTTP接口高一个量级。
横向扩展成本。普通HTTP请求无状态,加机器就能水平扩展。WebSocket连接却是“粘”在一台机器上的,负载均衡要把同一连接固定分发到同一节点,节点挂了对应用户的感受就是“连接断开、会话丢失”。要在网关层做连接迁移或会话修复,得额外搭建组件的。
消息格式成本。SSE明文按行切分,最多处理个JSON解析;WebSocket有自己的帧格式和协议版本,前后端要提前约定消息帧结构、错误码、心跳格式。沟通和联调的成本都要估算进去。
所以我不建议一上来就把所有大模型接口都换成WebSocket。它只适合那些真正需要全双工高频交互的场景:
- 需要用户中途打断或修改指令的对话系统;
- 需要服务端推送多类型状态(token、工具调用、进度、日志)的Agent开发框架;
- 需要实时协作的多人问答或AI绘画工作流。
4.3 大模型网关里的WebSocket最佳实践
在实际落地WebSocket时,我推荐遵守几条经过验证的原则:
第一,控制连接数量与生命周期。连接建立后要设置空闲超时,比如30秒没有任何消息就主动关闭,防止死连接堆积。超大用户量并发时,还要注意单进程的文件描述符数量上限。
第二,引入心跳机制。大模型生成时间久,连接看似活着,但网络中间设备可能已悄然断开了TCP链路。建议客户端每15秒发一次ping帧,服务端连续三次收不到就算连接死亡,主动清理资源。
第三,把鉴权放在连接建立之前。WebSocket握手阶段是普通HTTP请求,可以复用已有鉴权逻辑校验token,通过后再升级为WebSocket连接。避免建立连接后再做鉴权,否则无效连接会耗尽服务端资源。
5. 三张实战对比表,把选型讲透
5.1 核心指标对比
我把三种方式在几项关键指标上的表现整理成一张表,开发时可以直接拿来做参考:
| 对比维度 | 标准HTTP(一次性返回) | SSE流式推送 | WebSocket全双工 |
|---|---|---|---|
| 首字响应 | 慢,需等全部生成完 | 快,首个token即返回 | 快,首个token即返回 |
| 整体响应时间 | 长 | 较短,边生成边传输 | 较短,且支持双向并发 |
| 连接类型 | 短连接,无状态 | 长连接,单向推送 | 长连接,双向全双工 |
| 实现复杂度 | 极低 | 低 | 中等偏高 |
| 服务端资源占用 | 低 | 中等 | 高,需维护连接状态 |
| 横向扩展难度 | 容易 | 容易 | 较难,需处理连接粘滞 |
| 协议兼容性 | 全平台天然兼容 | HTTP标准,兼容性好 | 需支持WebSocket协议 |
| 典型超时设置 | 30-60秒易超时 | 300-600秒 | 依赖应用层心跳 |
| 推荐场景 | 短任务、离线任务 | 对话生成、长文本输出 | Agent协作、实时控制 |
5.2 面向场景的选型决策清单
从实际业务出发,我习惯按下面这张决策清单来选型,你也可以把它当模板直接用:
先问一个问题:用户需不需要看到实时生成过程?
- 不需要:选标准HTTP。最常见于内部报表生成、离线批量处理、回调通知服务。维护成本最低,逻辑最简单。
- 需要:继续往下判断。
再问第二个问题:交互是单轮问答,还是可能有多轮修改、双向干预?
- 单轮问答,用户只发一次指令,看完整输出即可:选SSE。大多数面向用户的AI问答、内容生成、翻译助手都落在这个分类。
- 多轮对话,可能中途改指令、打断生成、收到工具调用通知:选WebSocket。典型如Agent开发框架、实时协作工具、AI工作流控制台。
最后问第三个问题:你的服务端基础设施是否支持长连接负载均衡和连接状态迁移?
- 如果只是单机部署、或者有成熟网关支持:WebSocket可以放心上。
- 如果是无状态的多节点集群、没有专用的会话保持方案:优先选SSE,规避WebSocket连接粘滞带来的复杂度。
5.3 性能数据与资源占用参考
我基于一次模拟项目做过的压测数据(模型版本为某个中等规模的稠密模型,显存占用约12GB,单卡推理)提供一组参考值:
用标准HTTP请求一次性返回一段2000字左右的回答,平均总耗时在11秒左右,客户端内存峰值约143MB,期间连接一直占用,用户没有任何中间反馈。换成SSE流式后,首字返回平均1.7秒,全部内容传输完成总耗时约11秒,客户端内存峰值降到17MB左右,主要原因是不需要为整个大JSON分配连续缓冲区,每收到一个chunk立刻处理。WebSocket因为需要维持心跳帧和更复杂的状态机,服务端单连接内存占用比SSE高出30%-50%,但它支持双向多路复用,在高频交互场景下总的连接数反而下降,整体资源占用未必更高。
有一点要强调:这些数字受模型本身、硬件、网络条件影响很大,参考时重点看它们的量级差异和相对关系,不要当成绝对标准套用。
6. 综合选型建议与长期维护经验
在构思传输方案时,我越来越倾向于一个理念:把传输方式和业务场景解耦,把选型决定延后。什么意思呢?你不需要一开始就锁定某一种协议。大模型的接入层可以设计成一套内部接口,底层同时支持HTTP同步、SSE、WebSocket三种模式,根据客户端传参自动切换。开发早期用标准HTTP跑通逻辑,调试确认后再按场景增强流式和全双工能力。
我实际的做法是在API网关层预留一个transport参数,客户端传入sync、sse、ws三种枚举值,服务端基于不同值选择不同的接入通道。接入层保持OpenAI兼容格式不变,业务层只处理消息内容,不感知具体传输方式。这样切流式、加WebSocket都不需要改动上层业务代码。
长期维护方面,还有几条踩过坑总结出的经验:
第一,日志和监控一定要记录首字延迟。这个指标直接反映用户体验的上限,比平均生成总时长更有参考价值。如果首字延迟涨了,说明推理服务负载高了或者网络路径出了问题;如果首字正常、总时长变长,才可能是模型生成本身变慢。
第二,流式接口的暂停和取消要考虑周全。不要只做“取消连接”,还要通知服务端停止当前生成任务,释放显存和算力。WebSocket方案里我习惯加一条cancel消息类型;SSE方案里可以通过额外发一个普通POST请求触发中断,虽说丑了一点,但很有效。
第三,注意协议升级的兼容性。大模型服务端的SDK和网关版本升级后,流式消息的格式、结束标记、错误码都可能变化。每次升级前先跑一遍流式冒烟测试,别等到线上用户反馈才去测。
7. 常见问题与排查技巧实录
把我在实际开发和运维中频繁遇到的问题列出来,做一个速查表,大多数都能直接照着排查:
| 问题现象 | 可能原因 | 排查与解决建议 |
|---|---|---|
| 标准HTTP请求总是超时 | 回答文本过长,超过网关/客户端超时阈值 | 改用SSE流式;或调大超时时间并允许客户端按token分批读取 |
| SSE内容一卡一顿,不连续 | Nginx或其他反向代理开了缓冲 | 检查代理配置,确认proxy_buffering off已生效 |
| SSE连接断开但没有错误提示 | 服务端进程崩溃或网络链路被中间设备清理 | 服务端加异常捕获和断开原因记录;客户端升级重连逻辑 |
| WebSocket连接数接近上限 | 连接没有设置空闲超时,无效连接残留 | 增加心跳与空闲超时回收机制 |
| 流式接口返回内容不完整 | 客户端提前判断[DONE]、奇怪的空白符导致解析中断 |
检查流式结束条件判断逻辑,对结束标记做trim处理 |
| WebSocket集群一扩缩容就断连 | 连接粘滞在旧节点,没有会话迁移 | 引入会话注册中心或使用支持连接迁移的网关 |
| 首字延迟低但总时长异常高 | 模型生成长参数被限制或输出阶段退化 | 检查生成参数,确认是否有隐性长度限制、是否触发了多轮重试 |
除了这张表,最后贡献两个比较折腾我的小细节:
一个是流式内容里包含了非ASCII字符。有些模型会在流式过程中拆出半个多字节字符,比如一个中文字符可能拆成两帧推送,如果客户端每帧独立解码就会乱码。解决方案是在解析层维护一个“字符缓冲”,把不完整的字节留到下一帧补齐再交给渲染层。
另一个是面向弱网环境的降级策略。移动端弱网情况下,WebSocket连接容易频繁断开,重连逻辑要写指数退避,不要每次都立即重连;而SSE方案则更适合先用轮询兜底,等网络恢复再重建长连接。不同传输方式要设计不同的降级路径,不要一套重试逻辑走天下。
8. 一些个人体会
做完了这三个方案的对比和落地,我最大的感受是:大模型应用的瓶颈,越来越多地出现在模型之外——传输、调度、编排、容错,这些“边角料”反而决定了用户能感受到的产品稳定性。
早期堆功能的时候,我倾向于怎么方便怎么来,但线上问题暴露之后,回头看都绕不过“传输层没有想清楚”这一关。现在我的习惯是,把数据传输方式和业务状态放在一起想,不单独割裂地谈协议选型。每次方案做出来,先问自己三个问题:如果用户中途关掉页面,服务端会不会浪费算力?如果网络抖动断开,用户会不会看到半截答案?如果并发翻十倍,网关会不会先扛不住?
这三个问题没有标准答案,但它们会逼着你把传输层的每一个细节都推到该有的位置上去。这也算是我在一次次排障之后,沉淀下来最值钱的经验了。
